From 351a6fca2a7a93c17d0d99623dccc500b9b13442 Mon Sep 17 00:00:00 2001 From: b7r6 Date: Thu, 12 Feb 2026 20:03:58 -0500 Subject: [PATCH] feat: add sensenet Buck2 build infrastructure Import sensenet's Buck2 module for build infrastructure: - Add BUILD.dhall with haskell_ffi_binary target - Import dhall prelude and toolchains from sensenet - Configure flake to use sensenet.flakeModules.buck2 Build: nix develop .#buck2-default && buck2 build //:slide GHC 9.12 package resolution uses ghc-pkg-id wrapper script to translate -package flags to -package-id flags (workaround for ghcWithPackages bug). --- .buckconfig | 28 ++ .clang-format | 1 + .gitignore | 23 + BUILD.dhall | 115 +++++ dhall-to-buck | 58 +++ dhall/Build.dhall | 125 ++++++ dhall/CFlags.dhall | 236 ++++++++++ dhall/DischargeProof.dhall | 163 +++++++ dhall/LDFlags.dhall | 111 +++++ dhall/Resource.dhall | 73 ++++ dhall/Toolchain.dhall | 219 ++++++++++ dhall/Triple.dhall | 429 ++++++++++++++++++ dhall/examples/hello.dhall | 17 + dhall/examples/llama.dhall | 55 +++ dhall/examples/src/main.cpp | 14 + dhall/examples/with-flake.dhall | 22 + dhall/package.dhall | 29 ++ dhall/prelude/Cxx.dhall | 49 +++ dhall/prelude/Haskell.dhall | 83 ++++ dhall/prelude/Lean.dhall | 37 ++ dhall/prelude/Nv.dhall | 43 ++ dhall/prelude/Prelude.dhall | 31 ++ dhall/prelude/PureScript.dhall | 68 +++ dhall/prelude/Rust.dhall | 53 +++ dhall/prelude/Types.dhall | 15 + dhall/prelude/extract-deps.dhall | 14 + dhall/prelude/package.dhall | 59 +++ dhall/prelude/to-starlark.dhall | 300 +++++++++++++ dhall/to-buck2.dhall | 145 ++++++ flake.lock | 650 ++++++++++++++++++++++++++- flake.nix | 213 +++++---- toolchains/cxx.bzl | 230 ++++++++++ toolchains/execution.bzl | 85 ++++ toolchains/haskell.bzl | 727 +++++++++++++++++++++++++++++++ toolchains/scripts/ghc-pkg-id | 59 +++ 35 files changed, 4472 insertions(+), 107 deletions(-) create mode 100644 .buckconfig create mode 120000 .clang-format create mode 100644 BUILD.dhall create mode 100755 dhall-to-buck create mode 100644 dhall/Build.dhall create mode 100644 dhall/CFlags.dhall create mode 100644 dhall/DischargeProof.dhall create mode 100644 dhall/LDFlags.dhall create mode 100644 dhall/Resource.dhall create mode 100644 dhall/Toolchain.dhall create mode 100644 dhall/Triple.dhall create mode 100644 dhall/examples/hello.dhall create mode 100644 dhall/examples/llama.dhall create mode 100644 dhall/examples/src/main.cpp create mode 100644 dhall/examples/with-flake.dhall create mode 100644 dhall/package.dhall create mode 100644 dhall/prelude/Cxx.dhall create mode 100644 dhall/prelude/Haskell.dhall create mode 100644 dhall/prelude/Lean.dhall create mode 100644 dhall/prelude/Nv.dhall create mode 100644 dhall/prelude/Prelude.dhall create mode 100644 dhall/prelude/PureScript.dhall create mode 100644 dhall/prelude/Rust.dhall create mode 100644 dhall/prelude/Types.dhall create mode 100644 dhall/prelude/extract-deps.dhall create mode 100644 dhall/prelude/package.dhall create mode 100644 dhall/prelude/to-starlark.dhall create mode 100644 dhall/to-buck2.dhall create mode 100644 toolchains/cxx.bzl create mode 100644 toolchains/execution.bzl create mode 100644 toolchains/haskell.bzl create mode 100755 toolchains/scripts/ghc-pkg-id diff --git a/.buckconfig b/.buckconfig new file mode 100644 index 0000000..44ce816 --- /dev/null +++ b/.buckconfig @@ -0,0 +1,28 @@ +# jaylene-slide Buck2 configuration +# +# Uses sensenet's Buck2 infrastructure via flake module. +# Run `nix develop .#buck2-default` to enter the build environment. + +[cells] +root = . +prelude = nix/build/prelude +toolchains = toolchains +none = none + +[cell_aliases] +config = prelude +ovr_config = prelude +fbcode = none +fbsource = none +fbcode_macros = none +buck = none + +[parser] +target_platform_detector_spec = target:root//...->prelude//platforms:default + +[buck2] +materializations = deferred +digest_algorithms = BLAKE3 + +[build] +execution_platforms = toolchains//:default diff --git a/.clang-format b/.clang-format new file mode 120000 index 0000000..f84cb4c --- /dev/null +++ b/.clang-format @@ -0,0 +1 @@ +/nix/store/a8npkxz9kfij786yqaay5fbib4gdsbv1-source/nix/configs/.clang-format \ No newline at end of file diff --git a/.gitignore b/.gitignore index c33954f..f540a0b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,24 @@ dist-newstyle/ +result +result-* +.direnv/ +buck-out/ +.buckconfig.local +nix/build/ + +# Generated BUCK file (from BUILD.dhall) +BUCK + +# GHC build artifacts +*.hi +*.o +*.dyn_hi +*.dyn_o + +# LSP generated files +/compile_commands.json +/.clangd +/.clang-tidy +/.rustfmt.toml +/.stylua.toml +/hie.yaml diff --git a/BUILD.dhall b/BUILD.dhall new file mode 100644 index 0000000..a2e8a07 --- /dev/null +++ b/BUILD.dhall @@ -0,0 +1,115 @@ +--| slide - Console cowboy for the sprawl +--| +--| Ingress adapter that jacks into OpenAI-compatible inference endpoints, +--| parses their SSE/JSON garbage, and emits clean SIGIL binary frames over ZMQ. +--| +--| Build: ./dhall-to-buck BUILD.dhall > BUCK + +let A = ./dhall/prelude/package.dhall +let S = ./dhall/prelude/to-starlark.dhall + +-- Library sources (all modules under src/Slide/) +let librarySrcs = + [ "src/Slide/Chunk.hs" + , "src/Slide/HotTable.hs" + , "src/Slide/Jack.hs" + , "src/Slide/Model.hs" + , "src/Slide/Parse.hs" + , "src/Slide/Provider.hs" + , "src/Slide/Provider/Baseten.hs" + , "src/Slide/Tokenizer.hs" + , "src/Slide/Tokenizer/FFI.hs" + , "src/Slide/Wire/Decode.hs" + , "src/Slide/Wire/Encode.hs" + , "src/Slide/Wire/Frame.hs" + , "src/Slide/Wire/Types.hs" + , "src/Slide/Wire/Varint.hs" + ] + +-- Executable sources +let appSrcs = [ "app/Main.hs" ] + +-- C++ FFI sources for tokenizers binding +let cxxSrcs = [ "cbits/tokenizers_c.cpp" ] + +-- All Haskell packages needed +let packages = + [ "base" + , "aeson" + , "async" + , "blake3" + , "bytestring" + , "case-insensitive" + , "containers" + , "crypton" + , "data-default-class" + , "dhall" + , "http2" + , "http-semantics" + , "http-types" + , "katip" + , "megaparsec" + , "memory" + , "network" + , "optparse-applicative" + , "prometheus-client" + , "prometheus-metrics-ghc" + , "random" + , "text" + , "time-manager" + , "tls" + , "vector" + , "wai" + , "warp" + , "zeromq4-haskell" + ] + +-- Language extensions +let extensions = + [ "BangPatterns" + , "CApiFFI" + , "DerivingStrategies" + , "ForeignFunctionInterface" + , "LambdaCase" + , "NumericUnderscores" + , "OverloadedStrings" + , "PatternSynonyms" + , "StrictData" + ] + +-- GHC options +let ghcOptions = + [ "-O2" + , "-threaded" + , "-rtsopts" + , "-with-rtsopts=-N -A64m -I0" + , "-isrc" + ] + +-- External libraries (from Nix via .buckconfig.local) +let extraLibs = + [ "tokenizers_cpp" + , "tokenizers_c" + , "sentencepiece" + ] + +-- slide executable with FFI +let slide = + (A.haskellFFIBinary "slide" (librarySrcs # appSrcs) cxxSrcs) + with packages = packages + with language_extensions = extensions + with ghc_options = ghcOptions + with extra_libs = extraLibs + with include_dirs = [ "cbits" ] + +in { rules = [ S.haskellFFIBinary slide ] + , header = '' + # Generated from BUILD.dhall + # + # Library paths for tokenizers-cpp come from .buckconfig.local [slide] section + # which is auto-generated by `nix develop` + # The haskell_ffi_binary rule reads these paths via read_root_config() + + load("@toolchains//:haskell.bzl", "haskell_ffi_binary") + '' + } diff --git a/dhall-to-buck b/dhall-to-buck new file mode 100755 index 0000000..4b89a56 --- /dev/null +++ b/dhall-to-buck @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# dhall-to-buck - Transpile BUILD.dhall to BUCK +set -euo pipefail + +[[ $# -lt 1 ]] && { echo "Usage: $0 " >&2; exit 1; } + +INPUT="$1" + +# Check if this is the new format (exports { rules, header }) or legacy (List Binary) +STRUCTURE=$(dhall type <<< "./$INPUT" 2>/dev/null || echo "unknown") + +if [[ "$STRUCTURE" == *"rules"* && "$STRUCTURE" == *"header"* ]]; then + # New format: { rules : List Text, header : Text } + HEADER=$(dhall text <<< "(./$INPUT).header") + echo "# Generated from $INPUT" + echo "$HEADER" + dhall text <<< "let P = ./dhall/prelude/Prelude.dhall in P.Text.concatSep \"\\n\" (./$INPUT).rules" + exit 0 +fi + +# Legacy format: List C.Binary (for backward compat with existing BUILD.dhall files) +DEPS=$(dhall text <<< "(./dhall/prelude/extract-deps.dhall) ./$INPUT" 2>/dev/null || echo "") + +if [[ -z "$DEPS" ]]; then + echo "# Generated from $INPUT" + echo 'load("@toolchains//:cxx.bzl", "cxx_binary")' + echo "" + dhall text <<< " +let S = ./dhall/prelude/to-starlark.dhall +let C = ./dhall/prelude/Cxx.dhall +let t = List/head C.Binary ./$INPUT +in merge { None = \"\", Some = \\(b : C.Binary) -> S.binary b { compiler = [] : List Text, linker = [] : List Text } } t" + exit 0 +fi + +FLAGS=$(buck2 run //src/nix-analyze:nix-analyze -- resolve $DEPS 2>/dev/null | grep -v "^\[") + +CF="" LF="" next=false +while IFS= read -r f; do + [[ -z "$f" ]] && continue + if $next; then CF+="\"$f\", "; next=false + elif [[ "$f" == "-isystem" ]]; then CF+="\"$f\", "; next=true + elif [[ "$f" == -I* ]]; then CF+="\"$f\", " + elif [[ "$f" == -L* || "$f" == -Wl,* || "$f" == -l* ]]; then LF+="\"$f\", " + fi +done <<< "$FLAGS" + +echo "# Generated from $INPUT" +echo "# Deps: ${DEPS//$'\n'/, }" +echo "" +echo 'load("@toolchains//:cxx.bzl", "cxx_binary")' +echo "" +dhall text <<< " +let S = ./dhall/prelude/to-starlark.dhall +let C = ./dhall/prelude/Cxx.dhall +let t = List/head C.Binary ./$INPUT +let f = { compiler = [${CF%,*}], linker = [${LF%,*}] } +in merge { None = \"\", Some = \\(b : C.Binary) -> S.binary b f } t" diff --git a/dhall/Build.dhall b/dhall/Build.dhall new file mode 100644 index 0000000..aba1432 --- /dev/null +++ b/dhall/Build.dhall @@ -0,0 +1,125 @@ +{- Build.dhall + + Build target definition with coeffects. +-} + +let Resource = ./Resource.dhall +let Toolchain = ./Toolchain.dhall +let Triple = ./Triple.dhall + +-- Dependency reference +let Dep + : Type + = < -- Local target (same repo) + Local : Text -- ":foo" or "//pkg:foo" + -- External (content-addressed) + | External : { hash : Text, name : Text } + -- Pkg-config + | PkgConfig : Text -- "openssl", "zlib" + -- Nix flake reference (resolved at graph construction) + | Flake : Text -- "nixpkgs#openssl", ".#libfoo", "github:owner/repo#pkg" + > + +-- Source specification +let Src + : Type + = < -- Local files + Files : List Text + -- Content-addressed fetch + | Fetch : { url : Text, hash : Text } + -- Git + | Git : { url : Text, rev : Text, hash : Text } + > + +-- Build target +let Target + : Type + = { name : Text + , srcs : Src + , deps : List Dep + , toolchain : Toolchain.Toolchain + , requires : Resource.Resources -- coeffects (what this build needs) + } + +-- Convenience constructor +let target + : Target → Target + = λ(t : Target) → t + +-- C/C++ library +let cxx-library + : { name : Text + , srcs : List Text + , deps : List Dep + , toolchain : Toolchain.Toolchain + , requires : Resource.Resources + } → Target + = λ(cfg : { name : Text + , srcs : List Text + , deps : List Dep + , toolchain : Toolchain.Toolchain + , requires : Resource.Resources + }) → + { name = cfg.name + , srcs = Src.Files cfg.srcs + , deps = cfg.deps + , toolchain = cfg.toolchain + , requires = cfg.requires + } + +-- C/C++ binary +let cxx-binary + : { name : Text + , srcs : List Text + , deps : List Dep + , toolchain : Toolchain.Toolchain + , requires : Resource.Resources + } → Target + = λ(cfg : { name : Text + , srcs : List Text + , deps : List Dep + , toolchain : Toolchain.Toolchain + , requires : Resource.Resources + }) → + { name = cfg.name + , srcs = Src.Files cfg.srcs + , deps = cfg.deps + , toolchain = cfg.toolchain + , requires = cfg.requires + } + +-- Fetch from URL (content-addressed) +let fetch + : { url : Text, hash : Text } → Src + = Src.Fetch + +-- Git source +let git + : { url : Text, rev : Text, hash : Text } → Src + = Src.Git + +-- Dependency constructors +let dep = + { local = Dep.Local + , pkgconfig = Dep.PkgConfig + , external = λ(hash : Text) → λ(name : Text) → + Dep.External { hash, name } + , flake = Dep.Flake + , nixpkgs = λ(pkg : Text) → Dep.Flake "nixpkgs#${pkg}" + , nixpkgsMusl = λ(pkg : Text) → Dep.Flake "nixpkgs#pkgsMusl.${pkg}" + , nixpkgsStatic = λ(pkg : Text) → Dep.Flake "nixpkgs#pkgsStatic.${pkg}" + } + +in { Dep + , Src + , Target + , target + , cxx-library + , cxx-binary + , fetch + , git + , dep + , Resource = Resource + , Toolchain = Toolchain + , Triple = Triple + } diff --git a/dhall/CFlags.dhall b/dhall/CFlags.dhall new file mode 100644 index 0000000..4f16d2c --- /dev/null +++ b/dhall/CFlags.dhall @@ -0,0 +1,236 @@ +{- CFlags.dhall + + Typed C/C++ compiler flags. + + No more string soup. The compiler catches typos. +-} + +let CStd + : Type + = < c89 | c99 | c11 | c17 | c23 > + +let CxxStd + : Type + = < cxx11 | cxx14 | cxx17 | cxx20 | cxx23 > + +let OptLevel + : Type + = < O0 | O1 | O2 | O3 | Os | Oz | Og > + +let LTOMode + : Type + = < off | thin | full > + +let Sanitizer + : Type + = < address | memory | thread | undefined | leak > + +let DebugLevel + : Type + = < g0 | g1 | g2 | g3 > + +let CFlag + : Type + = < -- Optimization + Opt : OptLevel + | LTO : LTOMode + + -- Standards + | StdC : CStd + | StdCxx : CxxStd + + -- Warnings + | Wall + | Wextra + | Werror + | Wpedantic + | Wno : Text -- -Wno- + + -- Preprocessor + | Define : { name : Text, value : Optional Text } + | Undef : Text + | Include : Text -- -I + | System : Text -- -isystem + + -- Code generation + | PIC + | PIE + | Static + | Shared + + -- Architecture + | March : Text -- -march= + | Mtune : Text -- -mtune= + | Native -- -march=native + + -- Debug + | Debug : DebugLevel + + -- Sanitizers + | Sanitize : Sanitizer + + -- Misc + | FunctionSections -- -ffunction-sections + | DataSections -- -fdata-sections + | NoExceptions -- -fno-exceptions + | NoRTTI -- -fno-rtti + | Pthread -- -pthread + + -- Escape hatch (logged, warned) + | Raw : Text + > + +-- Render a flag to string +let render + : CFlag → Text + = λ(f : CFlag) → + merge + { Opt = λ(o : OptLevel) → + merge + { O0 = "-O0", O1 = "-O1", O2 = "-O2", O3 = "-O3" + , Os = "-Os", Oz = "-Oz", Og = "-Og" + } + o + , LTO = λ(l : LTOMode) → + merge + { off = "", thin = "-flto=thin", full = "-flto" + } + l + , StdC = λ(s : CStd) → + merge + { c89 = "-std=c89", c99 = "-std=c99", c11 = "-std=c11" + , c17 = "-std=c17", c23 = "-std=c23" + } + s + , StdCxx = λ(s : CxxStd) → + merge + { cxx11 = "-std=c++11", cxx14 = "-std=c++14" + , cxx17 = "-std=c++17", cxx20 = "-std=c++20" + , cxx23 = "-std=c++23" + } + s + , Wall = "-Wall" + , Wextra = "-Wextra" + , Werror = "-Werror" + , Wpedantic = "-Wpedantic" + , Wno = λ(w : Text) → "-Wno-${w}" + , Define = λ(d : { name : Text, value : Optional Text }) → + merge + { None = "-D${d.name}" + , Some = λ(v : Text) → "-D${d.name}=${v}" + } + d.value + , Undef = λ(u : Text) → "-U${u}" + , Include = λ(p : Text) → "-I${p}" + , System = λ(p : Text) → "-isystem ${p}" + , PIC = "-fPIC" + , PIE = "-fPIE" + , Static = "-static" + , Shared = "-shared" + , March = λ(a : Text) → "-march=${a}" + , Mtune = λ(c : Text) → "-mtune=${c}" + , Native = "-march=native" + , Debug = λ(d : DebugLevel) → + merge + { g0 = "-g0", g1 = "-g1", g2 = "-g2", g3 = "-g3" + } + d + , Sanitize = λ(s : Sanitizer) → + merge + { address = "-fsanitize=address" + , memory = "-fsanitize=memory" + , thread = "-fsanitize=thread" + , undefined = "-fsanitize=undefined" + , leak = "-fsanitize=leak" + } + s + , FunctionSections = "-ffunction-sections" + , DataSections = "-fdata-sections" + , NoExceptions = "-fno-exceptions" + , NoRTTI = "-fno-rtti" + , Pthread = "-pthread" + , Raw = λ(r : Text) → r + } + f + +-- Convenience constructors +let opt = + { O0 = CFlag.Opt OptLevel.O0 + , O1 = CFlag.Opt OptLevel.O1 + , O2 = CFlag.Opt OptLevel.O2 + , O3 = CFlag.Opt OptLevel.O3 + , Os = CFlag.Opt OptLevel.Os + , Oz = CFlag.Opt OptLevel.Oz + } + +let std = + { c11 = CFlag.StdC CStd.c11 + , c17 = CFlag.StdC CStd.c17 + , c23 = CFlag.StdC CStd.c23 + , cxx17 = CFlag.StdCxx CxxStd.cxx17 + , cxx20 = CFlag.StdCxx CxxStd.cxx20 + , cxx23 = CFlag.StdCxx CxxStd.cxx23 + } + +let warn = + { all = CFlag.Wall + , extra = CFlag.Wextra + , error = CFlag.Werror + , pedantic = CFlag.Wpedantic + } + +let lto = + { off = CFlag.LTO LTOMode.off + , thin = CFlag.LTO LTOMode.thin + , full = CFlag.LTO LTOMode.full + } + +let debug = + { none = CFlag.Debug DebugLevel.g0 + , minimal = CFlag.Debug DebugLevel.g1 + , default = CFlag.Debug DebugLevel.g2 + , full = CFlag.Debug DebugLevel.g3 + } + +let sanitize = + { address = CFlag.Sanitize Sanitizer.address + , memory = CFlag.Sanitize Sanitizer.memory + , thread = CFlag.Sanitize Sanitizer.thread + , undefined = CFlag.Sanitize Sanitizer.undefined + } + +let define + : Text → Optional Text → CFlag + = λ(name : Text) → λ(value : Optional Text) → CFlag.Define { name, value } + +let include + : Text → CFlag + = CFlag.Include + +in { CStd + , CxxStd + , OptLevel + , LTOMode + , Sanitizer + , DebugLevel + , CFlag + , render + -- Convenience + , opt + , std + , warn + , lto + , debug + , sanitize + , define + , include + , native = CFlag.Native + , pic = CFlag.PIC + , pie = CFlag.PIE + , static = CFlag.Static + , pthread = CFlag.Pthread + , no-exceptions = CFlag.NoExceptions + , no-rtti = CFlag.NoRTTI + , function-sections = CFlag.FunctionSections + , data-sections = CFlag.DataSections + } diff --git a/dhall/DischargeProof.dhall b/dhall/DischargeProof.dhall new file mode 100644 index 0000000..5b9dc5a --- /dev/null +++ b/dhall/DischargeProof.dhall @@ -0,0 +1,163 @@ +{- DischargeProof.dhall + + Evidence that coeffects were satisfied during a build. + + This type mirrors: + - Haskell: Armitage.Builder.DischargeProof + - Lean: Continuity.DischargeProof (when formalized) + + The proof is: + 1. Generated by armitage after successful build + 2. Signed (optionally) with ed25519 + 3. Verifiable by the Lean formalization + + Soundness: A valid DischargeProof for coeffects C implies that + the build environment actually provided C during execution. +-} + +let Resource = ./Resource.dhall + +-------------------------------------------------------------------------------- +-- Network Access Witness +-- +-- Recorded by the TLS MITM proxy. Each access includes the content hash, +-- so replays can verify the same content was received. +-------------------------------------------------------------------------------- + +let NetworkAccess + : Type + = { url : Text + , method : Text -- GET, POST, etc. + , contentHash : Text -- SHA256 of response body + , timestamp : Text -- ISO 8601 + } + +-------------------------------------------------------------------------------- +-- Filesystem Access Witness +-- +-- Recorded for non-pure builds that access paths outside the sandbox. +-------------------------------------------------------------------------------- + +let FilesystemAccess + : Type + = { path : Text + , mode : < Read | Write | Execute > + , contentHash : Optional Text -- SHA256 if readable file + , timestamp : Text + } + +-------------------------------------------------------------------------------- +-- Auth Token Usage +-- +-- Recorded when a build uses an authentication token. +-- The token value itself is NOT recorded (security), just the provider. +-------------------------------------------------------------------------------- + +let AuthUsage + : Type + = { provider : Text -- e.g., "github", "docker" + , scope : Optional Text -- what scope was used + , timestamp : Text + } + +-------------------------------------------------------------------------------- +-- Discharge Proof +-- +-- Complete evidence of coeffect satisfaction. +-------------------------------------------------------------------------------- + +let DischargeProof + : Type + = { -- What coeffects were required + coeffects : Resource.Resources + + -- Evidence of network access (from witness proxy) + , networkAccess : List NetworkAccess + + -- Evidence of filesystem access (from sandbox hooks) + , filesystemAccess : List FilesystemAccess + + -- Evidence of auth token usage + , authUsage : List AuthUsage + + -- Build metadata + , buildId : Text -- unique build identifier + , derivationHash : Text -- content hash of input derivation + , outputHashes : List { name : Text, hash : Text } + , startTime : Text -- ISO 8601 + , endTime : Text -- ISO 8601 + + -- Optional cryptographic signature + -- Signs: sha256(derivationHash ++ outputHashes ++ evidence) + , signature : Optional { publicKey : Text, sig : Text } + } + +-------------------------------------------------------------------------------- +-- Constructors +-------------------------------------------------------------------------------- + +-- Empty proof for pure builds (no external requirements) +let pureProof + : { buildId : Text + , derivationHash : Text + , outputHashes : List { name : Text, hash : Text } + , startTime : Text + , endTime : Text + } → DischargeProof + = λ(meta : { buildId : Text + , derivationHash : Text + , outputHashes : List { name : Text, hash : Text } + , startTime : Text + , endTime : Text + }) → + { coeffects = Resource.pure + , networkAccess = [] : List NetworkAccess + , filesystemAccess = [] : List FilesystemAccess + , authUsage = [] : List AuthUsage + , buildId = meta.buildId + , derivationHash = meta.derivationHash + , outputHashes = meta.outputHashes + , startTime = meta.startTime + , endTime = meta.endTime + , signature = None { publicKey : Text, sig : Text } + } + +-------------------------------------------------------------------------------- +-- Verification predicates +-- +-- These would ideally be in Lean, but we express the structure here +-- so Dhall can validate the shape at parse time. +-------------------------------------------------------------------------------- + +-- Check if proof is for pure build +let isPure : DischargeProof → Bool = λ(p : DischargeProof) → + Resource.isPure p.coeffects + +-- Check if proof has network evidence +let hasNetworkEvidence : DischargeProof → Bool = λ(p : DischargeProof) → + merge + { None = False + , Some = λ(_ : NetworkAccess) → True + } + (List/head NetworkAccess p.networkAccess) + +-- Check if proof is signed +let isSigned : DischargeProof → Bool = λ(p : DischargeProof) → + merge + { None = False + , Some = λ(_ : { publicKey : Text, sig : Text }) → True + } + p.signature + +in { -- Types + NetworkAccess + , FilesystemAccess + , AuthUsage + , DischargeProof + -- Constructors + , pureProof + -- Predicates + , isPure + , hasNetworkEvidence + , isSigned + } diff --git a/dhall/LDFlags.dhall b/dhall/LDFlags.dhall new file mode 100644 index 0000000..62b3fa7 --- /dev/null +++ b/dhall/LDFlags.dhall @@ -0,0 +1,111 @@ +{- LDFlags.dhall + + Typed linker flags. +-} + +let LDFlag + : Type + = < -- Linking mode + Static + | Shared + | Pie + | NoPie + | Relocatable -- -r + + -- Libraries + | Lib : Text -- -l + | LibPath : Text -- -L + + -- Runtime + | Rpath : Text + | RpathLink : Text + + -- Symbols + | Strip -- -s + | StripDebug -- -S + | ExportDynamic -- --export-dynamic + | AsNeeded -- --as-needed + | NoAsNeeded -- --no-as-needed + + -- Garbage collection + | GcSections -- --gc-sections + | NoGcSections + | PrintGcSections -- --print-gc-sections + + -- Output control + | Soname : Text -- -soname= + | VersionScript : Text + + -- LTO + | LTOJobs : Natural -- -flto-jobs=N + + -- Escape hatch + | Raw : Text + > + +-- Render to string +let render + : LDFlag → Text + = λ(f : LDFlag) → + merge + { Static = "-static" + , Shared = "-shared" + , Pie = "-pie" + , NoPie = "-no-pie" + , Relocatable = "-r" + , Lib = λ(l : Text) → "-l${l}" + , LibPath = λ(p : Text) → "-L${p}" + , Rpath = λ(p : Text) → "-Wl,-rpath,${p}" + , RpathLink = λ(p : Text) → "-Wl,-rpath-link,${p}" + , Strip = "-s" + , StripDebug = "-S" + , ExportDynamic = "-Wl,--export-dynamic" + , AsNeeded = "-Wl,--as-needed" + , NoAsNeeded = "-Wl,--no-as-needed" + , GcSections = "-Wl,--gc-sections" + , NoGcSections = "-Wl,--no-gc-sections" + , PrintGcSections = "-Wl,--print-gc-sections" + , Soname = λ(n : Text) → "-Wl,-soname,${n}" + , VersionScript = λ(p : Text) → "-Wl,--version-script,${p}" + , LTOJobs = λ(n : Natural) → "-flto-jobs=${Natural/show n}" + , Raw = λ(r : Text) → r + } + f + +-- Convenience +let static = LDFlag.Static +let shared = LDFlag.Shared +let pie = LDFlag.Pie +let strip = LDFlag.Strip +let strip-debug = LDFlag.StripDebug +let gc-sections = LDFlag.GcSections +let as-needed = LDFlag.AsNeeded +let export-dynamic = LDFlag.ExportDynamic + +let lib + : Text → LDFlag + = LDFlag.Lib + +let lib-path + : Text → LDFlag + = LDFlag.LibPath + +let rpath + : Text → LDFlag + = LDFlag.Rpath + +in { LDFlag + , render + -- Convenience + , static + , shared + , pie + , strip + , strip-debug + , gc-sections + , as-needed + , export-dynamic + , lib + , lib-path + , rpath + } diff --git a/dhall/Resource.dhall b/dhall/Resource.dhall new file mode 100644 index 0000000..8278445 --- /dev/null +++ b/dhall/Resource.dhall @@ -0,0 +1,73 @@ +{- Resource.dhall + + The coeffect algebra. Resources are what builds *require* from the environment. + + This is not effects (what builds do). This is coeffects (what builds need). + + Note: Dhall doesn't support recursive types directly. We use a flat list + representation for combined resources instead of a recursive tree. +-} + +let Resource + : Type + = < Pure -- needs nothing external + | Network -- needs network access + | Auth : Text -- needs credential (provider name) + | Sandbox : Text -- needs isolation (sandbox type) + | Filesystem : Text -- needs filesystem path + > + +-- A set of resources (representing combination via ⊗) +let Resources : Type = List Resource + +let pure : Resources = [] : List Resource + +let network : Resources = [ Resource.Network ] + +let auth : Text → Resources = λ(provider : Text) → [ Resource.Auth provider ] + +let sandbox : Text → Resources = λ(name : Text) → [ Resource.Sandbox name ] + +let filesystem : Text → Resources = λ(path : Text) → [ Resource.Filesystem path ] + +-- Combine two resource sets (the ⊗ operator) +let combine + : Resources → Resources → Resources + = λ(r : Resources) → + λ(s : Resources) → + r # s -- list concatenation + +-- ASCII alias for the tensor product +let tensor = combine + +-- Check if resources are pure (empty) +let isPure : Resources → Bool = λ(r : Resources) → + merge + { None = True + , Some = λ(_ : Resource) → False + } + (List/head Resource r) + +-- Render a single resource to text +let renderOne : Resource → Text = λ(r : Resource) → + merge + { Pure = "pure" + , Network = "network" + , Auth = λ(x : Text) → "auth:${x}" + , Sandbox = λ(x : Text) → "sandbox:${x}" + , Filesystem = λ(x : Text) → "fs:${x}" + } + r + +in { Resource + , Resources + , pure + , network + , auth + , sandbox + , filesystem + , combine + , tensor + , isPure + , renderOne + } diff --git a/dhall/Toolchain.dhall b/dhall/Toolchain.dhall new file mode 100644 index 0000000..771949a --- /dev/null +++ b/dhall/Toolchain.dhall @@ -0,0 +1,219 @@ +{- Toolchain.dhall + + A toolchain is: compiler + host + target + flags + + Cross-compilation is just: host ≠ target +-} + +let Triple = ./Triple.dhall +let CFlags = ./CFlags.dhall +let LDFlags = ./LDFlags.dhall + +let Compiler + : Type + = < Clang : { version : Text } + | NVClang : { version : Text } -- LLVM git with CUDA C++23 patches + | GCC : { version : Text } + | NVCC : { version : Text } -- NVIDIA CUDA compiler + | Rustc : { version : Text } + | GHC : { version : Text } + | Lean : { version : Text } + > + +let Toolchain + : Type + = { compiler : Compiler + , host : Triple.Triple + , target : Triple.Triple + , cflags : List CFlags.CFlag + , cxxflags : List CFlags.CFlag + , ldflags : List LDFlags.LDFlag + , sysroot : Optional Text + } + +-- Note: Cross-compilation detection (host ≠ target) must happen in Nix, +-- as Dhall only supports == on Bool, not on Text or union types. +-- The build system compares Triple.render host vs Triple.render target. + +-- Convenience constructors +let clang + : { version : Text + , host : Triple.Triple + , target : Triple.Triple + , cflags : List CFlags.CFlag + , ldflags : List LDFlags.LDFlag + } → Toolchain + = λ(cfg : { version : Text + , host : Triple.Triple + , target : Triple.Triple + , cflags : List CFlags.CFlag + , ldflags : List LDFlags.LDFlag + }) → + { compiler = Compiler.Clang { version = cfg.version } + , host = cfg.host + , target = cfg.target + , cflags = cfg.cflags + , cxxflags = cfg.cflags -- default: same as cflags + , ldflags = cfg.ldflags + , sysroot = None Text + } + +let gcc + : { version : Text + , host : Triple.Triple + , target : Triple.Triple + , cflags : List CFlags.CFlag + , ldflags : List LDFlags.LDFlag + } → Toolchain + = λ(cfg : { version : Text + , host : Triple.Triple + , target : Triple.Triple + , cflags : List CFlags.CFlag + , ldflags : List LDFlags.LDFlag + }) → + { compiler = Compiler.GCC { version = cfg.version } + , host = cfg.host + , target = cfg.target + , cflags = cfg.cflags + , cxxflags = cfg.cflags + , ldflags = cfg.ldflags + , sysroot = None Text + } + +-- NV-Clang constructor (CUDA C++23) +let nv-clang + : { version : Text + , host : Triple.Triple + , target : Triple.Triple + , cflags : List CFlags.CFlag + , ldflags : List LDFlags.LDFlag + } → Toolchain + = λ(cfg : { version : Text + , host : Triple.Triple + , target : Triple.Triple + , cflags : List CFlags.CFlag + , ldflags : List LDFlags.LDFlag + }) → + { compiler = Compiler.NVClang { version = cfg.version } + , host = cfg.host + , target = cfg.target + , cflags = cfg.cflags + , cxxflags = cfg.cflags + , ldflags = cfg.ldflags + , sysroot = None Text + } + +-------------------------------------------------------------------------------- +-- Preset toolchains +-------------------------------------------------------------------------------- + +let presets = + let native = Triple.x86_64-linux-gnu + + in { -- Native dynamic (glibc) + clang-18-glibc-dynamic = clang + { version = "18" + , host = native + , target = native + , cflags = [ CFlags.opt.O2, CFlags.warn.all, CFlags.warn.error ] + , ldflags = [] : List LDFlags.LDFlag + } + + -- Native static (musl) + , clang-18-musl-static = clang + { version = "18" + , host = Triple.x86_64-linux-musl + , target = Triple.x86_64-linux-musl + , cflags = + [ CFlags.opt.O2 + , CFlags.warn.all + , CFlags.warn.error + , CFlags.static + ] + , ldflags = [ LDFlags.static, LDFlags.gc-sections, LDFlags.strip ] + } + + -- Cross to aarch64 (glibc) + , clang-18-aarch64-cross = clang + { version = "18" + , host = native + , target = Triple.aarch64-linux-gnu + , cflags = [ CFlags.opt.O2, CFlags.warn.all ] + , ldflags = [] : List LDFlags.LDFlag + } + + -- WASM + , clang-18-wasm = clang + { version = "18" + , host = native + , target = Triple.wasm32-wasi + , cflags = [ CFlags.opt.Oz ] + , ldflags = [] : List LDFlags.LDFlag + } + + ------------------------------------------------------------------------ + -- CUDA toolchains + ------------------------------------------------------------------------ + + -- H100 (Hopper) with x86_64 host + , nv-clang-h100 = nv-clang + { version = "git" + , host = native + , target = Triple.x86_64-h100 + , cflags = + [ CFlags.opt.O3 + , CFlags.std.cxx23 + , CFlags.lto.thin + ] + , ldflags = [ LDFlags.gc-sections ] + } + + -- Grace Hopper (GH200) + , nv-clang-grace-hopper = nv-clang + { version = "git" + , host = Triple.grace-hopper + , target = Triple.grace-hopper + , cflags = + [ CFlags.opt.O3 + , CFlags.std.cxx23 + , CFlags.lto.thin + ] + , ldflags = [ LDFlags.gc-sections ] + } + + -- Jetson Orin (embedded) + , nv-clang-orin = nv-clang + { version = "git" + , host = native + , target = Triple.jetson-orin + , cflags = + [ CFlags.opt.O2 + , CFlags.std.cxx20 -- embedded may lag on C++23 + ] + , ldflags = [] : List LDFlags.LDFlag + } + + -- Blackwell (next-gen) + , nv-clang-blackwell = nv-clang + { version = "git" + , host = native + , target = Triple.dgx-blackwell + , cflags = + [ CFlags.opt.O3 + , CFlags.std.cxx23 + , CFlags.lto.full + ] + , ldflags = [ LDFlags.gc-sections ] + } + } + +in { -- Types + Compiler + , Toolchain + -- Constructors + , clang + , gcc + , nv-clang + -- Presets + , presets + } diff --git a/dhall/Triple.dhall b/dhall/Triple.dhall new file mode 100644 index 0000000..d7d6555 --- /dev/null +++ b/dhall/Triple.dhall @@ -0,0 +1,429 @@ +{- Triple.dhall + + Target triples: arch-vendor-os-abi + cpu/gpu microarchitecture + + Standard nomenclature from LLVM/GCC/Rust. + Microarchitecture types from nix/prelude/types/Target.dhall. +-} + +let Arch + : Type + = < x86_64 + | aarch64 + | riscv64 + | wasm32 + | armv7 + > + +let Vendor + : Type + = < unknown + | pc + | apple + | nvidia + > + +let OS + : Type + = < linux + | darwin + | windows + | wasi + | none -- bare metal + > + +let ABI + : Type + = < gnu -- glibc + | musl -- musl libc (static-friendly) + | eabi -- embedded + | eabihf -- embedded hard float + | msvc -- Microsoft + | none -- no libc + > + +-------------------------------------------------------------------------------- +-- CPU Microarchitecture +-- +-- These matter for -march/-mtune. Native builds should use `native`. +-- Cross builds must specify the exact target CPU. +-------------------------------------------------------------------------------- + +let Cpu + : Type + = < generic -- safe baseline + | native -- detect at compile time (-march=native) + -- x86_64 + | x86_64_v2 -- SSE4.2, POPCNT (Nehalem+) + | x86_64_v3 -- AVX2, BMI2 (Haswell+) + | x86_64_v4 -- AVX-512 (Skylake-X+) + | znver3 -- AMD Zen 3 + | znver4 -- AMD Zen 4 (AVX-512) + | znver5 -- AMD Zen 5 + | sapphirerapids -- Intel Sapphire Rapids + | alderlake -- Intel Alder Lake (hybrid) + -- aarch64 datacenter (SBSA) + | neoverse_v2 -- Grace (GH200, GB200, DGX Spark) + | neoverse_n2 -- Altra, Ampere + -- aarch64 embedded (Jetson) + | cortex_a78ae -- Orin (AGX/NX/Nano), Thor + | cortex_a78c -- Orin variants + -- aarch64 consumer + | apple_m1 + | apple_m2 + | apple_m3 + | apple_m4 + > + +-------------------------------------------------------------------------------- +-- GPU Microarchitecture (SM version) +-- +-- For CUDA compilation. Used with -arch=sm_XX. +-- Only NVIDIA GPUs have SM versions; others use `none`. +-------------------------------------------------------------------------------- + +let Gpu + : Type + = < none + -- Ampere + | sm_80 -- A100 + | sm_86 -- RTX 30xx, A series + -- Ada Lovelace + | sm_89 -- RTX 40xx, L40 + -- Hopper + | sm_90 -- H100 (PCIe) + | sm_90a -- H100 SXM (async features) + -- Orin (embedded) + | sm_87 -- Jetson Orin + -- Blackwell + | sm_100 -- B100, B200 + | sm_100a -- B200 (full features) + | sm_120 -- RTX 50xx + > + +let Triple + : Type + = { arch : Arch + , vendor : Vendor + , os : OS + , abi : ABI + , cpu : Cpu -- CPU microarchitecture + , gpu : Gpu -- GPU SM version (none for non-NVIDIA) + } + +-- Render arch to string +let renderArch : Arch → Text = λ(a : Arch) → + merge + { x86_64 = "x86_64" + , aarch64 = "aarch64" + , riscv64 = "riscv64" + , wasm32 = "wasm32" + , armv7 = "armv7" + } + a + +-- Render vendor to string +let renderVendor : Vendor → Text = λ(v : Vendor) → + merge + { unknown = "unknown" + , pc = "pc" + , apple = "apple" + , nvidia = "nvidia" + } + v + +-- Render OS to string +let renderOS : OS → Text = λ(o : OS) → + merge + { linux = "linux" + , darwin = "darwin" + , windows = "windows" + , wasi = "wasi" + , none = "none" + } + o + +-- Render ABI to string (returns Optional for none case) +let renderABI : ABI → Optional Text = λ(a : ABI) → + merge + { gnu = Some "gnu" + , musl = Some "musl" + , eabi = Some "eabi" + , eabihf = Some "eabihf" + , msvc = Some "msvc" + , none = None Text + } + a + +-- Check if ABI is none +let abiIsNone : ABI → Bool = λ(a : ABI) → + merge + { gnu = False + , musl = False + , eabi = False + , eabihf = False + , msvc = False + , none = True + } + a + +-- Render CPU to string (for -march/-mtune) +let renderCpu : Cpu → Text = λ(c : Cpu) → + merge + { generic = "generic" + , native = "native" + , x86_64_v2 = "x86-64-v2" + , x86_64_v3 = "x86-64-v3" + , x86_64_v4 = "x86-64-v4" + , znver3 = "znver3" + , znver4 = "znver4" + , znver5 = "znver5" + , sapphirerapids = "sapphirerapids" + , alderlake = "alderlake" + , neoverse_v2 = "neoverse-v2" + , neoverse_n2 = "neoverse-n2" + , cortex_a78ae = "cortex-a78ae" + , cortex_a78c = "cortex-a78c" + , apple_m1 = "apple-m1" + , apple_m2 = "apple-m2" + , apple_m3 = "apple-m3" + , apple_m4 = "apple-m4" + } + c + +-- Render GPU to CUDA arch string +let renderGpu : Gpu → Optional Text = λ(g : Gpu) → + merge + { none = None Text + , sm_80 = Some "sm_80" + , sm_86 = Some "sm_86" + , sm_87 = Some "sm_87" + , sm_89 = Some "sm_89" + , sm_90 = Some "sm_90" + , sm_90a = Some "sm_90a" + , sm_100 = Some "sm_100" + , sm_100a = Some "sm_100a" + , sm_120 = Some "sm_120" + } + g + +-- Check if GPU is none +let gpuIsNone : Gpu → Bool = λ(g : Gpu) → + merge + { none = True + , sm_80 = False + , sm_86 = False + , sm_87 = False + , sm_89 = False + , sm_90 = False + , sm_90a = False + , sm_100 = False + , sm_100a = False + , sm_120 = False + } + g + +-- Render to canonical LLVM triple string (without cpu/gpu, those go to flags) +let render + : Triple → Text + = λ(t : Triple) → + let archStr = renderArch t.arch + let vendorStr = renderVendor t.vendor + let osStr = renderOS t.os + in if abiIsNone t.abi + then "${archStr}-${vendorStr}-${osStr}" + else merge + { Some = λ(abiStr : Text) → "${archStr}-${vendorStr}-${osStr}-${abiStr}" + , None = "${archStr}-${vendorStr}-${osStr}" + } + (renderABI t.abi) + +-------------------------------------------------------------------------------- +-- Common triples +-------------------------------------------------------------------------------- + +-- x86_64 Linux (glibc) +let x86_64-linux-gnu + : Triple + = { arch = Arch.x86_64 + , vendor = Vendor.unknown + , os = OS.linux + , abi = ABI.gnu + , cpu = Cpu.generic + , gpu = Gpu.none + } + +-- x86_64 Linux (musl, static-friendly) +let x86_64-linux-musl + : Triple + = { arch = Arch.x86_64 + , vendor = Vendor.unknown + , os = OS.linux + , abi = ABI.musl + , cpu = Cpu.generic + , gpu = Gpu.none + } + +-- aarch64 Linux (glibc) +let aarch64-linux-gnu + : Triple + = { arch = Arch.aarch64 + , vendor = Vendor.unknown + , os = OS.linux + , abi = ABI.gnu + , cpu = Cpu.generic + , gpu = Gpu.none + } + +-- aarch64 Linux (musl) +let aarch64-linux-musl + : Triple + = { arch = Arch.aarch64 + , vendor = Vendor.unknown + , os = OS.linux + , abi = ABI.musl + , cpu = Cpu.generic + , gpu = Gpu.none + } + +-- macOS Apple Silicon +let aarch64-apple-darwin + : Triple + = { arch = Arch.aarch64 + , vendor = Vendor.apple + , os = OS.darwin + , abi = ABI.none + , cpu = Cpu.apple_m1 -- base Apple Silicon + , gpu = Gpu.none + } + +-- macOS Intel +let x86_64-apple-darwin + : Triple + = { arch = Arch.x86_64 + , vendor = Vendor.apple + , os = OS.darwin + , abi = ABI.none + , cpu = Cpu.generic + , gpu = Gpu.none + } + +-- WebAssembly +let wasm32-wasi + : Triple + = { arch = Arch.wasm32 + , vendor = Vendor.unknown + , os = OS.wasi + , abi = ABI.none + , cpu = Cpu.generic + , gpu = Gpu.none + } + +-- RISC-V 64-bit +let riscv64-linux-gnu + : Triple + = { arch = Arch.riscv64 + , vendor = Vendor.unknown + , os = OS.linux + , abi = ABI.gnu + , cpu = Cpu.generic + , gpu = Gpu.none + } + +-------------------------------------------------------------------------------- +-- NVIDIA targets (specific CPU+GPU combinations) +-------------------------------------------------------------------------------- + +-- Grace Hopper (GH200) - datacenter +let grace-hopper + : Triple + = { arch = Arch.aarch64 + , vendor = Vendor.nvidia + , os = OS.linux + , abi = ABI.gnu + , cpu = Cpu.neoverse_v2 + , gpu = Gpu.sm_90a -- H100 SXM + } + +-- Jetson Orin (embedded) +let jetson-orin + : Triple + = { arch = Arch.aarch64 + , vendor = Vendor.nvidia + , os = OS.linux + , abi = ABI.gnu + , cpu = Cpu.cortex_a78ae + , gpu = Gpu.sm_87 + } + +-- DGX Blackwell (next-gen datacenter) +let dgx-blackwell + : Triple + = { arch = Arch.aarch64 + , vendor = Vendor.nvidia + , os = OS.linux + , abi = ABI.gnu + , cpu = Cpu.neoverse_v2 + , gpu = Gpu.sm_100a -- B200 full features + } + +-- x86_64 with H100 (traditional datacenter) +let x86_64-h100 + : Triple + = { arch = Arch.x86_64 + , vendor = Vendor.unknown + , os = OS.linux + , abi = ABI.gnu + , cpu = Cpu.sapphirerapids + , gpu = Gpu.sm_90 + } + +-- x86_64 with RTX 4090 (consumer/prosumer) +let x86_64-rtx4090 + : Triple + = { arch = Arch.x86_64 + , vendor = Vendor.unknown + , os = OS.linux + , abi = ABI.gnu + , cpu = Cpu.znver4 -- Zen 4 is common pairing + , gpu = Gpu.sm_89 + } + +-- Note: Dhall doesn't support == on union types, so is-cross would require +-- comparing rendered strings. Cross detection happens in the build system +-- layer (Nix) not the configuration layer (Dhall). + +in { -- Types + Arch + , Vendor + , OS + , ABI + , Cpu + , Gpu + , Triple + -- Rendering + , render + , renderArch + , renderVendor + , renderOS + , renderABI + , renderCpu + , renderGpu + , abiIsNone + , gpuIsNone + -- Common triples (generic) + , x86_64-linux-gnu + , x86_64-linux-musl + , aarch64-linux-gnu + , aarch64-linux-musl + , aarch64-apple-darwin + , x86_64-apple-darwin + , wasm32-wasi + , riscv64-linux-gnu + -- NVIDIA targets + , grace-hopper + , jetson-orin + , dgx-blackwell + , x86_64-h100 + , x86_64-rtx4090 + } diff --git a/dhall/examples/hello.dhall b/dhall/examples/hello.dhall new file mode 100644 index 0000000..f8c6906 --- /dev/null +++ b/dhall/examples/hello.dhall @@ -0,0 +1,17 @@ +{- Example: hello world build target + + Demonstrates typed toolchains, flags, and coeffects. + Pure build - needs nothing external. +-} + +let Build = ../Build.dhall +let Resource = ../Resource.dhall +let Toolchain = ../Toolchain.dhall + +in Build.cxx-binary + { name = "hello" + , srcs = ["src/main.cpp"] + , deps = [] : List Build.Dep + , toolchain = Toolchain.presets.clang-18-musl-static + , requires = Resource.pure -- no external resources needed + } diff --git a/dhall/examples/llama.dhall b/dhall/examples/llama.dhall new file mode 100644 index 0000000..3a478b9 --- /dev/null +++ b/dhall/examples/llama.dhall @@ -0,0 +1,55 @@ +{- Example: llama inference with gated model and GPU + + Demonstrates coeffects: network, auth, sandbox. + Uses H100 target with explicit GPU SM version. +-} + +let Build = ../Build.dhall +let Resource = ../Resource.dhall +let Toolchain = ../Toolchain.dhall +let Triple = ../Triple.dhall +let CFlags = ../CFlags.dhall +let LDFlags = ../LDFlags.dhall + +-- Use H100 preset (already has correct cpu=sapphirerapids, gpu=sm_90) +let h100-toolchain = Toolchain.presets.nv-clang-h100 + +-- Or define custom with explicit cpu/gpu targeting: +let custom-h100-toolchain = Toolchain.nv-clang + { version = "git" + , host = Triple.x86_64-linux-gnu + , target = Triple.x86_64-h100 -- Includes gpu=sm_90 + , cflags = + [ CFlags.opt.O3 + , CFlags.std.cxx23 + , CFlags.warn.all + , CFlags.define "GGML_CUDA" (Some "1") + , CFlags.lto.thin + ] + , ldflags = [ LDFlags.gc-sections ] + } + +let llama-inference = Build.cxx-binary + { name = "llama-inference" + , srcs = + [ "src/main.cpp" + , "src/model.cpp" + , "src/inference.cpp" + ] + , deps = + [ Build.dep.local ":llama-cpp" + , Build.dep.pkgconfig "cuda" + ] + , toolchain = custom-h100-toolchain + + -- The coeffects: what this build requires + -- network: fetch model weights + -- auth "huggingface": gated model access + -- sandbox "gpu": GPU isolation + , requires = + Resource.combine + (Resource.combine Resource.network (Resource.auth "huggingface")) + (Resource.sandbox "gpu") + } + +in llama-inference diff --git a/dhall/examples/src/main.cpp b/dhall/examples/src/main.cpp new file mode 100644 index 0000000..4adc24e --- /dev/null +++ b/dhall/examples/src/main.cpp @@ -0,0 +1,14 @@ +// Test file for armitage with-flake example +// Uses fmt and nlohmann_json from nixpkgs + +#include + +#include + +int main() { + nlohmann::json j = { + {"project", "armitage"}, {"version", 1}, {"features", {"dice", "coeffects", "flakes"}}}; + + fmt::print("Build system: {}\n", j.dump(2)); + return 0; +} diff --git a/dhall/examples/with-flake.dhall b/dhall/examples/with-flake.dhall new file mode 100644 index 0000000..4b3194a --- /dev/null +++ b/dhall/examples/with-flake.dhall @@ -0,0 +1,22 @@ +{- Example: build with nixpkgs dependency + + Demonstrates flake refs in deps - DICE resolves these during analysis. +-} + +let Build = ../Build.dhall +let Resource = ../Resource.dhall +let Toolchain = ../Toolchain.dhall +let Triple = ../Triple.dhall +let CFlags = ../CFlags.dhall +let LDFlags = ../LDFlags.dhall + +in Build.cxx-binary + { name = "fmt-example" + , srcs = ["src/armitage/dhall/examples/src/main.cpp"] + , deps = + [ Build.dep.flake "nixpkgs#fmt" -- fmt library from nixpkgs + , Build.dep.nixpkgs "nlohmann_json" -- convenience: expands to nixpkgs#nlohmann_json + ] + , toolchain = Toolchain.presets.clang-18-glibc-dynamic + , requires = Resource.pure + } diff --git a/dhall/package.dhall b/dhall/package.dhall new file mode 100644 index 0000000..ce1d5b2 --- /dev/null +++ b/dhall/package.dhall @@ -0,0 +1,29 @@ +{- package.dhall + + Entry point for Armitage Dhall types. + + Import this to get all types: + let Armitage = ./package.dhall + + Then use: + Armitage.Triple.x86_64-linux-gnu + Armitage.Resource.network + Armitage.Build.cxx-binary { ... } +-} + +let Triple = ./Triple.dhall +let Resource = ./Resource.dhall +let CFlags = ./CFlags.dhall +let LDFlags = ./LDFlags.dhall +let Toolchain = ./Toolchain.dhall +let Build = ./Build.dhall +let DischargeProof = ./DischargeProof.dhall + +in { Triple + , Resource + , CFlags + , LDFlags + , Toolchain + , Build + , DischargeProof + } diff --git a/dhall/prelude/Cxx.dhall b/dhall/prelude/Cxx.dhall new file mode 100644 index 0000000..7c8355e --- /dev/null +++ b/dhall/prelude/Cxx.dhall @@ -0,0 +1,49 @@ +--| C/C++ Rules + +let T = ./Types.dhall + +let Binary = + { name : Text + , srcs : List Text + , deps : List T.Dep + , std : T.CxxStd + , cflags : List Text + , ldflags : List Text + , vis : T.Vis + } + +let binary + : Text -> List Text -> List T.Dep -> Binary + = \(name : Text) -> + \(srcs : List Text) -> + \(deps : List T.Dep) -> + { name, srcs, deps + , std = T.CxxStd.Cxx17 + , cflags = [] : List Text + , ldflags = [] : List Text + , vis = T.Vis.Public + } + +let Library = + { name : Text + , srcs : List Text + , hdrs : List Text + , deps : List T.Dep + , std : T.CxxStd + , cflags : List Text + , vis : T.Vis + } + +let library + : Text -> List Text -> List T.Dep -> Library + = \(name : Text) -> + \(srcs : List Text) -> + \(deps : List T.Dep) -> + { name, srcs, deps + , hdrs = [] : List Text + , std = T.CxxStd.Cxx17 + , cflags = [] : List Text + , vis = T.Vis.Public + } + +in { Binary, binary, Library, library } diff --git a/dhall/prelude/Haskell.dhall b/dhall/prelude/Haskell.dhall new file mode 100644 index 0000000..caea077 --- /dev/null +++ b/dhall/prelude/Haskell.dhall @@ -0,0 +1,83 @@ +--| Haskell Rules + +let T = ./Types.dhall + +let Binary = + { name : Text + , srcs : List Text + , main : Text + , packages : List Text + , language_extensions : List Text + , ghc_options : List Text + , deps : List T.Dep + , vis : T.Vis + } + +let binary + : Text -> List Text -> Binary + = \(name : Text) -> + \(srcs : List Text) -> + { name, srcs + , main = "Main" + , packages = [ "base" ] : List Text + , language_extensions = [] : List Text + , ghc_options = [ "-O2", "-Wall" ] : List Text + , deps = [] : List T.Dep + , vis = T.Vis.Public + } + +let Library = + { name : Text + , srcs : List Text + , packages : List Text + , language_extensions : List Text + , ghc_options : List Text + , deps : List T.Dep + , vis : T.Vis + } + +let library + : Text -> List Text -> Library + = \(name : Text) -> + \(srcs : List Text) -> + { name, srcs + , packages = [ "base" ] : List Text + , language_extensions = [] : List Text + , ghc_options = [ "-O2", "-Wall" ] : List Text + , deps = [] : List T.Dep + , vis = T.Vis.Public + } + +let FFIBinary = + { name : Text + , hs_srcs : List Text + , cxx_srcs : List Text + , cxx_headers : List Text + , packages : List Text + , language_extensions : List Text + , ghc_options : List Text + , extra_libs : List Text + , extra_lib_dirs : List Text + , include_dirs : List Text + , linker_flags : List Text + , vis : T.Vis + } + +let ffiBinary + : Text -> List Text -> List Text -> FFIBinary + = \(name : Text) -> + \(hs_srcs : List Text) -> + \(cxx_srcs : List Text) -> + { name, hs_srcs, cxx_srcs + , cxx_headers = [] : List Text + , packages = [ "base" ] : List Text + , language_extensions = [] : List Text + , ghc_options = [ "-O2" ] : List Text + , extra_libs = [] : List Text + , extra_lib_dirs = [] : List Text + , include_dirs = [] : List Text + , linker_flags = [] : List Text + , vis = T.Vis.Public + } + +in { Binary, binary, Library, library, FFIBinary, ffiBinary } diff --git a/dhall/prelude/Lean.dhall b/dhall/prelude/Lean.dhall new file mode 100644 index 0000000..467ca7a --- /dev/null +++ b/dhall/prelude/Lean.dhall @@ -0,0 +1,37 @@ +--| Lean 4 Rules + +let T = ./Types.dhall + +let Binary = + { name : Text + , srcs : List Text + , deps : List T.Dep + , vis : T.Vis + } + +let binary + : Text -> List Text -> Binary + = \(name : Text) -> + \(srcs : List Text) -> + { name, srcs + , deps = [] : List T.Dep + , vis = T.Vis.Public + } + +let Library = + { name : Text + , srcs : List Text + , deps : List T.Dep + , vis : T.Vis + } + +let library + : Text -> List Text -> Library + = \(name : Text) -> + \(srcs : List Text) -> + { name, srcs + , deps = [] : List T.Dep + , vis = T.Vis.Public + } + +in { Binary, binary, Library, library } diff --git a/dhall/prelude/Nv.dhall b/dhall/prelude/Nv.dhall new file mode 100644 index 0000000..efb6a01 --- /dev/null +++ b/dhall/prelude/Nv.dhall @@ -0,0 +1,43 @@ +--| NVIDIA CUDA Rules (using clang, not nvcc) + +let T = ./Types.dhall + +let Binary = + { name : Text + , srcs : List Text + , deps : List T.Dep + , archs : List Text + , vis : T.Vis + } + +let binary + : Text -> List Text -> Binary + = \(name : Text) -> + \(srcs : List Text) -> + { name, srcs + , deps = [] : List T.Dep + , archs = [] : List Text -- uses default from buckconfig + , vis = T.Vis.Public + } + +let Library = + { name : Text + , srcs : List Text + , exported_headers : List Text + , deps : List T.Dep + , archs : List Text + , vis : T.Vis + } + +let library + : Text -> List Text -> Library + = \(name : Text) -> + \(srcs : List Text) -> + { name, srcs + , exported_headers = [] : List Text + , deps = [] : List T.Dep + , archs = [] : List Text + , vis = T.Vis.Public + } + +in { Binary, binary, Library, library } diff --git a/dhall/prelude/Prelude.dhall b/dhall/prelude/Prelude.dhall new file mode 100644 index 0000000..e37fb61 --- /dev/null +++ b/dhall/prelude/Prelude.dhall @@ -0,0 +1,31 @@ +--| Minimal Prelude (no network) + +let concatSep + : Text -> List Text -> Text + = \(sep : Text) -> + \(xs : List Text) -> + let r = List/fold Text xs { fst : Bool, acc : Text } + (\(x : Text) -> \(s : { fst : Bool, acc : Text }) -> + if s.fst then { fst = False, acc = x } + else { fst = False, acc = x ++ sep ++ s.acc }) + { fst = True, acc = "" } + in r.acc + +let map + : forall (a : Type) -> forall (b : Type) -> (a -> b) -> List a -> List b + = \(a : Type) -> \(b : Type) -> \(f : a -> b) -> \(xs : List a) -> + List/build b (\(l : Type) -> \(c : b -> l -> l) -> + List/fold a xs l (\(x : a) -> c (f x))) + +let concatMap + : forall (a : Type) -> forall (b : Type) -> (a -> List b) -> List a -> List b + = \(a : Type) -> \(b : Type) -> \(f : a -> List b) -> \(xs : List a) -> + List/build b (\(l : Type) -> \(c : b -> l -> l) -> + List/fold a xs l (\(x : a) -> List/fold b (f x) l c)) + +let null + : forall (a : Type) -> List a -> Bool + = \(a : Type) -> \(xs : List a) -> + Natural/isZero (List/length a xs) + +in { Text = { concatSep }, List = { map, concatMap, null } } diff --git a/dhall/prelude/PureScript.dhall b/dhall/prelude/PureScript.dhall new file mode 100644 index 0000000..0047048 --- /dev/null +++ b/dhall/prelude/PureScript.dhall @@ -0,0 +1,68 @@ +--| PureScript Rules + +let T = ./Types.dhall + +-- | PureScript web application (Halogen, etc.) +let App = + { name : Text + , srcs : List Text + , spago_dhall : Text + , packages_dhall : Optional Text + , main : Text + , index_html : Optional Text + , style_css : Optional Text + , vis : T.Vis + } + +let app + : Text -> List Text -> Text -> App + = \(name : Text) -> + \(srcs : List Text) -> + \(spago_dhall : Text) -> + { name, srcs, spago_dhall + , packages_dhall = Some "packages.dhall" + , main = "Main" + , index_html = Some "index.html" + , style_css = Some "style.css" + , vis = T.Vis.Public + } + +-- | PureScript Node.js binary +let Binary = + { name : Text + , srcs : List Text + , spago_dhall : Text + , packages_dhall : Optional Text + , main : Text + , vis : T.Vis + } + +let binary + : Text -> List Text -> Text -> Binary + = \(name : Text) -> + \(srcs : List Text) -> + \(spago_dhall : Text) -> + { name, srcs, spago_dhall + , packages_dhall = Some "packages.dhall" + , main = "Main" + , vis = T.Vis.Public + } + +-- | PureScript library +let Library = + { name : Text + , srcs : List Text + , spago_yaml : Optional Text + , vis : T.Vis + } + +let library + : Text -> List Text -> Library + = \(name : Text) -> + \(srcs : List Text) -> + { name, srcs + , spago_yaml = None Text + , vis = T.Vis.Public + } + +in { App, app, Binary, binary, Library, library } diff --git a/dhall/prelude/Rust.dhall b/dhall/prelude/Rust.dhall new file mode 100644 index 0000000..04e5657 --- /dev/null +++ b/dhall/prelude/Rust.dhall @@ -0,0 +1,53 @@ +--| Rust Rules + +let T = ./Types.dhall + +let Edition = < E2015 | E2018 | E2021 | E2024 > + +let Binary = + { name : Text + , srcs : List Text + , deps : List T.Dep + , edition : Edition + , features : List Text + , rustflags : List Text + , vis : T.Vis + } + +let binary + : Text -> List Text -> List T.Dep -> Binary + = \(name : Text) -> + \(srcs : List Text) -> + \(deps : List T.Dep) -> + { name, srcs, deps + , edition = Edition.E2021 + , features = [] : List Text + , rustflags = [] : List Text + , vis = T.Vis.Public + } + +let Library = + { name : Text + , srcs : List Text + , deps : List T.Dep + , edition : Edition + , crate_name : Optional Text + , features : List Text + , proc_macro : Bool + , vis : T.Vis + } + +let library + : Text -> List Text -> List T.Dep -> Library + = \(name : Text) -> + \(srcs : List Text) -> + \(deps : List T.Dep) -> + { name, srcs, deps + , edition = Edition.E2021 + , crate_name = None Text + , features = [] : List Text + , proc_macro = False + , vis = T.Vis.Public + } + +in { Edition, Binary, binary, Library, library } diff --git a/dhall/prelude/Types.dhall b/dhall/prelude/Types.dhall new file mode 100644 index 0000000..27c64cc --- /dev/null +++ b/dhall/prelude/Types.dhall @@ -0,0 +1,15 @@ +--| Core Types + +let Dep = < Local : Text | Flake : Text > + +let CxxStd = < Cxx11 | Cxx14 | Cxx17 | Cxx20 | Cxx23 > + +let Vis = < Public | Private > + +in { Dep + , CxxStd + , Vis + , local = Dep.Local + , flake = Dep.Flake + , nix = \(p : Text) -> Dep.Flake "nixpkgs#${p}" + } diff --git a/dhall/prelude/extract-deps.dhall b/dhall/prelude/extract-deps.dhall new file mode 100644 index 0000000..098f144 --- /dev/null +++ b/dhall/prelude/extract-deps.dhall @@ -0,0 +1,14 @@ +--| Extract flake refs from BUILD.dhall + +let P = ./Prelude.dhall +let T = ./Types.dhall +let C = ./Cxx.dhall + +let flakes = \(ds : List T.Dep) -> + P.List.concatMap T.Dep Text + (\(d : T.Dep) -> merge { Local = \(_ : Text) -> [] : List Text + , Flake = \(r : Text) -> [r] } d) ds + +in \(bs : List C.Binary) -> + P.Text.concatSep "\n" + (P.List.concatMap C.Binary Text (\(b : C.Binary) -> flakes b.deps) bs) diff --git a/dhall/prelude/package.dhall b/dhall/prelude/package.dhall new file mode 100644 index 0000000..ec45345 --- /dev/null +++ b/dhall/prelude/package.dhall @@ -0,0 +1,59 @@ +--| Aleph Prelude + +let T = ./Types.dhall +let C = ./Cxx.dhall +let R = ./Rust.dhall +let H = ./Haskell.dhall +let L = ./Lean.dhall +let N = ./Nv.dhall +let PS = ./PureScript.dhall + +in { -- Types + Dep = T.Dep + , CxxStd = T.CxxStd + , RustEdition = R.Edition + , Vis = T.Vis + -- Dep constructors + , local = T.local + , flake = T.flake + , nix = T.nix + -- C++ rules + , CxxBinary = C.Binary + , cxxBinary = C.binary + , CxxLibrary = C.Library + , cxxLibrary = C.library + -- Rust rules + , RustBinary = R.Binary + , rustBinary = R.binary + , RustLibrary = R.Library + , rustLibrary = R.library + -- Haskell rules + , HaskellBinary = H.Binary + , haskellBinary = H.binary + , HaskellLibrary = H.Library + , haskellLibrary = H.library + , HaskellFFIBinary = H.FFIBinary + , haskellFFIBinary = H.ffiBinary + -- Lean rules + , LeanBinary = L.Binary + , leanBinary = L.binary + , LeanLibrary = L.Library + , leanLibrary = L.library + -- NVIDIA rules + , NvBinary = N.Binary + , nvBinary = N.binary + , NvLibrary = N.Library + , nvLibrary = N.library + -- PureScript rules + , PureScriptApp = PS.App + , purescriptApp = PS.app + , PureScriptBinary = PS.Binary + , purescriptBinary = PS.binary + , PureScriptLibrary = PS.Library + , purescriptLibrary = PS.library + -- Backward compat (short names for common case) + , Binary = C.Binary + , binary = C.binary + , Library = C.Library + , library = C.library + } diff --git a/dhall/prelude/to-starlark.dhall b/dhall/prelude/to-starlark.dhall new file mode 100644 index 0000000..d2a127c --- /dev/null +++ b/dhall/prelude/to-starlark.dhall @@ -0,0 +1,300 @@ +--| Dhall -> Starlark + +let P = ./Prelude.dhall +let T = ./Types.dhall +let C = ./Cxx.dhall +let R = ./Rust.dhall +let H = ./Haskell.dhall +let L = ./Lean.dhall +let N = ./Nv.dhall +let PS = ./PureScript.dhall + +let q = \(t : Text) -> "\"${t}\"" + +let list = \(xs : List Text) -> + "[" ++ P.Text.concatSep ", " (P.List.map Text Text q xs) ++ "]" + +let flakes + : List T.Dep -> List Text + = \(ds : List T.Dep) -> + P.List.concatMap T.Dep Text + (\(d : T.Dep) -> merge { Local = \(_ : Text) -> [] : List Text + , Flake = \(r : Text) -> [r] } d) ds + +let locals + : List T.Dep -> List Text + = \(ds : List T.Dep) -> + P.List.concatMap T.Dep Text + (\(d : T.Dep) -> merge { Local = \(t : Text) -> [t] + , Flake = \(_ : Text) -> [] : List Text } d) ds + +let cxxStd = \(s : T.CxxStd) -> merge + { Cxx11 = "-std=c++11", Cxx14 = "-std=c++14", Cxx17 = "-std=c++17" + , Cxx20 = "-std=c++20", Cxx23 = "-std=c++23" } s + +let rustEdition = \(e : R.Edition) -> merge + { E2015 = "2015", E2018 = "2018", E2021 = "2021", E2024 = "2024" } e + +let vis = \(v : T.Vis) -> merge { Public = "[\"PUBLIC\"]", Private = "[]" } v + +let Flags = { compiler : List Text, linker : List Text } + +let cxxBinary + : C.Binary -> Flags -> Text + = \(b : C.Binary) -> \(f : Flags) -> + let cf = [cxxStd b.std] # b.cflags # f.compiler + let lf = b.ldflags # f.linker + in '' + cxx_binary( + name = ${q b.name}, + srcs = ${list b.srcs}, + deps = ${list (locals b.deps)}, + compiler_flags = ${list cf}, + linker_flags = ${list lf}, + visibility = ${vis b.vis}, + ) + '' + +let rustBinary + : R.Binary -> Text + = \(b : R.Binary) -> + '' + rust_binary( + name = ${q b.name}, + srcs = ${list b.srcs}, + deps = ${list (locals b.deps)}, + edition = ${q (rustEdition b.edition)}, + visibility = ${vis b.vis}, + ) + '' + +let rustLibrary + : R.Library -> Text + = \(lib : R.Library) -> + let crateName = merge { Some = \(n : Text) -> " crate_name = ${q n},\n" + , None = "" } lib.crate_name + let procMacro = if lib.proc_macro then " proc_macro = True,\n" else "" + let features = if P.List.null Text lib.features + then "" + else " features = ${list lib.features},\n" + in '' + rust_library( + name = ${q lib.name}, + srcs = ${list lib.srcs}, + deps = ${list (locals lib.deps)}, + edition = ${q (rustEdition lib.edition)}, + ${crateName}${procMacro}${features} visibility = ${vis lib.vis}, + ) + '' + +-- ══════════════════════════════════════════════════════════════════════════════ +-- Haskell +-- ══════════════════════════════════════════════════════════════════════════════ + +let haskellBinary + : H.Binary -> Text + = \(b : H.Binary) -> + let exts = if P.List.null Text b.language_extensions + then "" + else " language_extensions = ${list b.language_extensions},\n" + in '' + haskell_binary( + name = ${q b.name}, + srcs = ${list b.srcs}, + main = ${q b.main}, + packages = ${list b.packages}, + ${exts} ghc_options = ${list b.ghc_options}, + visibility = ${vis b.vis}, + ) + '' + +let haskellLibrary + : H.Library -> Text + = \(lib : H.Library) -> + let exts = if P.List.null Text lib.language_extensions + then "" + else " language_extensions = ${list lib.language_extensions},\n" + in '' + haskell_library( + name = ${q lib.name}, + srcs = ${list lib.srcs}, + packages = ${list lib.packages}, + ${exts} ghc_options = ${list lib.ghc_options}, + visibility = ${vis lib.vis}, + ) + '' + +let haskellFFIBinary + : H.FFIBinary -> Text + = \(b : H.FFIBinary) -> + let hdrs = if P.List.null Text b.cxx_headers + then "" + else " cxx_headers = ${list b.cxx_headers},\n" + let pkgs = if P.List.null Text b.packages + then "" + else " packages = ${list b.packages},\n" + let exts = if P.List.null Text b.language_extensions + then "" + else " language_extensions = ${list b.language_extensions},\n" + let ghcOpts = if P.List.null Text b.ghc_options + then "" + else " ghc_options = ${list b.ghc_options},\n" + let extraLibs = if P.List.null Text b.extra_libs + then "" + else " extra_libs = ${list b.extra_libs},\n" + let extraLibDirs = if P.List.null Text b.extra_lib_dirs + then "" + else " extra_lib_dirs = ${list b.extra_lib_dirs},\n" + let includeDirs = if P.List.null Text b.include_dirs + then "" + else " include_dirs = ${list b.include_dirs},\n" + let linkerFlags = if P.List.null Text b.linker_flags + then "" + else " linker_flags = ${list b.linker_flags},\n" + in '' + haskell_ffi_binary( + name = ${q b.name}, + hs_srcs = ${list b.hs_srcs}, + cxx_srcs = ${list b.cxx_srcs}, + ${hdrs}${pkgs}${exts}${ghcOpts}${extraLibs}${extraLibDirs}${includeDirs}${linkerFlags} visibility = ${vis b.vis}, + ) + '' + +-- ══════════════════════════════════════════════════════════════════════════════ +-- Lean +-- ══════════════════════════════════════════════════════════════════════════════ + +let leanBinary + : L.Binary -> Text + = \(b : L.Binary) -> + '' + lean_binary( + name = ${q b.name}, + srcs = ${list b.srcs}, + visibility = ${vis b.vis}, + ) + '' + +let leanLibrary + : L.Library -> Text + = \(lib : L.Library) -> + '' + lean_library( + name = ${q lib.name}, + srcs = ${list lib.srcs}, + visibility = ${vis lib.vis}, + ) + '' + +-- ══════════════════════════════════════════════════════════════════════════════ +-- NVIDIA/CUDA +-- ══════════════════════════════════════════════════════════════════════════════ + +let nvBinary + : N.Binary -> Text + = \(b : N.Binary) -> + '' + nv_binary( + name = ${q b.name}, + srcs = ${list b.srcs}, + visibility = ${vis b.vis}, + ) + '' + +let nvLibrary + : N.Library -> Text + = \(lib : N.Library) -> + let hdrs = if P.List.null Text lib.exported_headers + then "" + else " exported_headers = ${list lib.exported_headers},\n" + in '' + nv_library( + name = ${q lib.name}, + srcs = ${list lib.srcs}, + ${hdrs} visibility = ${vis lib.vis}, + ) + '' + +-- ══════════════════════════════════════════════════════════════════════════════ +-- PureScript +-- ══════════════════════════════════════════════════════════════════════════════ + +let purescriptApp + : PS.App -> Text + = \(a : PS.App) -> + let packagesDhall = merge { Some = \(f : Text) -> " packages_dhall = ${q f},\n" + , None = "" } a.packages_dhall + let indexHtml = merge { Some = \(f : Text) -> " index_html = ${q f},\n" + , None = "" } a.index_html + let styleCss = merge { Some = \(f : Text) -> " style_css = ${q f},\n" + , None = "" } a.style_css + in '' + purescript_app( + name = ${q a.name}, + srcs = ${list a.srcs}, + spago_dhall = ${q a.spago_dhall}, + ${packagesDhall} main = ${q a.main}, + ${indexHtml}${styleCss} visibility = ${vis a.vis}, + ) + '' + +let purescriptBinary + : PS.Binary -> Text + = \(b : PS.Binary) -> + let packagesDhall = merge { Some = \(f : Text) -> " packages_dhall = ${q f},\n" + , None = "" } b.packages_dhall + in '' + purescript_binary( + name = ${q b.name}, + srcs = ${list b.srcs}, + spago_dhall = ${q b.spago_dhall}, + ${packagesDhall} main = ${q b.main}, + visibility = ${vis b.vis}, + ) + '' + +let purescriptLibrary + : PS.Library -> Text + = \(lib : PS.Library) -> + let spagoYaml = merge { Some = \(f : Text) -> " spago_yaml = ${q f},\n" + , None = "" } lib.spago_yaml + in '' + purescript_library( + name = ${q lib.name}, + srcs = ${list lib.srcs}, + ${spagoYaml} visibility = ${vis lib.vis}, + ) + '' + +-- ══════════════════════════════════════════════════════════════════════════════ +-- Dep extractors +-- ══════════════════════════════════════════════════════════════════════════════ + +let cxxDeps = \(b : C.Binary) -> P.Text.concatSep "\n" (flakes b.deps) +let rustBinaryDeps = \(b : R.Binary) -> P.Text.concatSep "\n" (flakes b.deps) +let rustLibraryDeps = \(lib : R.Library) -> P.Text.concatSep "\n" (flakes lib.deps) + +-- Backward compat aliases +let std = cxxStd +let binary = cxxBinary +let deps = cxxDeps + +in { q, list, flakes, locals + , cxxStd, rustEdition, vis, Flags + -- C++ + , cxxBinary + , cxxDeps + -- Rust + , rustBinary, rustLibrary + , rustBinaryDeps, rustLibraryDeps + -- Haskell + , haskellBinary, haskellLibrary, haskellFFIBinary + -- Lean + , leanBinary, leanLibrary + -- NVIDIA + , nvBinary, nvLibrary + -- PureScript + , purescriptApp, purescriptBinary, purescriptLibrary + -- backward compat + , std, binary, deps + } diff --git a/dhall/to-buck2.dhall b/dhall/to-buck2.dhall new file mode 100644 index 0000000..b603a46 --- /dev/null +++ b/dhall/to-buck2.dhall @@ -0,0 +1,145 @@ +{- to-buck2.dhall + + Render a Dhall Target as a Buck2 BUCK file. + + This version uses nix_genrule.bzl which resolves nix flake refs at + genrule execution time (not analysis time). This avoids the need for + .buckconfig.local shell hook magic. + + Usage: + dhall text <<< '(./to-buck2.dhall).render ./BUILD.dhall' + dhall text <<< '(./to-buck2.dhall).render ./BUILD.dhall' > BUCK +-} + +let Target = ./Build.dhall +let Resource = ./Resource.dhall +let Prelude = https://prelude.dhall-lang.org/v23.1.0/package.dhall + sha256:931cbfae9d746c4611b07633ab1e547637ab4ba138b16bf65ef1b9ad66a60b7f + +-- Quote a string for Starlark +let quote = \(t : Text) -> "\"${t}\"" + +-- Join list of text with separator +let join : Text -> List Text -> Text = \(sep : Text) -> \(xs : List Text) -> + Prelude.Text.concatSep sep xs + +-- Render sources list (only handles Files variant for now) +let renderSrcs : Target.Src -> Text = \(srcs : Target.Src) -> + merge + { Files = \(files : List Text) -> + let quoted = Prelude.List.map Text Text quote files + in "[" ++ join ", " quoted ++ "]" + , Fetch = \(_ : { url : Text, hash : Text }) -> "[]" + , Git = \(_ : { url : Text, rev : Text, hash : Text }) -> "[]" + } + srcs + +-- Extract flake refs from deps (these become nix_deps) +let extractFlakeRefs : List Target.Dep -> List Text = \(deps : List Target.Dep) -> + Prelude.List.concatMap + Target.Dep + Text + (\(d : Target.Dep) -> + merge + { Local = \(_ : Text) -> [] : List Text + , Flake = \(ref : Text) -> [ref] + , PkgConfig = \(_ : Text) -> [] : List Text + , External = \(_ : { hash : Text, name : Text }) -> [] : List Text + } + d + ) + deps + +-- Render list of text as Starlark list +let renderList : List Text -> Text = \(xs : List Text) -> + let quoted = Prelude.List.map Text Text quote xs + in "[" ++ join ", " quoted ++ "]" + +-- Extract local deps (become regular deps in Buck2) +let extractLocalDeps : List Target.Dep -> List Text = \(deps : List Target.Dep) -> + Prelude.List.concatMap + Target.Dep + Text + (\(d : Target.Dep) -> + merge + { Local = \(t : Text) -> [t] + , Flake = \(_ : Text) -> [] : List Text + , PkgConfig = \(_ : Text) -> [] : List Text + , External = \(_ : { hash : Text, name : Text }) -> [] : List Text + } + d + ) + deps + +-- Render coeffects as string +let renderCoeffects : Resource.Resources -> Text = \(rs : Resource.Resources) -> + if Resource.isPure rs then "pure" + else + let first = List/head Resource.Resource rs + in merge + { None = "pure" + , Some = Resource.renderOne + } + first + +-- Get compiler from toolchain +let getCompiler : Target.Toolchain.Toolchain -> Text = + \(tc : Target.Toolchain.Toolchain) -> + merge + { Clang = \(_ : { version : Text }) -> "clang++" + , NVClang = \(_ : { version : Text }) -> "nv-clang++" + , GCC = \(_ : { version : Text }) -> "g++" + , NVCC = \(_ : { version : Text }) -> "nvcc" + , Rustc = \(_ : { version : Text }) -> "rustc" + , GHC = \(_ : { version : Text }) -> "ghc" + , Lean = \(_ : { version : Text }) -> "lean" + } + tc.compiler + +-- Main render function +let render : Target.Target -> Text = \(target : Target.Target) -> + let flakeRefs = extractFlakeRefs target.deps + let coeffects = renderCoeffects target.requires + let compiler = getCompiler target.toolchain + in + '' + # Generated by dhall-to-buck2 + # Target: ${target.name} + # Coeffects: ${coeffects} + + load("@toolchains//:nix_genrule.bzl", "nix_cxx_binary") + + nix_cxx_binary( + name = ${quote target.name}, + srcs = ${renderSrcs target.srcs}, + nix_deps = ${renderList flakeRefs}, + compiler = "${compiler}", + ) + '' + +-- Render with witnessed execution (attestations) +let renderWitnessed : Target.Target -> Text = \(target : Target.Target) -> + let flakeRefs = extractFlakeRefs target.deps + let coeffects = renderCoeffects target.requires + let compiler = getCompiler target.toolchain + in + '' + # Generated by dhall-to-buck2 (witnessed) + # Target: ${target.name} + # Coeffects: ${coeffects} + + load("@toolchains//:nix_genrule.bzl", "nix_cxx_binary_witnessed") + + nix_cxx_binary_witnessed( + name = ${quote target.name}, + srcs = ${renderSrcs target.srcs}, + nix_deps = ${renderList flakeRefs}, + compiler = "${compiler}", + coeffects = "${coeffects}", + ) + '' + +in { render + , renderWitnessed + , default = render + } diff --git a/flake.lock b/flake.lock index bd9905c..388d701 100644 --- a/flake.lock +++ b/flake.lock @@ -1,12 +1,68 @@ { "nodes": { + "agenix": { + "inputs": { + "darwin": "darwin", + "home-manager": "home-manager", + "nixpkgs": [ + "sensenet", + "nvidia-sdk", + "nixpkgs" + ], + "systems": "systems_2" + }, + "locked": { + "lastModified": 1762618334, + "narHash": "sha256-wyT7Pl6tMFbFrs8Lk/TlEs81N6L+VSybPfiIgzU8lbQ=", + "owner": "ryantm", + "repo": "agenix", + "rev": "fcdea223397448d35d9b31f798479227e80183f6", + "type": "github" + }, + "original": { + "owner": "ryantm", + "repo": "agenix", + "type": "github" + } + }, + "buck2-prelude": { + "flake": false, + "locked": { + "lastModified": 1747422168, + "narHash": "sha256-iO5DxMc0uBhQfCVYJ+gD4avIhqlect7QOaszyBcBtUI=", + "owner": "weyl-ai", + "repo": "straylight-buck2-prelude", + "rev": "b42171bedf69b18a3c9a8e5918e96e10c196062f", + "type": "github" + }, + "original": { + "owner": "weyl-ai", + "repo": "straylight-buck2-prelude", + "type": "github" + } + }, "crane": { "locked": { - "lastModified": 1770169865, - "narHash": "sha256-iPiy13xzDQ9GjpOez+NNIjh/qjl7i4RDf9dF2x5mF9I=", + "lastModified": 1770419512, + "narHash": "sha256-o8Vcdz6B6bkiGUYkZqFwH3Pv1JwZyXht3dMtS7RchIo=", + "owner": "ipetkov", + "repo": "crane", + "rev": "2510f2cbc3ccd237f700bb213756a8f35c32d8d7", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "crane_2": { + "locked": { + "lastModified": 1747587869, + "narHash": "sha256-Zay3WJdSvC2VQmNqWSVLBOg/1iS/0/Q0c9JOBsB+3qw=", "owner": "ipetkov", "repo": "crane", - "rev": "8254ccf3b5b5131890ee073776f2e61c6d1e55d4", + "rev": "76603d32f18e0e378d9f6335c8fc286413493655", "type": "github" }, "original": { @@ -15,16 +71,146 @@ "type": "github" } }, + "darwin": { + "inputs": { + "nixpkgs": [ + "sensenet", + "nvidia-sdk", + "agenix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1744478979, + "narHash": "sha256-dyN+teG9G82G+m+PX/aSAagkC+vUv0SgUw3XkPhQodQ=", + "owner": "lnl7", + "repo": "nix-darwin", + "rev": "43975d782b418ebf4969e9ccba82466728c2851b", + "type": "github" + }, + "original": { + "owner": "lnl7", + "ref": "master", + "repo": "nix-darwin", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1769996383, + "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "57928607ea566b5db3ad13af0e57e921e6b12381", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_2": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib_2" + }, + "locked": { + "lastModified": 1769996383, + "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "57928607ea566b5db3ad13af0e57e921e6b12381", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_3": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib_3" + }, + "locked": { + "lastModified": 1743550720, + "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "c621e8422220273271f52058f618c94e405bb0f5", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_4": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib_4" + }, + "locked": { + "lastModified": 1769996383, + "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "57928607ea566b5db3ad13af0e57e921e6b12381", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_5": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib_5" + }, + "locked": { + "lastModified": 1767602221, + "narHash": "sha256-wl3P4WioICNY9SEvClT4JLAWFtItk3CbFNm/FmeeWlI=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "1e34f377e37de05071735d373b481da2508dd625", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": "systems" }, "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "type": "github" }, "original": { @@ -33,13 +219,202 @@ "type": "github" } }, + "ghc-source-gen-src": { + "flake": false, + "locked": { + "lastModified": 1770666256, + "narHash": "sha256-yD8BRFCkZft7wQZ9Wj25o4zkXYBIYmSAHj2OJvyk2lA=", + "owner": "google", + "repo": "ghc-source-gen", + "rev": "985e3ad928e3c02c425f92f406d27d0fb13c8564", + "type": "github" + }, + "original": { + "owner": "google", + "repo": "ghc-source-gen", + "type": "github" + } + }, + "git-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "sensenet", + "nativelink", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1747372754, + "narHash": "sha256-2Y53NGIX2vxfie1rOW0Qb86vjRZ7ngizoo+bnXU9D9k=", + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "80479b6ec16fefd9c1db3ea13aeb038c60530f46", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "sensenet", + "nativelink", + "git-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "home-manager": { + "inputs": { + "nixpkgs": [ + "sensenet", + "nvidia-sdk", + "agenix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1745494811, + "narHash": "sha256-YZCh2o9Ua1n9uCvrvi5pRxtuVNml8X2a03qIFfRKpFs=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "abfad3d2958c9e6300a883bd443512c55dfeb1be", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "home-manager", + "type": "github" + } + }, + "llvm-project": { + "flake": false, + "locked": { + "lastModified": 1767604300, + "narHash": "sha256-mxeR/IxwZqi+Wbh0MXE0YFFzCQl8YpIY/nHF5WyHMNE=", + "owner": "llvm", + "repo": "llvm-project", + "rev": "bb1f220d534b0f6d80bea36662f5188ff11c2e54", + "type": "github" + }, + "original": { + "owner": "llvm", + "repo": "llvm-project", + "rev": "bb1f220d534b0f6d80bea36662f5188ff11c2e54", + "type": "github" + } + }, + "llvm-project_2": { + "flake": false, + "locked": { + "lastModified": 1767604300, + "narHash": "sha256-mxeR/IxwZqi+Wbh0MXE0YFFzCQl8YpIY/nHF5WyHMNE=", + "owner": "llvm", + "repo": "llvm-project", + "rev": "bb1f220d534b0f6d80bea36662f5188ff11c2e54", + "type": "github" + }, + "original": { + "owner": "llvm", + "repo": "llvm-project", + "rev": "bb1f220d534b0f6d80bea36662f5188ff11c2e54", + "type": "github" + } + }, + "nativelink": { + "inputs": { + "crane": "crane_2", + "flake-parts": "flake-parts_3", + "git-hooks": "git-hooks", + "nix2container": "nix2container", + "nixpkgs": "nixpkgs_2", + "rust-overlay": "rust-overlay_2" + }, + "locked": { + "lastModified": 1770915725, + "narHash": "sha256-v05TnA+SXH32T0NaA+Sjr5qpwrZvyqClTw0CSzhFO4c=", + "owner": "TraceMachina", + "repo": "nativelink", + "rev": "6ffab5f049666158b14e277653d8ce6b487c2ff6", + "type": "github" + }, + "original": { + "owner": "TraceMachina", + "repo": "nativelink", + "type": "github" + } + }, + "nix-compile": { + "inputs": { + "flake-parts": "flake-parts_4", + "nixpkgs": [ + "sensenet", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1770927881, + "narHash": "sha256-jAbdEW0zWh2uckZFA49GLX5TMqluZZPUQPSpVn89qN4=", + "owner": "straylight-software", + "repo": "nix-compile", + "rev": "7954785e214d2d999d6c47414496fb1405a7cf36", + "type": "github" + }, + "original": { + "owner": "straylight-software", + "repo": "nix-compile", + "type": "github" + } + }, + "nix2container": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": [ + "sensenet", + "nativelink", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1729339656, + "narHash": "sha256-smV7HQ/OqZeRguQxNjsb3uQDwm0p6zKDbSDbPCav/oY=", + "owner": "nlewo", + "repo": "nix2container", + "rev": "cc96df7c3747c61c584d757cfc083922b4f4b33e", + "type": "github" + }, + "original": { + "owner": "nlewo", + "repo": "nix2container", + "rev": "cc96df7c3747c61c584d757cfc083922b4f4b33e", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1770197578, - "narHash": "sha256-AYqlWrX09+HvGs8zM6ebZ1pwUqjkfpnv8mewYwAo+iM=", + "lastModified": 1770841267, + "narHash": "sha256-9xejG0KoqsoKEGp2kVbXRlEYtFFcDTHjidiuX8hGO44=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "00c21e4c93d963c50d4c0c89bfa84ed6e0694df2", + "rev": "ec7c70d12ce2fc37cb92aff673dcdca89d187bae", "type": "github" }, "original": { @@ -49,12 +424,145 @@ "type": "github" } }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1769909678, + "narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "72716169fe93074c333e8d0173151350670b824c", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixpkgs-lib_2": { + "locked": { + "lastModified": 1769909678, + "narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "72716169fe93074c333e8d0173151350670b824c", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixpkgs-lib_3": { + "locked": { + "lastModified": 1743296961, + "narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixpkgs-lib_4": { + "locked": { + "lastModified": 1769909678, + "narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "72716169fe93074c333e8d0173151350670b824c", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixpkgs-lib_5": { + "locked": { + "lastModified": 1765674936, + "narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1747852984, + "narHash": "sha256-q2PmaOxyR3zqOF54a3E1Cj1gh0sDu8APX9b+OkX4J5s=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "8c441601c43232976179eac52dde704c8bdf81ed", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1770107345, + "narHash": "sha256-tbS0Ebx2PiA1FRW8mt8oejR0qMXmziJmPaU1d4kYY9g=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "4533d9293756b63904b7238acb84ac8fe4c8c2c4", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nvidia-sdk": { + "inputs": { + "agenix": "agenix", + "flake-parts": "flake-parts_5", + "llvm-project": "llvm-project_2", + "nixpkgs": [ + "sensenet", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1770925697, + "narHash": "sha256-4/fn8M6ijwvFvzoh7Hy8ihn5JPnvcKaEcx72w2uAFhc=", + "owner": "weyl-ai", + "repo": "nvidia-sdk", + "rev": "94339be57fba64fe091c5b796c4d4e26e44c6412", + "type": "github" + }, + "original": { + "owner": "weyl-ai", + "repo": "nvidia-sdk", + "type": "github" + } + }, "root": { "inputs": { "crane": "crane", - "flake-utils": "flake-utils", + "flake-parts": "flake-parts", "nixpkgs": "nixpkgs", - "rust-overlay": "rust-overlay" + "rust-overlay": "rust-overlay", + "sensenet": "sensenet", + "systems": "systems_4" } }, "rust-overlay": { @@ -64,11 +572,11 @@ ] }, "locked": { - "lastModified": 1770347142, - "narHash": "sha256-uz+ZSqXpXEPtdRPYwvgsum/CfNq7AUQ/0gZHqTigiPM=", + "lastModified": 1770865833, + "narHash": "sha256-oiARqnlvaW6pVGheVi4ye6voqCwhg5hCcGish2ZvQzI=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "2859683cd9ef7858d324c5399b0d8d6652bf4044", + "rev": "c8cfbe26238638e2f3a2c0ae7e8d240f5e4ded85", "type": "github" }, "original": { @@ -77,6 +585,57 @@ "type": "github" } }, + "rust-overlay_2": { + "inputs": { + "nixpkgs": [ + "sensenet", + "nativelink", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1747795013, + "narHash": "sha256-c7i0xJ+xFhgjO9SWHYu5dF/7lq63RPDvwKAdjc6VCE4=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "6b1cf12374361859242a562e1933a7930649131a", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "sensenet": { + "inputs": { + "buck2-prelude": "buck2-prelude", + "flake-parts": "flake-parts_2", + "ghc-source-gen-src": "ghc-source-gen-src", + "llvm-project": "llvm-project", + "nativelink": "nativelink", + "nix-compile": "nix-compile", + "nixpkgs": [ + "nixpkgs" + ], + "nvidia-sdk": "nvidia-sdk", + "systems": "systems_3", + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1770930760, + "narHash": "sha256-RIyuu5ZX3+8jXFpL2kPX8JLo+0j74TRlj8HMrK5iYr4=", + "owner": "straylight-software", + "repo": "sensenet", + "rev": "0477adf19ebe7f0d4c01873e0421c774e7bad249", + "type": "github" + }, + "original": { + "owner": "straylight-software", + "repo": "sensenet", + "type": "github" + } + }, "systems": { "locked": { "lastModified": 1681028828, @@ -91,6 +650,69 @@ "repo": "default", "type": "github" } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_3": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_4": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": "nixpkgs_3" + }, + "locked": { + "lastModified": 1770228511, + "narHash": "sha256-wQ6NJSuFqAEmIg2VMnLdCnUc0b7vslUohqqGGD+Fyxk=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "337a4fe074be1042a35086f15481d763b8ddc0e7", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index af93385..ae48e29 100644 --- a/flake.nix +++ b/flake.nix @@ -1,9 +1,18 @@ { - description = "slide - nobody in la fucks with me, you got that?"; + description = "slide - console cowboy for the sprawl"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - flake-utils.url = "github:numtide/flake-utils"; + systems.url = "github:nix-systems/default"; + flake-parts.url = "github:hercules-ci/flake-parts"; + + # sensenet provides Buck2 build infrastructure + sensenet = { + url = "github:straylight-software/sensenet"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + # Rust tooling for tokenizers-cpp crane.url = "github:ipetkov/crane"; rust-overlay = { url = "github:oxalica/rust-overlay"; @@ -11,99 +20,117 @@ }; }; - outputs = { self, nixpkgs, flake-utils, crane, rust-overlay }: - flake-utils.lib.eachDefaultSystem (system: - let - overlays = [ (import rust-overlay) ]; - pkgs = import nixpkgs { inherit system overlays; }; - - rustToolchain = pkgs.rust-bin.stable.latest.default; - craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain; - - # tokenizers-cpp with pre-built Rust library - tokenizers-cpp = pkgs.callPackage ./nix/tokenizers-cpp.nix { - inherit craneLib; - }; - - hsPkgs = pkgs.haskellPackages.override { - overrides = hself: hsuper: { - slide = - let - drv = hself.callCabal2nix "slide" ./. {}; - in - pkgs.haskell.lib.overrideCabal drv (old: { - buildTools = (old.buildTools or []) ++ [ pkgs.gcc pkgs.cmake ]; - librarySystemDepends = (old.librarySystemDepends or []) ++ [ tokenizers-cpp ]; - # Set up library paths for C++ compilation and linking - preConfigure = '' - export LIBRARY_PATH="${tokenizers-cpp}/lib''${LIBRARY_PATH:+:$LIBRARY_PATH}" - export LD_LIBRARY_PATH="${tokenizers-cpp}/lib''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" - export CFLAGS="-I${tokenizers-cpp}/include $CFLAGS" - export CXXFLAGS="-I${tokenizers-cpp}/include $CXXFLAGS" - export LDFLAGS="-L${tokenizers-cpp}/lib -ltokenizers_cpp -ltokenizers_c -lsentencepiece -lstdc++ $LDFLAGS" - ''; - }); + outputs = + inputs@{ flake-parts, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + systems = import inputs.systems; + + imports = [ + # Import sensenet's Buck2 module + inputs.sensenet.flakeModules.buck2 + inputs.sensenet.flakeModules.std + ]; + + perSystem = + { pkgs, system, ... }: + let + # GHC 9.12 with sensenet's haskell overlay + inherit (pkgs.haskell.packages) ghc912; + + # Rust toolchain for tokenizers-cpp + rustOverlays = [ (import inputs.rust-overlay) ]; + rustPkgs = import inputs.nixpkgs { + inherit system; + overlays = rustOverlays; }; - }; - - in { - packages = { - inherit tokenizers-cpp; - slide = hsPkgs.slide; - default = hsPkgs.slide; - }; - + rustToolchain = rustPkgs.rust-bin.stable.latest.default; + craneLib = (inputs.crane.mkLib rustPkgs).overrideToolchain rustToolchain; - - apps = { - slide = { - type = "app"; - program = "${self.packages.${system}.slide}/bin/slide"; + # tokenizers-cpp with pre-built Rust library + tokenizers-cpp = pkgs.callPackage ./nix/tokenizers-cpp.nix { + inherit craneLib; + }; + in + { + # ══════════════════════════════════════════════════════════════════════ + # slide - Buck2 build + # ══════════════════════════════════════════════════════════════════════ + # Usage: nix develop + # buck2 build //:slide + buck2.projects.default = { + src = ./.; + targets = [ "//:slide" ]; + toolchain = { + cxx.enable = true; + haskell = { + enable = true; + ghcPackages = ghc912; + packages = hp: [ + # Core + hp.aeson + hp.async + hp.bytestring + hp.containers + hp.data-default-class + hp.text + hp.vector + # Parsing + hp.megaparsec + # Networking + hp.case-insensitive + hp.http2 + hp.http-semantics + hp.http-types + hp.network + hp.time-manager + hp.tls + hp.wai + hp.warp + hp.zeromq4-haskell + # Config + hp.dhall + hp.optparse-applicative + # Crypto + hp.blake3 + hp.crypton + hp.memory + # Observability + hp.katip + hp.prometheus-client + hp.prometheus-metrics-ghc + hp.random + ]; + }; + }; + extraPackages = [ + tokenizers-cpp + pkgs.zeromq + ]; + extraBuckconfigSections = '' + + [slide] + tokenizers_cpp_lib = ${tokenizers-cpp}/lib + tokenizers_cpp_include = ${tokenizers-cpp}/include + ''; + devShellPackages = [ + pkgs.dhall + pkgs.dhall-json + ghc912.haskell-language-server + ]; + devShellHook = '' + export LIBRARY_PATH="${tokenizers-cpp}/lib''${LIBRARY_PATH:+:$LIBRARY_PATH}" + export C_INCLUDE_PATH="${tokenizers-cpp}/include''${C_INCLUDE_PATH:+:$C_INCLUDE_PATH}" + export LD_LIBRARY_PATH="${tokenizers-cpp}/lib''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" + + echo "" + echo " ╷┌─┐┐ ┬┬ ┌─┐┌┐┐┌─┐ ┐─┐┬ o┬─┐┌─┐" + echo " ││─┤└┬┘│ ├─ │││├─ └─┐│ ││ │├─ " + echo "╶─┘┘ ┴ ┴ ┘─┘┴─┘┘└┘┴─┘ ──┘┘─┘┘┘─┘┴─┘" + echo "" + echo " buck2 build //:slide" + echo "" + ''; }; - default = self.apps.${system}.slide; - }; - - devShells.default = pkgs.mkShell { - buildInputs = with pkgs; [ - # Haskell - hsPkgs.ghc - hsPkgs.cabal-install - hsPkgs.haskell-language-server - - # C++ toolchain for FFI - gcc - cmake - - # Dependencies - zeromq - pkg-config - zlib - - # tokenizers-cpp for FFI development - tokenizers-cpp - - # Dev tools - hsPkgs.hlint - hsPkgs.fourmolu - ]; - - LIBRARY_PATH = "${tokenizers-cpp}/lib"; - C_INCLUDE_PATH = "${tokenizers-cpp}/include"; - PKG_CONFIG_PATH = "${tokenizers-cpp}/lib/pkgconfig"; - LD_LIBRARY_PATH = "${tokenizers-cpp}/lib"; - - inputsFrom = [ self.packages.${system}.slide ]; - - shellHook = '' - echo "" - echo " ╷┌─┐┐ ┬┬ ┌─┐┌┐┐┌─┐ ┐─┐┬ o┬─┐┌─┐" - echo " ││─┤└┬┘│ ├─ │││├─ └─┐│ ││ │├─ " - echo "╶─┘┘ ┴ ┴ ┘─┘┴─┘┘└┘┴─┘ ──┘┘─┘┘┘─┘┴─┘" - echo "" - echo " straylight dev shell" - echo "" - ''; }; - } - ); + }; } diff --git a/toolchains/cxx.bzl b/toolchains/cxx.bzl new file mode 100644 index 0000000..d3bf541 --- /dev/null +++ b/toolchains/cxx.bzl @@ -0,0 +1,230 @@ +# nix/build/toolchains/cxx.bzl +# +# LLVM 22 C++ toolchain using hermetic Nix store paths. +# +# One toolchain for everything: +# - Host C++ compilation (clang++) +# - Device compilation (clang++ -x cuda) +# - Linking (lld) +# - Archives (llvm-ar) +# +# Paths are read from .buckconfig.local, generated by `nix develop`. +# No wrappers. No PATH lookup. Just absolute Nix store paths. +# +# No GCC. No nvcc. Ever. + +# NOTE: We must use upstream @prelude types for providers that interact with +# upstream rules (cxx_binary, etc.). Buck2 uses nominal typing, so even +# structurally identical providers are incompatible if defined in different cells. +# +# Our local @straylight_prelude extractions are for documentation/archaeology +# and will be used once we have our own rule implementations. +load( + "@prelude//cxx:cxx_toolchain_types.bzl", + "BinaryUtilitiesInfo", + "CCompilerInfo", + "CvtresCompilerInfo", + "CxxCompilerInfo", + "CxxInternalTools", + "CxxPlatformInfo", + "CxxToolchainInfo", + "LinkerInfo", + "LinkerType", + "PicBehavior", + "RcCompilerInfo", + "ShlibInterfacesMode", +) +load("@prelude//cxx:headers.bzl", "HeaderMode") +load("@prelude//linking:link_info.bzl", "LinkStyle") +load("@prelude//linking:lto.bzl", "LtoMode") + +def _run_info(args): + return None if args == None else RunInfo(args = [args]) + +def _llvm_toolchain_impl(ctx: AnalysisContext) -> list[Provider]: + """ + LLVM 22 toolchain with paths from .buckconfig.local. + + Reads [cxx] section for absolute Nix store paths: + cc, cxx, ar, ld - tool paths + clang_resource_dir, gcc_include, etc. - for include flags + """ + + # ════════════════════════════════════════════════════════════════════════════ + # Read tool paths from config (fall back to PATH lookup) + # ════════════════════════════════════════════════════════════════════════════ + cc = read_root_config("cxx", "cc", "clang") + cxx = read_root_config("cxx", "cxx", "clang++") + ar = read_root_config("cxx", "ar", "llvm-ar") + ld = read_root_config("cxx", "ld", "ld.lld") + + # ════════════════════════════════════════════════════════════════════════════ + # Read Turing Registry flags from config + # ════════════════════════════════════════════════════════════════════════════ + # These are the non-negotiable flags from nix/prelude/turing-registry.nix + config_c_flags_str = read_root_config("cxx.flags", "c_flags", "") + config_cxx_flags_str = read_root_config("cxx.flags", "cxx_flags", "") + + # Parse space-separated flags into list + config_c_flags = config_c_flags_str.split() if config_c_flags_str else [] + config_cxx_flags = config_cxx_flags_str.split() if config_cxx_flags_str else [] + + # ════════════════════════════════════════════════════════════════════════════ + # Build include flags from config paths + # ════════════════════════════════════════════════════════════════════════════ + include_flags = [] + + # Clang resource directory (for __stddef.h, etc.) + clang_resource_dir = read_root_config("cxx", "clang_resource_dir", None) + if clang_resource_dir: + include_flags.append("-resource-dir=" + clang_resource_dir) + include_flags.append("-isystem" + clang_resource_dir + "/include") + + # GCC libstdc++ headers + gcc_include = read_root_config("cxx", "gcc_include", None) + if gcc_include: + include_flags.append("-isystem" + gcc_include) + + gcc_include_arch = read_root_config("cxx", "gcc_include_arch", None) + if gcc_include_arch: + include_flags.append("-isystem" + gcc_include_arch) + + # glibc headers + glibc_include = read_root_config("cxx", "glibc_include", None) + if glibc_include: + include_flags.append("-isystem" + glibc_include) + + # mdspan (Kokkos reference implementation, until libstdc++ ships it) + mdspan_include = read_root_config("cxx", "mdspan_include", None) + if mdspan_include: + include_flags.append("-isystem" + mdspan_include) + + # ════════════════════════════════════════════════════════════════════════════ + # Build link flags from config paths + # ════════════════════════════════════════════════════════════════════════════ + # Get the bin directory from the linker path for -B + # NOTE: -B must come BEFORE -fuse-ld so clang knows where to find lld + llvm_bin_dir = ld.rsplit("/", 1)[0] if "/" in ld else None + extra_link_flags = [] + if llvm_bin_dir: + extra_link_flags.append("-B" + llvm_bin_dir) + extra_link_flags.append("-fuse-ld=lld") + + # glibc_lib: contains CRT files (Scrt1.o, crti.o, crtn.o) and libc, libm, libpthread + glibc_lib = read_root_config("cxx", "glibc_lib", None) + if glibc_lib: + # -B tells clang where to find CRT files + extra_link_flags.append("-B" + glibc_lib) + extra_link_flags.append("-L" + glibc_lib) + extra_link_flags.append("-Wl,-rpath," + glibc_lib) + + # gcc_lib: contains crtbeginS.o, crtendS.o, libgcc.a, libgcc_s.so + gcc_lib = read_root_config("cxx", "gcc_lib", None) + if gcc_lib: + extra_link_flags.append("-B" + gcc_lib) + extra_link_flags.append("-L" + gcc_lib) + extra_link_flags.append("-Wl,-rpath," + gcc_lib) + + # gcc_lib_base: contains libstdc++.so + gcc_lib_base = read_root_config("cxx", "gcc_lib_base", None) + if gcc_lib_base: + extra_link_flags.append("-L" + gcc_lib_base) + extra_link_flags.append("-Wl,-rpath," + gcc_lib_base) + + # ════════════════════════════════════════════════════════════════════════════ + # Combine flags: include paths + turing registry + extra flags + # ════════════════════════════════════════════════════════════════════════════ + # Order: include_flags (paths) + config flags (turing registry) + extra flags (project-specific) + c_flags = include_flags + config_c_flags + ctx.attrs.c_extra_flags + cxx_flags = include_flags + config_cxx_flags + ctx.attrs.cxx_extra_flags + link_flags = extra_link_flags + ctx.attrs.link_flags + + # ════════════════════════════════════════════════════════════════════════════ + # Build the toolchain provider + # ════════════════════════════════════════════════════════════════════════════ + return [ + DefaultInfo(), + CxxToolchainInfo( + internal_tools = ctx.attrs._internal_tools[CxxInternalTools], + linker_info = LinkerInfo( + linker = _run_info(cxx), + linker_flags = link_flags, + post_linker_flags = [], + archiver = _run_info(ar), + archiver_type = "gnu", + archiver_supports_argfiles = True, + generate_linker_maps = False, + lto_mode = LtoMode("none"), + type = LinkerType("gnu"), + link_binaries_locally = True, + link_libraries_locally = True, + archive_objects_locally = True, + use_archiver_flags = True, + static_dep_runtime_ld_flags = [], + static_pic_dep_runtime_ld_flags = [], + shared_dep_runtime_ld_flags = [], + independent_shlib_interface_linker_flags = [], + shlib_interfaces = ShlibInterfacesMode("disabled"), + link_style = LinkStyle(ctx.attrs.link_style), + link_weight = 1, + binary_extension = "", + object_file_extension = "o", + shared_library_name_default_prefix = "lib", + shared_library_name_format = "{}.so", + shared_library_versioned_name_format = "{}.so.{}", + static_library_extension = "a", + force_full_hybrid_if_capable = False, + is_pdb_generated = False, + link_ordering = None, + ), + bolt_enabled = False, + binary_utilities_info = BinaryUtilitiesInfo( + nm = RunInfo(args = ["llvm-nm"]), + objcopy = RunInfo(args = ["llvm-objcopy"]), + objdump = RunInfo(args = ["llvm-objdump"]), + ranlib = RunInfo(args = ["llvm-ranlib"]), + strip = RunInfo(args = ["llvm-strip"]), + dwp = None, + bolt_msdk = None, + ), + cxx_compiler_info = CxxCompilerInfo( + compiler = _run_info(cxx), + preprocessor_flags = [], + compiler_flags = cxx_flags, + compiler_type = "clang", + ), + c_compiler_info = CCompilerInfo( + compiler = _run_info(cc), + preprocessor_flags = [], + compiler_flags = c_flags, + compiler_type = "clang", + ), + as_compiler_info = CCompilerInfo( + compiler = _run_info(cc), + compiler_type = "clang", + ), + asm_compiler_info = CCompilerInfo( + compiler = _run_info(cc), + compiler_type = "clang", + ), + header_mode = HeaderMode("symlink_tree_only"), + cpp_dep_tracking_mode = "makefile", + pic_behavior = PicBehavior("supported"), + llvm_link = RunInfo(args = ["llvm-link"]), + ), + CxxPlatformInfo(name = "x86_64"), + ] + +llvm_toolchain = rule( + impl = _llvm_toolchain_impl, + attrs = { + # Extra flags are added AFTER the Turing Registry flags from config + # Use these for project-specific additions, not to override the registry + "c_extra_flags": attrs.list(attrs.string(), default = []), + "cxx_extra_flags": attrs.list(attrs.string(), default = []), + "link_flags": attrs.list(attrs.string(), default = []), + "link_style": attrs.string(default = "static"), + "_internal_tools": attrs.default_only(attrs.exec_dep(providers = [CxxInternalTools], default = "prelude//cxx/tools:internal_tools")), + }, + is_toolchain_rule = True, +) diff --git a/toolchains/execution.bzl b/toolchains/execution.bzl new file mode 100644 index 0000000..24b9e0c --- /dev/null +++ b/toolchains/execution.bzl @@ -0,0 +1,85 @@ +# toolchains/execution.bzl +# +# Execution platforms for Buck2 remote execution (LRE). +# +# By default, the prelude's execution_platform has remote_enabled=False. +# These platforms enable remote execution for NativeLink. + +def _lre_execution_platform_impl(ctx: AnalysisContext) -> list[Provider]: + """Execution platform with remote execution enabled.""" + constraints = dict() + constraints.update(ctx.attrs.cpu_configuration[ConfigurationInfo].constraints) + constraints.update(ctx.attrs.os_configuration[ConfigurationInfo].constraints) + cfg = ConfigurationInfo(constraints = constraints, values = {}) + + name = ctx.label.raw_target() + + # Build executor config based on whether remote is enabled + if ctx.attrs.remote_enabled: + executor_config = CommandExecutorConfig( + local_enabled = ctx.attrs.local_enabled, + remote_enabled = True, + use_windows_path_separators = False, + # RE properties - platform capabilities for worker matching + # nix-worker matches both local NixOS workers and Fly.io workers + remote_execution_properties = { + "OSFamily": "linux", + "container-image": "nix-worker", + }, + remote_execution_use_case = "buck2-default", + remote_output_paths = "output_paths", + ) + else: + executor_config = CommandExecutorConfig( + local_enabled = ctx.attrs.local_enabled, + remote_enabled = False, + use_windows_path_separators = False, + ) + + platform = ExecutionPlatformInfo( + label = name, + configuration = cfg, + executor_config = executor_config, + ) + + return [ + DefaultInfo(), + platform, + PlatformInfo(label = str(name), configuration = cfg), + ExecutionPlatformRegistrationInfo(platforms = [platform]), + ] + +lre_execution_platform = rule( + impl = _lre_execution_platform_impl, + attrs = { + "cpu_configuration": attrs.dep(providers = [ConfigurationInfo]), + "os_configuration": attrs.dep(providers = [ConfigurationInfo]), + "local_enabled": attrs.bool(default = True), + "remote_enabled": attrs.bool(default = True), + }, +) + +def _host_cpu_configuration() -> str: + arch = host_info().arch + if arch.is_aarch64: + return "prelude//cpu:arm64" + elif arch.is_arm: + return "prelude//cpu:arm32" + elif arch.is_i386: + return "prelude//cpu:x86_32" + else: + return "prelude//cpu:x86_64" + +def _host_os_configuration() -> str: + os = host_info().os + if os.is_macos: + return "prelude//os:macos" + elif os.is_windows: + return "prelude//os:windows" + else: + return "prelude//os:linux" + +host_configuration = struct( + cpu = _host_cpu_configuration(), + os = _host_os_configuration(), +) diff --git a/toolchains/haskell.bzl b/toolchains/haskell.bzl new file mode 100644 index 0000000..39e8bdc --- /dev/null +++ b/toolchains/haskell.bzl @@ -0,0 +1,727 @@ +# toolchains/haskell.bzl +# +# Haskell toolchain and rules using GHC from Nix. +# +# Uses ghcWithPackages from the Nix devshell, which includes all +# dependencies. The bin/ghc wrapper filters Mercury-specific flags +# that stock GHC doesn't understand. +# +# Paths are read from .buckconfig.local [haskell] section. +# +# Rules: +# haskell_toolchain - toolchain definition +# haskell_library - compile to .hi/.o with HaskellLibraryInfo +# haskell_binary - executable from sources + deps +# haskell_c_library - FFI exports callable from C/C++ +# haskell_ffi_binary - Haskell calling C/C++ via FFI +# haskell_script - single-file scripts +# haskell_test - test executable + +# NOTE: Must use upstream @prelude types for HaskellToolchainInfo since prelude +# haskell_binary rule expects that provider. Our custom rules (haskell_script, +# etc.) don't use the toolchain provider - they read config directly. +load("@prelude//haskell:toolchain.bzl", "HaskellToolchainInfo", "HaskellPlatformInfo") + +# ═══════════════════════════════════════════════════════════════════════════════ +# CONFIGURATION +# ═══════════════════════════════════════════════════════════════════════════════ + +# Mandatory compiler flags - applied to all Haskell compilation +# These are non-negotiable and cannot be overridden by targets +MANDATORY_GHC_FLAGS = [ + "-Wall", + "-Werror", +] + +def _get_ghc() -> str: + return read_root_config("haskell", "ghc", "bin/ghc") + +def _get_ghc_pkg() -> str: + return read_root_config("haskell", "ghc_pkg", "bin/ghc-pkg") + +def _get_package_db() -> str | None: + return read_root_config("haskell", "global_package_db", None) + +# ═══════════════════════════════════════════════════════════════════════════════ +# PROVIDERS +# ═══════════════════════════════════════════════════════════════════════════════ + +HaskellLibraryInfo = provider(fields = { + "package_name": provider_field(str), + "hi_dir": provider_field(Artifact | None, default = None), + "object_dir": provider_field(Artifact | None, default = None), + "stub_dir": provider_field(Artifact | None, default = None), + "hie_dir": provider_field(Artifact | None, default = None), # For IDE support + "objects": provider_field(list, default = []), + "modules": provider_field(list, default = []), # Source files for source-based deps +}) + +# For C consumers of Haskell FFI libraries +HaskellIncludeInfo = provider(fields = { + "include_dirs": provider_field(list, default = []), +}) + +# ═══════════════════════════════════════════════════════════════════════════════ +# TOOLCHAIN +# ═══════════════════════════════════════════════════════════════════════════════ + +def _haskell_toolchain_impl(ctx: AnalysisContext) -> list[Provider]: + """ + Haskell toolchain with paths from .buckconfig.local. + + Reads [haskell] section for: + ghc - GHC compiler + ghc_pkg - GHC package manager + haddock - Documentation generator + ghc_lib_dir - GHC library directory + global_package_db - Global package database + """ + ghc = read_root_config("haskell", "ghc", "bin/ghc") + ghc_pkg = read_root_config("haskell", "ghc_pkg", "bin/ghc-pkg") + haddock = read_root_config("haskell", "haddock", "bin/haddock") + + return [ + DefaultInfo(), + HaskellToolchainInfo( + compiler = ghc, + packager = ghc_pkg, + linker = ghc, + haddock = haddock, + compiler_flags = ctx.attrs.compiler_flags, + linker_flags = ctx.attrs.linker_flags, + ghci_script_template = ctx.attrs.ghci_script_template, + ghci_iserv_template = ctx.attrs.ghci_iserv_template, + script_template_processor = ctx.attrs.script_template_processor, + cache_links = True, + archive_contents = "normal", + support_expose_package = False, + ), + HaskellPlatformInfo( + name = "x86_64-linux", + ), + ] + +haskell_toolchain = rule( + impl = _haskell_toolchain_impl, + attrs = { + "compiler_flags": attrs.list(attrs.string(), default = []), + "linker_flags": attrs.list(attrs.string(), default = []), + "ghci_script_template": attrs.option(attrs.source(), default = None), + "ghci_iserv_template": attrs.option(attrs.source(), default = None), + "script_template_processor": attrs.option(attrs.exec_dep(providers = [RunInfo]), default = None), + }, + is_toolchain_rule = True, +) + +# ═══════════════════════════════════════════════════════════════════════════════ +# haskell_library - Compile to .hi/.o files +# ═══════════════════════════════════════════════════════════════════════════════ + +def _haskell_library_impl(ctx: AnalysisContext) -> list[Provider]: + """ + Build a Haskell library. + + Compiles sources to .hi interface files and .o object files. + For multi-source libraries, all sources are compiled together. + """ + ghc = _get_ghc() + package_db = _get_package_db() + + if not ctx.attrs.srcs: + return [ + DefaultInfo(), + HaskellLibraryInfo(package_name = ctx.attrs.name, modules = []), + ] + + # Output directories + obj_dir = ctx.actions.declare_output("objs", dir = True) + hi_dir = ctx.actions.declare_output("hi", dir = True) + stub_dir = ctx.actions.declare_output("stubs", dir = True) + + # Collect dependency hi directories for -i flag + dep_hi_dirs = [] + dep_objects = [] + for dep in ctx.attrs.deps: + if HaskellLibraryInfo in dep: + lib_info = dep[HaskellLibraryInfo] + if lib_info.hi_dir: + dep_hi_dirs.append(lib_info.hi_dir) + if lib_info.objects: + dep_objects.extend(lib_info.objects) + elif lib_info.object_dir: + dep_objects.append(lib_info.object_dir) + + # Build GHC command + cmd = cmd_args([ghc]) + cmd.add("-no-link") + cmd.add("-package-env=-") + + if package_db: + cmd.add("-package-db", package_db) + + cmd.add("-odir", obj_dir.as_output()) + cmd.add("-hidir", hi_dir.as_output()) + cmd.add("-stubdir", stub_dir.as_output()) + + # Generate .hie files for IDE support (go-to-definition, etc.) + hie_dir = ctx.actions.declare_output("hie", dir = True) + cmd.add("-fwrite-ide-info") + cmd.add("-hiedir", hie_dir.as_output()) + + # Mandatory flags (non-negotiable) + cmd.add(MANDATORY_GHC_FLAGS) + + # Language extensions + cmd.add("-XGHC2024") + for ext in ctx.attrs.language_extensions: + cmd.add("-X{}".format(ext)) + + # GHC options + cmd.add(ctx.attrs.ghc_options) + + # Packages + for pkg in ctx.attrs.packages: + cmd.add("-package", pkg) + + # Include paths for dependencies + for hi_d in dep_hi_dirs: + cmd.add(cmd_args("-i", hi_d, delimiter = "")) + + # Sources + cmd.add(ctx.attrs.srcs) + + ctx.actions.run(cmd, category = "haskell_compile", identifier = ctx.attrs.name) + + # Create static library from objects + lib = ctx.actions.declare_output("lib{}.a".format(ctx.attrs.name)) + ar_cmd = cmd_args( + "/bin/sh", "-c", + cmd_args("ar rcs", lib.as_output(), cmd_args(obj_dir, format = "{}/*.o"), delimiter = " "), + ) + ctx.actions.run(ar_cmd, category = "haskell_archive", identifier = ctx.attrs.name) + + return [ + DefaultInfo( + default_output = lib, + sub_targets = { + "hi": [DefaultInfo(default_outputs = [hi_dir])], + "stubs": [DefaultInfo(default_outputs = [stub_dir])], + "objects": [DefaultInfo(default_outputs = [obj_dir])], + "hie": [DefaultInfo(default_outputs = [hie_dir])], + }, + ), + HaskellLibraryInfo( + package_name = ctx.attrs.name, + hi_dir = hi_dir, + object_dir = lib, + stub_dir = stub_dir, + hie_dir = hie_dir, + objects = [], + modules = ctx.attrs.srcs, + ), + ] + +haskell_library = rule( + impl = _haskell_library_impl, + attrs = { + "srcs": attrs.list(attrs.source(), default = []), + "deps": attrs.list(attrs.dep(), default = []), + "packages": attrs.list(attrs.string(), default = []), + "ghc_options": attrs.list(attrs.string(), default = []), + "language_extensions": attrs.list(attrs.string(), default = []), + }, +) + +# ═══════════════════════════════════════════════════════════════════════════════ +# haskell_binary - Executable from sources + deps +# ═══════════════════════════════════════════════════════════════════════════════ + +def _haskell_binary_impl(ctx: AnalysisContext) -> list[Provider]: + """ + Build a Haskell executable. + """ + ghc = _get_ghc() + package_db = _get_package_db() + + out = ctx.actions.declare_output(ctx.attrs.name) + + # Output directories for intermediate files (keeps source tree clean) + obj_dir = ctx.actions.declare_output("objs", dir = True) + hi_dir = ctx.actions.declare_output("hi", dir = True) + + # Collect dependency info + dep_hi_dirs = [] + dep_libs = [] + dep_sources = [] # For source-based deps + for dep in ctx.attrs.deps: + if HaskellLibraryInfo in dep: + lib_info = dep[HaskellLibraryInfo] + if lib_info.hi_dir: + dep_hi_dirs.append(lib_info.hi_dir) + if lib_info.objects: + dep_libs.extend(lib_info.objects) + elif lib_info.object_dir: + dep_libs.append(lib_info.object_dir) + # Also collect source modules for source-based compilation + if lib_info.modules: + dep_sources.extend(lib_info.modules) + + cmd = cmd_args([ghc]) + cmd.add("-package-env=-") + cmd.add("-O2") + + # Output directories (intermediate .o/.hi files go to buck-out, not source tree) + cmd.add("-odir", obj_dir.as_output()) + cmd.add("-hidir", hi_dir.as_output()) + + # Generate .hie files for IDE support (go-to-definition, etc.) + hie_dir = ctx.actions.declare_output("hie", dir = True) + cmd.add("-fwrite-ide-info") + cmd.add("-hiedir", hie_dir.as_output()) + + + # Mandatory flags (non-negotiable) + cmd.add(MANDATORY_GHC_FLAGS) + cmd.add("-XGHC2024") + + if package_db: + cmd.add("-package-db", package_db) + + # Main module + if ctx.attrs.main: + cmd.add("-main-is", ctx.attrs.main) + + cmd.add("-o", out.as_output()) + + # Language extensions + for ext in ctx.attrs.language_extensions: + cmd.add("-X{}".format(ext)) + + # GHC options (includes compiler_flags for backwards compat) + cmd.add(ctx.attrs.ghc_options) + cmd.add(ctx.attrs.compiler_flags) + + # Packages + for pkg in ctx.attrs.packages: + cmd.add("-package", pkg) + + # Include paths for dependencies + for hi_d in dep_hi_dirs: + cmd.add(cmd_args("-i", hi_d, delimiter = "")) + + # Sources (our sources + source-based deps) + cmd.add(ctx.attrs.srcs) + cmd.add(dep_sources) + + # Link against compiled deps + cmd.add(dep_libs) + + ctx.actions.run(cmd, category = "ghc", identifier = ctx.attrs.name) + + return [ + DefaultInfo( + default_output = out, + sub_targets = { + "hi": [DefaultInfo(default_outputs = [hi_dir])], + "hie": [DefaultInfo(default_outputs = [hie_dir])], + }, + ), + RunInfo(args = cmd_args(out)), + ] + +haskell_binary = rule( + impl = _haskell_binary_impl, + attrs = { + "srcs": attrs.list(attrs.source()), + "deps": attrs.list(attrs.dep(), default = []), + "main": attrs.option(attrs.string(), default = None), + "packages": attrs.list(attrs.string(), default = []), + "ghc_options": attrs.list(attrs.string(), default = []), + "language_extensions": attrs.list(attrs.string(), default = []), + "compiler_flags": attrs.list(attrs.string(), default = []), # Backwards compat + }, +) + +# ═══════════════════════════════════════════════════════════════════════════════ +# haskell_c_library - FFI exports callable from C/C++ +# ═══════════════════════════════════════════════════════════════════════════════ + +def _haskell_c_library_impl(ctx: AnalysisContext) -> list[Provider]: + """ + Build a C-callable library from Haskell code with foreign exports. + + Produces: + 1. Static library with Haskell code + 2. Stub headers for C consumers + 3. HaskellIncludeInfo for include path propagation + + C code must call hs_init() before any Haskell functions. + """ + ghc = _get_ghc() + package_db = _get_package_db() + + stub_dir = ctx.actions.declare_output("stubs", dir = True) + lib = ctx.actions.declare_output("lib{}.a".format(ctx.attrs.name)) + + # Collect dependency hi directories + dep_hi_dirs = [] + for dep in ctx.attrs.deps: + if HaskellLibraryInfo in dep: + lib_info = dep[HaskellLibraryInfo] + if lib_info.hi_dir: + dep_hi_dirs.append(lib_info.hi_dir) + + # Compile each source individually to get proper stub generation + objects = [] + hi_files = [] + + for src in ctx.attrs.srcs: + src_path = src.short_path + if src_path.endswith(".hs"): + base_name = src_path.replace(".hs", "").split("/")[-1] + obj = ctx.actions.declare_output("{}.o".format(base_name)) + hi = ctx.actions.declare_output("{}.hi".format(base_name)) + + cmd = cmd_args([ghc]) + cmd.add("-c") + cmd.add("-package-env=-") + cmd.add("-fPIC") # Position independent for shared libs + + if package_db: + cmd.add("-package-db", package_db) + + cmd.add("-stubdir", stub_dir.as_output()) + cmd.add("-o", obj.as_output()) + cmd.add("-ohi", hi.as_output()) + + # Mandatory flags (non-negotiable) + cmd.add(MANDATORY_GHC_FLAGS) + + # Language extensions (ForeignFunctionInterface is required) + cmd.add("-XGHC2024") + cmd.add("-XForeignFunctionInterface") + for ext in ctx.attrs.language_extensions: + cmd.add("-X{}".format(ext)) + + cmd.add(ctx.attrs.ghc_options) + + # Dependencies + for hi_d in dep_hi_dirs: + cmd.add(cmd_args("-i", hi_d, delimiter = "")) + + for pkg in ctx.attrs.packages: + cmd.add("-package", pkg) + + cmd.add(src) + + ctx.actions.run(cmd, category = "haskell_compile", identifier = src_path) + objects.append(obj) + hi_files.append(hi) + + if not objects: + return [DefaultInfo()] + + # Create hi directory with symlinks + hi_dir = ctx.actions.declare_output("hi", dir = True) + hi_symlinks = {hi.basename: hi for hi in hi_files} + ctx.actions.symlinked_dir(hi_dir, hi_symlinks) + + # Archive objects + ar_cmd = cmd_args("ar", "rcs", lib.as_output()) + ar_cmd.add(objects) + ctx.actions.run(ar_cmd, category = "haskell_archive", identifier = ctx.attrs.name) + + return [ + DefaultInfo( + default_output = lib, + sub_targets = { + "stubs": [DefaultInfo(default_outputs = [stub_dir])], + "hi": [DefaultInfo(default_outputs = hi_files)], + "objects": [DefaultInfo(default_outputs = objects)], + }, + ), + HaskellIncludeInfo(include_dirs = [stub_dir]), + HaskellLibraryInfo( + package_name = ctx.attrs.name, + hi_dir = hi_dir, + object_dir = lib, + stub_dir = stub_dir, + objects = objects, + modules = [], + ), + ] + +haskell_c_library = rule( + impl = _haskell_c_library_impl, + attrs = { + "srcs": attrs.list(attrs.source(), default = []), + "deps": attrs.list(attrs.dep(), default = []), + "packages": attrs.list(attrs.string(), default = ["base"]), + "ghc_options": attrs.list(attrs.string(), default = []), + "language_extensions": attrs.list(attrs.string(), default = []), + }, + doc = """ + Build a C-callable static library from Haskell with foreign exports. + + Example Haskell: + {-# LANGUAGE ForeignFunctionInterface #-} + module FFI where + foreign export ccall hs_double :: CInt -> IO CInt + hs_double x = return (x * 2) + + Example C: + #include "HsFFI.h" + #include "FFI_stub.h" + int main(int argc, char *argv[]) { + hs_init(&argc, &argv); + int result = hs_double(21); + hs_exit(); + return 0; + } + """, +) + +# ═══════════════════════════════════════════════════════════════════════════════ +# haskell_ffi_binary - Haskell calling C/C++ via FFI +# ═══════════════════════════════════════════════════════════════════════════════ + +def _haskell_ffi_binary_impl(ctx: AnalysisContext) -> list[Provider]: + """ + Build a Haskell binary that calls C/C++ code via FFI. + + Steps: + 1. Compile C++ sources to .o files with clang + 2. Compile and link Haskell sources with GHC, including the C++ objects + + Supports external libraries via: + - extra_libs: library names to link (e.g., ["tokenizers_cpp", "sentencepiece"]) + - extra_lib_dirs: paths to search for libraries (can also be read from config) + - include_dirs: paths for C++ header includes (can also be read from config) + + Config integration: + - [slide] tokenizers_cpp_lib: library path for tokenizers-cpp + - [slide] tokenizers_cpp_include: include path for tokenizers-cpp + + GHC 9.12 Workaround: + Uses toolchains/scripts/ghc-pkg-id wrapper to translate -package flags + to -package-id flags, working around a GHC 9.12 bug where -package + doesn't expose packages correctly with ghcWithPackages. + """ + ghc = _get_ghc() + ghc_pkg = _get_ghc_pkg() + package_db = _get_package_db() + cxx = read_root_config("cxx", "cxx", "clang++") + + # Read additional paths from config (for Nix-provided libraries) + tokenizers_lib = read_root_config("slide", "tokenizers_cpp_lib", "") + tokenizers_include = read_root_config("slide", "tokenizers_cpp_include", "") + + # C++ stdlib paths for unwrapped clang + gcc_include = read_root_config("cxx", "gcc_include", "") + gcc_include_arch = read_root_config("cxx", "gcc_include_arch", "") + glibc_include = read_root_config("cxx", "glibc_include", "") + clang_resource_dir = read_root_config("cxx", "clang_resource_dir", "") + gcc_lib_base = read_root_config("cxx", "gcc_lib_base", "") + + out = ctx.actions.declare_output(ctx.attrs.name) + + # Step 1: Compile C++ sources + cxx_compile_flags = ["-std=c++17", "-O2", "-fPIC", "-c"] + + if gcc_include: + cxx_compile_flags.extend(["-isystem", gcc_include]) + if gcc_include_arch: + cxx_compile_flags.extend(["-isystem", gcc_include_arch]) + if glibc_include: + cxx_compile_flags.extend(["-isystem", glibc_include]) + if clang_resource_dir: + cxx_compile_flags.extend(["-resource-dir=" + clang_resource_dir]) + + cxx_compile_flags.extend(["-I", "."]) + + # Add user-specified include directories + for inc_dir in ctx.attrs.include_dirs: + cxx_compile_flags.extend(["-I", inc_dir]) + + # Add config-provided include directories (from Nix) + if tokenizers_include: + cxx_compile_flags.extend(["-I", tokenizers_include]) + + cxx_objects = [] + for src in ctx.attrs.cxx_srcs: + obj_name = src.short_path.replace(".cpp", ".o").replace(".c", ".o") + obj = ctx.actions.declare_output(obj_name) + + cmd = cmd_args([cxx] + cxx_compile_flags + ["-o", obj.as_output(), src]) + ctx.actions.run(cmd, category = "cxx_compile", identifier = src.short_path) + cxx_objects.append(obj) + + # Step 2: Compile Haskell and link + # Output directories for intermediate files (keeps source tree clean) + obj_dir = ctx.actions.declare_output("hs_objs", dir = True) + hi_dir = ctx.actions.declare_output("hs_hi", dir = True) + + # Use ghc-pkg-id wrapper script to translate -package to -package-id + # This works around GHC 9.12 bug where -package doesn't expose packages + ghc_wrapper = "toolchains/scripts/ghc-pkg-id" + ghc_cmd = cmd_args([ghc_wrapper, ghc, ghc_pkg]) + ghc_cmd.add("-O2", "-threaded") + # NOTE: Don't use -package-env=- or explicit -package-db as it breaks + # package resolution in GHC 9.12 with ghcWithPackages + # The ghcWithPackages wrapper sets up the package db correctly via -B flag + + # Output directories (intermediate .o/.hi files go to buck-out, not source tree) + ghc_cmd.add("-odir", obj_dir.as_output()) + ghc_cmd.add("-hidir", hi_dir.as_output()) + + # Mandatory flags (non-negotiable) + ghc_cmd.add(MANDATORY_GHC_FLAGS) + ghc_cmd.add("-XGHC2024") + + # GCC library path for libstdc++ + if gcc_lib_base: + ghc_cmd.add("-optl", "-L" + gcc_lib_base) + + # Extra library directories (e.g., tokenizers-cpp) + for lib_dir in ctx.attrs.extra_lib_dirs: + ghc_cmd.add("-optl", "-L" + lib_dir) + ghc_cmd.add("-optl", "-Wl,-rpath," + lib_dir) + + # Config-provided library directories (from Nix) + if tokenizers_lib: + ghc_cmd.add("-optl", "-L" + tokenizers_lib) + ghc_cmd.add("-optl", "-Wl,-rpath," + tokenizers_lib) + + # Link against stdc++ + ghc_cmd.add("-lstdc++") + + # Link against extra libraries + for lib in ctx.attrs.extra_libs: + ghc_cmd.add("-l" + lib) + + # Extra linker flags + for flag in ctx.attrs.linker_flags: + ghc_cmd.add("-optl", flag) + + ghc_cmd.add("-o", out.as_output()) + + # Packages + for pkg in ctx.attrs.packages: + ghc_cmd.add("-package", pkg) + + # Language extensions + for ext in ctx.attrs.language_extensions: + ghc_cmd.add("-X{}".format(ext)) + + # GHC options + ghc_cmd.add(ctx.attrs.ghc_options) + ghc_cmd.add(ctx.attrs.compiler_flags) + + # Include directories for Haskell FFI (cbits) + for inc_dir in ctx.attrs.include_dirs: + ghc_cmd.add("-I" + inc_dir) + + # Config-provided include directories (from Nix) + if tokenizers_include: + ghc_cmd.add("-I" + tokenizers_include) + + ghc_cmd.add(ctx.attrs.hs_srcs) + ghc_cmd.add(cxx_objects) + + ctx.actions.run(ghc_cmd, category = "ghc_link", identifier = ctx.attrs.name) + + return [ + DefaultInfo(default_output = out), + RunInfo(args = [out]), + ] + +haskell_ffi_binary = rule( + impl = _haskell_ffi_binary_impl, + attrs = { + "hs_srcs": attrs.list(attrs.source()), + "cxx_srcs": attrs.list(attrs.source(), default = []), + "cxx_headers": attrs.list(attrs.source(), default = []), + "deps": attrs.list(attrs.dep(), default = []), + "packages": attrs.list(attrs.string(), default = []), + "ghc_options": attrs.list(attrs.string(), default = []), + "compiler_flags": attrs.list(attrs.string(), default = []), + "language_extensions": attrs.list(attrs.string(), default = []), + "extra_libs": attrs.list(attrs.string(), default = []), + "extra_lib_dirs": attrs.list(attrs.string(), default = []), + "include_dirs": attrs.list(attrs.string(), default = []), + "linker_flags": attrs.list(attrs.string(), default = []), + }, +) + +# ═══════════════════════════════════════════════════════════════════════════════ +# haskell_script - Single-file scripts +# ═══════════════════════════════════════════════════════════════════════════════ + +def _haskell_script_impl(ctx: AnalysisContext) -> list[Provider]: + """ + Build a single-file Haskell script. + + Uses ghcWithPackages from Nix for external deps. + """ + ghc = _get_ghc() + + out = ctx.actions.declare_output(ctx.attrs.name) + + # Output directories for intermediate files (keeps source tree clean) + obj_dir = ctx.actions.declare_output("objs", dir = True) + hi_dir = ctx.actions.declare_output("hi", dir = True) + + cmd = cmd_args([ghc]) + + # Output directories (intermediate .o/.hi files go to buck-out, not source tree) + cmd.add("-odir", obj_dir.as_output()) + cmd.add("-hidir", hi_dir.as_output()) + + # Mandatory flags (non-negotiable) + cmd.add(MANDATORY_GHC_FLAGS) + cmd.add("-XGHC2024") + + cmd.add(ctx.attrs.compiler_flags) + cmd.add("-o", out.as_output()) + + for include_path in ctx.attrs.include_paths: + cmd.add("-i" + include_path) + + for pkg in ctx.attrs.packages: + cmd.add("-package", pkg) + + cmd.add(ctx.attrs.srcs) + + ctx.actions.run(cmd, category = "haskell_script", identifier = ctx.attrs.name) + + return [ + DefaultInfo(default_output = out), + RunInfo(args = [out]), + ] + +haskell_script = rule( + impl = _haskell_script_impl, + attrs = { + "srcs": attrs.list(attrs.source()), + "include_paths": attrs.list(attrs.string(), default = []), + "compiler_flags": attrs.list(attrs.string(), default = []), + "packages": attrs.list(attrs.string(), default = []), + }, +) + +# ═══════════════════════════════════════════════════════════════════════════════ +# haskell_test - Test executable (same as binary) +# ═══════════════════════════════════════════════════════════════════════════════ + +haskell_test = rule( + impl = _haskell_binary_impl, + attrs = { + "srcs": attrs.list(attrs.source()), + "deps": attrs.list(attrs.dep(), default = []), + "main": attrs.option(attrs.string(), default = None), + "packages": attrs.list(attrs.string(), default = ["base"]), + "ghc_options": attrs.list(attrs.string(), default = []), + "language_extensions": attrs.list(attrs.string(), default = []), + "compiler_flags": attrs.list(attrs.string(), default = []), + }, +) diff --git a/toolchains/scripts/ghc-pkg-id b/toolchains/scripts/ghc-pkg-id new file mode 100755 index 0000000..c8d9ad3 --- /dev/null +++ b/toolchains/scripts/ghc-pkg-id @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# ghc-pkg-id - GHC wrapper that translates -package to -package-id +# +# GHC 9.12 with ghcWithPackages has a bug where -package NAME doesn't expose +# packages. However, -package-id UNIT_ID works correctly. +# +# This wrapper intercepts -package arguments, resolves them to unit IDs using +# ghc-pkg, and passes -package-id to the underlying GHC. +# +# Usage: ghc-pkg-id [ghc-args...] + +set -euo pipefail + +GHC="$1" +GHC_PKG="$2" +shift 2 + +# Build translated args +args=() + +while [[ $# -gt 0 ]]; do + case "$1" in + -package) + # Next arg is the package name + if [[ $# -lt 2 ]]; then + echo "Error: -package requires an argument" >&2 + exit 1 + fi + pkg_name="$2" + # Get the unit ID for this package + pkg_id=$("$GHC_PKG" field "$pkg_name" id --simple-output 2>/dev/null || true) + if [[ -n "$pkg_id" ]]; then + args+=("-package-id" "$pkg_id") + else + # Fall back to -package if we can't resolve + args+=("-package" "$pkg_name") + fi + shift 2 + ;; + -package=*) + # Handle -package=name form + pkg_name="${1#-package=}" + pkg_id=$("$GHC_PKG" field "$pkg_name" id --simple-output 2>/dev/null || true) + if [[ -n "$pkg_id" ]]; then + args+=("-package-id" "$pkg_id") + else + args+=("$1") + fi + shift + ;; + *) + args+=("$1") + shift + ;; + esac +done + +# Execute GHC with translated arguments +exec "$GHC" "${args[@]}"