1
1
mirror of https://github.com/nmattia/snack.git synced 2024-11-28 12:27:41 +03:00
snack/snack-lib/build.nix
2018-09-25 16:53:35 -04:00

154 lines
4.8 KiB
Nix

{ runCommand
, lib
, callPackage
, stdenv
, rsync
, symlinkJoin
}:
with (callPackage ./modules.nix {});
with (callPackage ./lib.nix {});
with (callPackage ./module-spec.nix {});
rec {
# Returns an attribute set where the keys are all the built module names and
# the values are the paths to the object files.
# mainModSpec: a "main" module
buildMain = ghcWith: mainModSpec:
buildModulesRec ghcWith
# XXX: the main modules need special handling regarding the object name
{ "${mainModSpec.moduleName}" =
"${buildModule ghcWith mainModSpec}/Main.o";}
mainModSpec.moduleImports;
# returns a attrset where the keys are the module names and the values are
# the modules' object file path
buildLibrary = ghcWith: modSpecs:
buildModulesRec ghcWith {} modSpecs;
linkMainModule = ghcWith: mod: # main module
let
objAttrs = buildMain ghcWith mod;
objList = lib.attrsets.mapAttrsToList (x: y: y) objAttrs;
deps = allTransitiveDeps [mod];
ghc = ghcWith deps;
ghcOptsArgs = lib.strings.escapeShellArgs mod.moduleGhcOpts;
packageList = map (p: "-package ${p}") deps;
relExePath = "bin/${lib.strings.toLower mod.moduleName}";
drv = runCommand "linker" {}
''
mkdir -p $out/bin
${ghc}/bin/ghc \
${lib.strings.escapeShellArgs packageList} \
${lib.strings.escapeShellArgs objList} \
${ghcOptsArgs} \
-o $out/${relExePath}
'';
in
{
out = drv;
relExePath = relExePath;
};
# Build the given modules (recursively) using the given accumulator to keep
# track of which modules have been built already
# XXX: doesn't work if several modules in the DAG have the same name
buildModulesRec = ghcWith: empty: modSpecs:
foldDAG
{ f = mod:
{ "${mod.moduleName}" =
"${buildModule ghcWith mod}/${moduleToObject mod.moduleName}";
};
elemLabel = mod: mod.moduleName;
elemChildren = mod: mod.moduleImports;
reduce = a: b: a // b;
empty = empty;
}
modSpecs;
buildModule = ghcWith: modSpec:
let
ghc = ghcWith deps;
deps = allTransitiveDeps [modSpec];
exts = modSpec.moduleExtensions;
ghcOpts = modSpec.moduleGhcOpts ++ (map (x: "-X${x}") exts);
ghcOptsArgs = lib.strings.escapeShellArgs ghcOpts;
objectName = modSpec.moduleName;
builtDeps = map (buildModule ghcWith) (allTransitiveImports [modSpec]);
depsDirs = map (x: x + "/") builtDeps;
base = modSpec.moduleBase;
makeSymtree =
if lib.lists.length depsDirs >= 1
# TODO: symlink instead of copy
then "rsync -r ${lib.strings.escapeShellArgs depsDirs} ."
else "";
makeSymModule =
# TODO: symlink instead of copy
"rsync -r ${singleOutModule base modSpec.moduleName}/ .";
pred = file: path: type:
let
topLevel = (builtins.toString base) + "/";
actual = (lib.strings.removePrefix topLevel path);
expected = file;
in
(expected == actual) ||
(type == "directory" && (lib.strings.hasPrefix actual expected));
extraFiles = builtins.filterSource
(p: t:
lib.lists.length
(
let
topLevel = (builtins.toString base) + "/";
actual = lib.strings.removePrefix topLevel p;
in
lib.filter (expected:
(expected == actual) ||
(t == "directory" && (lib.strings.hasPrefix actual expected))
)
modSpec.moduleFiles
) >= 1
) base;
in stdenv.mkDerivation
{ name = objectName;
src = symlinkJoin
{ name = "extra-files";
paths = [ extraFiles ] ++ modSpec.moduleDirectories;
};
phases =
[ "unpackPhase" "buildPhase" ];
imports = map (mmm: mmm.moduleName) modSpec.moduleImports;
buildPhase =
''
echo "Building module ${modSpec.moduleName}"
echo "Local imports are:"
for foo in $imports; do
echo " - $foo"
done
mkdir -p $out
echo "Creating dependencies symtree for module ${modSpec.moduleName}"
${makeSymtree}
echo "Creating module symlink for module ${modSpec.moduleName}"
${makeSymModule}
echo "Compiling module ${modSpec.moduleName}"
# Set a tmpdir we have control over, otherwise GHC fails, not sure why
mkdir -p tmp
ghc -tmpdir tmp/ ${moduleToFile modSpec.moduleName} -c \
-outputdir $out \
${ghcOptsArgs} \
2>&1
ls $out
echo "Done building module ${modSpec.moduleName}"
'';
buildInputs =
[ ghc
rsync
];
};
}