1
1
mirror of https://github.com/nmattia/snack.git synced 2024-09-21 08:37:09 +03:00

snack-lib: make the dependencies explicit

This commit is contained in:
zimbatm 2018-06-08 12:13:29 +01:00
parent 135f8cb578
commit 4561519d8e

View File

@ -6,7 +6,14 @@
# TODO: use --make everywhere ?!? NOTE: this is tricky because GHC flags # TODO: use --make everywhere ?!? NOTE: this is tricky because GHC flags
# change: when a module is built with its dependencies, the flags for the # change: when a module is built with its dependencies, the flags for the
# dependencies change as well, which causes them to be recompiled # dependencies change as well, which causes them to be recompiled
{ pkgs }: { lib
, haskellPackages
, makeWrapper
, rsync
, stdenv
, symlinkJoin
, writeScript
}:
let let
# Takes a (string) filepath and creates a derivation for that file (and for # Takes a (string) filepath and creates a derivation for that file (and for
# that file only) # that file only)
@ -15,20 +22,20 @@ let
basePrefix = (builtins.toString base) + "/"; basePrefix = (builtins.toString base) + "/";
pred = file: path: type: pred = file: path: type:
let let
actual = (pkgs.lib.strings.removePrefix basePrefix path); actual = (lib.strings.removePrefix basePrefix path);
expected = file; expected = file;
in in
(expected == actual) || (expected == actual) ||
(type == "directory" && (pkgs.lib.strings.hasPrefix actual expected)); (type == "directory" && (lib.strings.hasPrefix actual expected));
mod = fileToModule file; mod = fileToModule file;
# TODO: even though we're doing a lot of cleaning, there's sitll some # TODO: even though we're doing a lot of cleaning, there's sitll some
# 'does-file-exist' happening # 'does-file-exist' happening
src0 = pkgs.lib.cleanSource base; src0 = lib.cleanSource base;
in pkgs.stdenv.mkDerivation { in stdenv.mkDerivation {
name = mod; name = mod;
src = pkgs.lib.cleanSourceWith { filter = (pred file); src = src0; }; src = lib.cleanSourceWith { filter = (pred file); src = src0; };
builder = pkgs.writeScript (mod + "-builder") builder = writeScript (mod + "-builder")
# TODO: make sure the file actually exists and that there's only one # TODO: make sure the file actually exists and that there's only one
'' ''
echo "Singling out module ${mod} (file is ${file})" echo "Singling out module ${mod} (file is ${file})"
@ -52,14 +59,14 @@ let
}; };
moduleToFile = mod: moduleToFile = mod:
(pkgs.lib.strings.replaceChars ["."] ["/"] mod) + ".hs"; (lib.strings.replaceChars ["."] ["/"] mod) + ".hs";
moduleToObject = mod: moduleToObject = mod:
(pkgs.lib.strings.replaceChars ["."] ["/"] mod) + ".o"; (lib.strings.replaceChars ["."] ["/"] mod) + ".o";
fileToModule = file: fileToModule = file:
pkgs.lib.strings.removeSuffix ".hs" lib.strings.removeSuffix ".hs"
(pkgs.lib.strings.replaceChars ["/"] ["."] file); (lib.strings.replaceChars ["/"] ["."] file);
singleOutModule = base: mod: singleOut base (moduleToFile mod); singleOutModule = base: mod: singleOut base (moduleToFile mod);
@ -69,7 +76,7 @@ let
# Create a module spec by following the dependencies. This assumes that the # Create a module spec by following the dependencies. This assumes that the
# specified module is a "Main" module. # specified module is a "Main" module.
makeModuleSpecRec = base: filesByModuleName: dirsByModuleName: makeModuleSpecRec = base: filesByModuleName: dirsByModuleName:
pkgs.lib.fix lib.fix
(f: isMain: modName: (f: isMain: modName:
makeModuleSpec makeModuleSpec
modName modName
@ -85,9 +92,9 @@ let
builtDeps = map (buildModule ghc base) mod.moduleDependencies; builtDeps = map (buildModule ghc base) mod.moduleDependencies;
depsDirs = map (x: x + "/") builtDeps; depsDirs = map (x: x + "/") builtDeps;
makeSymtree = makeSymtree =
if pkgs.lib.lists.length depsDirs >= 1 if lib.lists.length depsDirs >= 1
# TODO: symlink instead of copy # TODO: symlink instead of copy
then "rsync -r ${pkgs.lib.strings.escapeShellArgs depsDirs} ." then "rsync -r ${lib.strings.escapeShellArgs depsDirs} ."
else ""; else "";
makeSymModule = makeSymModule =
# TODO: symlink instead of copy # TODO: symlink instead of copy
@ -95,32 +102,32 @@ let
pred = file: path: type: pred = file: path: type:
let let
topLevel = (builtins.toString base) + "/"; topLevel = (builtins.toString base) + "/";
actual = (pkgs.lib.strings.removePrefix topLevel path); actual = (lib.strings.removePrefix topLevel path);
expected = file; expected = file;
in in
(expected == actual) || (expected == actual) ||
(type == "directory" && (pkgs.lib.strings.hasPrefix actual expected)); (type == "directory" && (lib.strings.hasPrefix actual expected));
extraFiles = builtins.filterSource extraFiles = builtins.filterSource
(p: t: (p: t:
pkgs.lib.lists.length lib.lists.length
( (
let let
topLevel = (builtins.toString base) + "/"; topLevel = (builtins.toString base) + "/";
actual = pkgs.lib.strings.removePrefix topLevel p; actual = lib.strings.removePrefix topLevel p;
in in
pkgs.lib.filter (expected: lib.filter (expected:
(expected == actual) || (expected == actual) ||
(t == "directory" && (pkgs.lib.strings.hasPrefix actual expected)) (t == "directory" && (lib.strings.hasPrefix actual expected))
) )
mod.moduleFiles mod.moduleFiles
) >= 1 ) >= 1
) base; ) base;
in pkgs.stdenv.mkDerivation in stdenv.mkDerivation
{ name = objectName; { name = objectName;
src = pkgs.symlinkJoin src = symlinkJoin
{ name = "extra-files"; { name = "extra-files";
paths = [ extraFiles ] ++ mod.moduleDirectories; paths = [ extraFiles ] ++ mod.moduleDirectories;
}; };
@ -145,28 +152,28 @@ let
buildInputs = buildInputs =
[ ghc [ ghc
pkgs.rsync rsync
]; ];
}; };
# Generate a list of haskell module names needed by the haskell file, # Generate a list of haskell module names needed by the haskell file,
# excluding modules that are not present in this project/base # excluding modules that are not present in this project/base
listModuleDependencies = base: modName: listModuleDependencies = base: modName:
pkgs.lib.filter lib.filter
(doesModuleExist base) (doesModuleExist base)
(builtins.fromJSON (builtins.fromJSON
(builtins.readFile (listAllModuleDependenciesJSON base modName)) (builtins.readFile (listAllModuleDependenciesJSON base modName))
); );
doesFileExist = base: filename: doesFileExist = base: filename:
pkgs.lib.lists.elem filename (listFilesInDir base); lib.lists.elem filename (listFilesInDir base);
listFilesInDir = dir: listFilesInDir = dir:
let let
go = dir: dirName: go = dir: dirName:
pkgs.lib.lists.concatLists lib.lists.concatLists
( (
pkgs.lib.attrsets.mapAttrsToList lib.attrsets.mapAttrsToList
(path: ty: (path: ty:
if ty == "directory" if ty == "directory"
then then
@ -184,10 +191,10 @@ let
# Lists all module dependencies, not limited to modules existing in this # Lists all module dependencies, not limited to modules existing in this
# project # project
listAllModuleDependenciesJSON = base: modName: listAllModuleDependenciesJSON = base: modName:
pkgs.stdenv.mkDerivation stdenv.mkDerivation
{ name = "module-deps"; { name = "module-deps";
src = null; src = null;
builder = pkgs.writeScript "dependencies-json" builder = writeScript "dependencies-json"
'' ''
echo "preparing dependencies" echo "preparing dependencies"
source $stdenv/setup source $stdenv/setup
@ -223,33 +230,33 @@ let
else moduleToObject x.moduleName; else moduleToObject x.moduleName;
attrs1 = f attrs0 mod; attrs1 = f attrs0 mod;
f = acc: elem: f = acc: elem:
if pkgs.lib.attrsets.hasAttr elem.moduleName acc if lib.attrsets.hasAttr elem.moduleName acc
then acc then acc
else acc // else acc //
{ "${elem.moduleName}" = { "${elem.moduleName}" =
"${buildModule ghc base elem}/${objectName elem}"; "${buildModule ghc base elem}/${objectName elem}";
}; };
in in
pkgs.lib.lists.foldl f attrs1 mod.moduleDependencies; lib.lists.foldl f attrs1 mod.moduleDependencies;
in go mod' {}; in go mod' {};
# TODO: it's sad that we pass ghcWithDeps + dependencies # TODO: it's sad that we pass ghcWithDeps + dependencies
linkModuleObjects = ghc: ghcOpts: dependencies: base: mod: linkModuleObjects = ghc: ghcOpts: dependencies: base: mod:
let let
objAttrs = flattenModuleObjects ghc base mod; objAttrs = flattenModuleObjects ghc base mod;
objList = pkgs.lib.attrsets.mapAttrsToList (x: y: y) objAttrs; objList = lib.attrsets.mapAttrsToList (x: y: y) objAttrs;
ghcOptsArgs = pkgs.lib.strings.escapeShellArgs ghcOpts; ghcOptsArgs = lib.strings.escapeShellArgs ghcOpts;
packageList = map (p: "-package ${p}") dependencies; packageList = map (p: "-package ${p}") dependencies;
in pkgs.stdenv.mkDerivation in stdenv.mkDerivation
{ name = "linker"; { name = "linker";
src = null; src = null;
builder = pkgs.writeScript "linker-builder" builder = writeScript "linker-builder"
'' ''
source $stdenv/setup source $stdenv/setup
mkdir -p $out mkdir -p $out
${ghc}/bin/ghc \ ${ghc}/bin/ghc \
${pkgs.lib.strings.escapeShellArgs packageList} \ ${lib.strings.escapeShellArgs packageList} \
${pkgs.lib.strings.escapeShellArgs objList} \ ${lib.strings.escapeShellArgs objList} \
${ghcOptsArgs} \ ${ghcOptsArgs} \
-o $out/out -o $out/out
''; '';
@ -257,20 +264,20 @@ let
# Returns a list of all module names depended on in the module spec # Returns a list of all module names depended on in the module spec
allModuleNames = modSpec: allModuleNames = modSpec:
[ modSpec.moduleName ] ++ (pkgs.lib.lists.concatMap allModuleNames modSpec.moduleDependencies); [ modSpec.moduleName ] ++ (lib.lists.concatMap allModuleNames modSpec.moduleDependencies);
allModuleDirectories = modSpec: allModuleDirectories = modSpec:
pkgs.lib.lists.concatLists lib.lists.concatLists
( (
[ modSpec.moduleDirectories ] [ modSpec.moduleDirectories ]
++ (pkgs.lib.lists.concatMap allModuleDirectories modSpec.moduleDependencies) ++ (lib.lists.concatMap allModuleDirectories modSpec.moduleDependencies)
); );
# Write a new ghci executable that loads all the modules defined in the # Write a new ghci executable that loads all the modules defined in the
# module spec # module spec
ghciExecutable = ghc: ghcOpts: base: modSpec: ghciExecutable = ghc: ghcOpts: base: modSpec:
let let
ghciArgs = pkgs.lib.strings.escapeShellArgs ghciArgs = lib.strings.escapeShellArgs
(ghcOpts ++ absoluteModuleFiles); (ghcOpts ++ absoluteModuleFiles);
absoluteModuleFiles = map prependBase moduleFiles; absoluteModuleFiles = map prependBase moduleFiles;
moduleFiles = map moduleToFile modules; moduleFiles = map moduleToFile modules;
@ -278,7 +285,7 @@ let
dirs = allModuleDirectories modSpec; dirs = allModuleDirectories modSpec;
prependBase = f: builtins.toString base + "/${f}"; prependBase = f: builtins.toString base + "/${f}";
newGhc = newGhc =
pkgs.symlinkJoin symlinkJoin
{ name = "ghci"; { name = "ghci";
paths = [ ghc ]; paths = [ ghc ];
postBuild = postBuild =
@ -286,16 +293,16 @@ let
wrapProgram "$out/bin/ghci" \ wrapProgram "$out/bin/ghci" \
--add-flags "${ghciArgs}" --add-flags "${ghciArgs}"
''; '';
buildInputs = [pkgs.makeWrapper]; buildInputs = [makeWrapper];
}; };
in in
# This symlinks the extra dirs to $PWD for GHCi to work # This symlinks the extra dirs to $PWD for GHCi to work
pkgs.writeScript "ghci-with-files" writeScript "ghci-with-files"
'' ''
set -euo pipefail set -euo pipefail
TRAPS="" TRAPS=""
for i in ${pkgs.lib.strings.escapeShellArgs dirs}; do for i in ${lib.strings.escapeShellArgs dirs}; do
if [ "$i" != "$PWD" ]; then if [ "$i" != "$PWD" ]; then
for j in $(find "$i" ! -path "$i"); do for j in $(find "$i" ! -path "$i"); do
file=$(basename $j) file=$(basename $j)
@ -312,14 +319,14 @@ let
executable = descr: executable = descr:
let let
ghc = pkgs.haskellPackages.ghcWithPackages ghc = haskellPackages.ghcWithPackages
(ps: map (p: ps.${p}) deps); (ps: map (p: ps.${p}) deps);
deps = descr.dependencies; deps = descr.dependencies;
ghcOpts = ghcOpts =
if (builtins.hasAttr "ghc-options" descr) if (builtins.hasAttr "ghc-options" descr)
then descr.ghc-options then descr.ghc-options
else []; else [];
base = descr.src ; # pkgs.lib.cleanSource descr.src; base = descr.src ; # lib.cleanSource descr.src;
extraFiles = extraFiles =
if (builtins.hasAttr "extra-files" descr) if (builtins.hasAttr "extra-files" descr)
then then