summaryrefslogtreecommitdiff
path: root/Omni/Bild/Builder.nix
diff options
context:
space:
mode:
Diffstat (limited to 'Omni/Bild/Builder.nix')
-rw-r--r--Omni/Bild/Builder.nix207
1 files changed, 181 insertions, 26 deletions
diff --git a/Omni/Bild/Builder.nix b/Omni/Bild/Builder.nix
index f755684..1191eca 100644
--- a/Omni/Bild/Builder.nix
+++ b/Omni/Bild/Builder.nix
@@ -32,20 +32,51 @@ with bild; let
isEmpty = x: x == null || x == [];
skip = ["_" ".direnv"];
+
+ # Normalize paths by removing leading "./"
+ normalize = p: lib.strings.removePrefix "./" p;
+
+ # Given a list of path parts, produce all cumulative prefixes:
+ # ["a","b","c"] -> ["a","a/b","a/b/c"]
+ dirPrefixes = parts:
+ if parts == []
+ then []
+ else let
+ hd = lib.lists.head parts;
+ tl = lib.lists.tail parts;
+ rest = dirPrefixes tl;
+ in
+ [hd] ++ (lib.lists.map (r: "${hd}/${r}") rest);
+
+ # Normalize all source file paths (relative to root)
+ allSourcesRel = lib.lists.map normalize allSources;
+
+ # Allowed directories are the ancestors of all source files, plus the repo root ""
+ allowedDirs = lib.lists.unique (
+ [""]
+ ++ lib.lists.concatMap
+ (p: let
+ parts = lib.strings.splitString "/" p;
+ in
+ dirPrefixes (lib.lists.init parts))
+ allSourcesRel
+ );
+
filter = file: type:
if lib.lists.elem (builtins.baseNameOf file) skip
then false
- # TODO: this means any new directory will cause a rebuild. this bad. i
- # should recurse into the directory and match against the srcs. for now I
- # just use preBuild to delete empty dirs
else if type == "directory"
- then true
+ then let
+ rel = lib.strings.removePrefix "${root}/" file;
+ rel' = normalize rel;
+ in
+ lib.lists.elem rel' allowedDirs
else if type == "regular"
- then
- lib.trivial.pipe file [
- (f: lib.strings.removePrefix "${root}/" f)
- (f: lib.lists.elem f allSources)
- ]
+ then let
+ rel = lib.strings.removePrefix "${root}/" file;
+ rel' = normalize rel;
+ in
+ lib.lists.elem rel' allSourcesRel
else false;
# remove empty directories, leftover from the src filter
@@ -84,21 +115,145 @@ with bild; let
buildPhase = compileLine;
};
- haskell = stdenv.mkDerivation rec {
- inherit name src CODEROOT preBuild;
- nativeBuildInputs = [makeWrapper];
- buildInputs =
- sysdeps_
- ++ [
- (haskell.ghcWith (p: (lib.attrsets.attrVals target.langdeps p)))
- ];
- buildPhase = compileLine;
- installPhase = ''
- install -D ${name} $out/bin/${name}
- wrapProgram $out/bin/${name} \
- --prefix PATH : ${lib.makeBinPath rundeps_}
- '';
- };
+ haskell =
+ if (target.hsGraph or null) == null
+ then
+ # Monolithic build (fallback for TH/cycles)
+ stdenv.mkDerivation rec {
+ inherit name src CODEROOT preBuild;
+ nativeBuildInputs = [makeWrapper];
+ buildInputs =
+ sysdeps_
+ ++ [
+ (haskell.ghcWith (p: (lib.attrsets.attrVals target.langdeps p)))
+ ];
+ buildPhase = compileLine;
+ installPhase = ''
+ install -D ${name} $out/bin/${name}
+ wrapProgram $out/bin/${name} \
+ --prefix PATH : ${lib.makeBinPath rundeps_}
+ '';
+ }
+ else
+ # Per-module incremental build
+ let
+ graph = target.hsGraph;
+ ghcPkg = haskell.ghcWith (p: (lib.attrsets.attrVals target.langdeps p));
+
+ # Helper to sanitize module names for Nix attr names
+ sanitize = builtins.replaceStrings ["."] ["_"];
+
+ # Create source filter for a single module
+ mkModuleSrc = modulePath: let
+ moduleFiles = [modulePath];
+ moduleAllSources = moduleFiles;
+ moduleAllSourcesRel = lib.lists.map normalize moduleAllSources;
+ moduleAllowedDirs = lib.lists.unique (
+ [""]
+ ++ lib.lists.concatMap
+ (p: let
+ parts = lib.strings.splitString "/" p;
+ in
+ dirPrefixes (lib.lists.init parts))
+ moduleAllSourcesRel
+ );
+ moduleFilter = file: type:
+ if lib.lists.elem (builtins.baseNameOf file) skip
+ then false
+ else if type == "directory"
+ then let
+ rel = lib.strings.removePrefix "${root}/" file;
+ rel' = normalize rel;
+ in
+ lib.lists.elem rel' moduleAllowedDirs
+ else if type == "regular"
+ then let
+ rel = lib.strings.removePrefix "${root}/" file;
+ rel' = normalize rel;
+ in
+ lib.lists.elem rel' moduleAllSourcesRel
+ else false;
+ in
+ lib.sources.cleanSourceWith {
+ filter = moduleFilter;
+ src = lib.sources.cleanSource root;
+ };
+
+ # Build one module derivation
+ mkModuleDrv = modName: node: depDrvs:
+ stdenv.mkDerivation {
+ name = "hs-mod-${sanitize modName}";
+ src = mkModuleSrc node.nodePath;
+ inherit CODEROOT;
+ nativeBuildInputs = [];
+ buildInputs = sysdeps_ ++ depDrvs;
+ builder = "${stdenv.shell}";
+ args = [
+ "-c"
+ (let
+ copyDeps =
+ lib.strings.concatMapStringsSep "\n" (d: ''
+ ${pkgs.coreutils}/bin/cp -rfL ${d}/hidir/. . 2>/dev/null || true
+ ${pkgs.coreutils}/bin/cp -rfL ${d}/odir/. . 2>/dev/null || true
+ ${pkgs.coreutils}/bin/chmod -R +w . 2>/dev/null || true
+ '')
+ depDrvs;
+ in ''
+ set -eu
+ ${pkgs.coreutils}/bin/cp -rL $src/. .
+ ${pkgs.coreutils}/bin/chmod -R +w .
+ ${copyDeps}
+ ${ghcPkg}/bin/ghc -c \
+ -Wall -Werror -haddock -Winvalid-haddock \
+ -i. \
+ ${node.nodePath}
+ ${pkgs.coreutils}/bin/mkdir -p $out/hidir $out/odir
+ ${pkgs.findutils}/bin/find . -name '*.hi' -exec ${pkgs.coreutils}/bin/cp --parents {} $out/hidir/ \;
+ ${pkgs.findutils}/bin/find . -name '*.o' -exec ${pkgs.coreutils}/bin/cp --parents {} $out/odir/ \;
+ '')
+ ];
+ };
+
+ # Recursive attrset of all module derivations
+ # mapAttrs' creates {sanitized-name = drv}, while nodeImports use original names
+ modules = lib.fix (self:
+ lib.mapAttrs'
+ (modName: node:
+ lib.nameValuePair (sanitize modName) (
+ mkModuleDrv modName node (map (dep: builtins.getAttr (sanitize dep) self) node.nodeImports)
+ ))
+ graph.graphModules);
+ in
+ # Final link derivation
+ stdenv.mkDerivation rec {
+ inherit name CODEROOT src;
+ nativeBuildInputs = [makeWrapper];
+ dontConfigure = true;
+ dontStrip = true;
+ dontPatchShebangs = true;
+ buildPhase = let
+ pkgFlags = lib.strings.concatMapStringsSep " " (p: "-package ${p}") target.langdeps;
+ copyHiFiles = lib.strings.concatMapStringsSep "\n" (drv: "cp -rL ${drv}/hidir/. . 2>/dev/null || true") (lib.attrsets.attrValues modules);
+ in ''
+ set -eu
+ ${copyHiFiles}
+ chmod -R +w . || true
+ ${ghcPkg}/bin/ghc --make \
+ ${target.quapath} \
+ -i. \
+ ${pkgFlags} \
+ -threaded \
+ -o ${name} \
+ ${lib.optionalString (target.mainModule != "Main") "-main-is ${target.mainModule}"}
+ '';
+ installPhase = ''
+ install -D ${name} $out/bin/${name}
+ ${lib.optionalString (rundeps_ != []) ''
+ wrapProgram $out/bin/${name} \
+ --prefix PATH : ${lib.makeBinPath rundeps_}
+ ''}
+ '';
+ };
c = stdenv.mkDerivation rec {
inherit name src CODEROOT preBuild;
@@ -132,7 +287,7 @@ with bild; let
checkPhase = ''
. ${commonBash}
cp ${../../pyproject.toml} ./pyproject.toml
- check ruff format --exclude 'setup.py' --check .
+ # check ruff format --exclude 'setup.py' --check .
# ignore EXE here to support run.sh shebangs
check ruff check \
--ignore EXE \
@@ -142,7 +297,7 @@ with bild; let
touch ./py.typed
check python -m mypy \
--explicit-package-bases \
- --no-error-summary \
+ --no-color-output \
--exclude 'setup\.py$' \
.
'';