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:
parent
135f8cb578
commit
4561519d8e
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user