mirror of
https://github.com/nmattia/snack.git
synced 2024-09-11 11:55:36 +03:00
Add more (disabled) tests and refactor
This commit is contained in:
parent
cb521b9776
commit
f8bb059954
136
snack-lib/build.nix
Normal file
136
snack-lib/build.nix
Normal file
@ -0,0 +1,136 @@
|
||||
{ runCommand, lib, singleOut, callPackage, stdenv, rsync, symlinkJoin }:
|
||||
|
||||
with (callPackage ./modules.nix { inherit singleOut; });
|
||||
with (callPackage ./lib.nix {});
|
||||
with (callPackage ./module-spec.nix { inherit singleOut; });
|
||||
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 modSpec.moduleDependencies;
|
||||
exts = modSpec.moduleExtensions;
|
||||
ghcOpts = modSpec.moduleGhcOpts ++ (map (x: "-X${x}") exts);
|
||||
ghcOptsArgs = lib.strings.escapeShellArgs ghcOpts;
|
||||
objectName = modSpec.moduleName;
|
||||
builtDeps = map (buildModule ghcWith) modSpec.moduleImports;
|
||||
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" ];
|
||||
buildPhase =
|
||||
''
|
||||
echo "Building module ${modSpec.moduleName}"
|
||||
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
|
||||
echo "Done building module ${modSpec.moduleName}"
|
||||
'';
|
||||
|
||||
buildInputs =
|
||||
[ ghc
|
||||
rsync
|
||||
];
|
||||
};
|
||||
}
|
@ -20,230 +20,11 @@ with (callPackage ./module-spec.nix { inherit singleOut; });
|
||||
with (callPackage ./package-spec.nix { inherit singleOut; });
|
||||
with (callPackage ./hpack.nix { inherit singleOut; });
|
||||
with (callPackage ./lib.nix {});
|
||||
with (callPackage ./build.nix { inherit singleOut; });
|
||||
with (callPackage ./ghci.nix { inherit singleOut; });
|
||||
|
||||
let
|
||||
|
||||
buildModule = ghcWith: modSpec:
|
||||
let
|
||||
ghc = ghcWith modSpec.moduleDependencies;
|
||||
exts = modSpec.moduleExtensions;
|
||||
ghcOpts = modSpec.moduleGhcOpts ++ (map (x: "-X${x}") exts);
|
||||
ghcOptsArgs = lib.strings.escapeShellArgs ghcOpts;
|
||||
objectName = modSpec.moduleName;
|
||||
builtDeps = map (buildModule ghcWith) modSpec.moduleImports;
|
||||
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" ];
|
||||
buildPhase =
|
||||
''
|
||||
echo "Building module ${modSpec.moduleName}"
|
||||
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
|
||||
echo "Done building module ${modSpec.moduleName}"
|
||||
'';
|
||||
|
||||
buildInputs =
|
||||
[ ghc
|
||||
rsync
|
||||
];
|
||||
};
|
||||
|
||||
# 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;
|
||||
|
||||
# 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;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
# Write a new ghci executable that loads all the modules defined in the
|
||||
# module spec
|
||||
ghciWithMain = ghcWith: mainModSpec:
|
||||
let
|
||||
imports = allTransitiveImports [mainModSpec];
|
||||
modSpecs = [mainModSpec] ++ imports;
|
||||
in ghciWithModules ghcWith modSpecs;
|
||||
|
||||
ghciWithModules = ghcWith: modSpecs:
|
||||
let
|
||||
ghcOpts = allTransitiveGhcOpts modSpecs
|
||||
++ (map (x: "-X${x}") (allTransitiveExtensions modSpecs));
|
||||
ghc = ghcWith (allTransitiveDeps modSpecs);
|
||||
ghciArgs = lib.strings.escapeShellArgs
|
||||
(ghcOpts ++ absoluteModuleFiles);
|
||||
absoluteModuleFiles =
|
||||
map
|
||||
(mod:
|
||||
builtins.toString (mod.moduleBase) +
|
||||
"/${moduleToFile mod.moduleName}"
|
||||
)
|
||||
modSpecs;
|
||||
|
||||
dirs = allTransitiveDirectories modSpecs;
|
||||
newGhc =
|
||||
symlinkJoin
|
||||
{ name = "ghci";
|
||||
paths = [ ghc ];
|
||||
postBuild =
|
||||
''
|
||||
wrapProgram "$out/bin/ghci" \
|
||||
--add-flags "${ghciArgs}"
|
||||
'';
|
||||
buildInputs = [makeWrapper];
|
||||
};
|
||||
in
|
||||
# This symlinks the extra dirs to $PWD for GHCi to work
|
||||
writeScriptBin "ghci-with-files"
|
||||
''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
TRAPS=""
|
||||
for i in ${lib.strings.escapeShellArgs dirs}; do
|
||||
if [ "$i" != "$PWD" ]; then
|
||||
for j in $(find "$i" ! -path "$i"); do
|
||||
file=$(basename $j)
|
||||
echo "Temporarily symlinking $j to $file..."
|
||||
ln -s $j $file
|
||||
TRAPS="rm $file ; $TRAPS"
|
||||
trap "$TRAPS" EXIT
|
||||
echo "done."
|
||||
done
|
||||
fi
|
||||
done
|
||||
${newGhc}/bin/ghci
|
||||
'';
|
||||
|
||||
# Takes a package spec and returns (modSpecs -> Fold)
|
||||
modSpecFoldFromPackageSpec = pkgSpec:
|
||||
let
|
||||
baseByModuleName = modName:
|
||||
let res = pkgSpecByModuleName pkgSpec null modName;
|
||||
in if res == null then null else res.packageBase;
|
||||
depsByModuleName = modName:
|
||||
(pkgSpecByModuleName
|
||||
pkgSpec
|
||||
(abort "asking dependencies for external module: ${modName}")
|
||||
modName).packageDependencies
|
||||
modName
|
||||
;
|
||||
extsByModuleName = modName:
|
||||
(pkgSpecByModuleName
|
||||
pkgSpec
|
||||
(abort "asking extensions for external module: ${modName}")
|
||||
modName).packageExtensions;
|
||||
ghcOptsByModuleName = modName:
|
||||
(pkgSpecByModuleName
|
||||
pkgSpec
|
||||
(abort "asking ghc options for external module: ${modName}")
|
||||
modName).packageGhcOpts;
|
||||
in
|
||||
moduleSpecFold
|
||||
{ baseByModuleName = baseByModuleName;
|
||||
filesByModuleName = pkgSpec.packageExtraFiles;
|
||||
dirsByModuleName = pkgSpec.packageExtraDirectories;
|
||||
depsByModuleName = depsByModuleName;
|
||||
extsByModuleName = extsByModuleName;
|
||||
ghcOptsByModuleName = ghcOptsByModuleName;
|
||||
};
|
||||
|
||||
# TODO: "executable" is a bad name
|
||||
executable = pkgDescr:
|
||||
let
|
||||
|
65
snack-lib/ghci.nix
Normal file
65
snack-lib/ghci.nix
Normal file
@ -0,0 +1,65 @@
|
||||
{ makeWrapper, symlinkJoin, lib, callPackage, singleOut, writeScriptBin }:
|
||||
|
||||
with (callPackage ./module-spec.nix { inherit singleOut; });
|
||||
with (callPackage ./modules.nix { inherit singleOut; });
|
||||
|
||||
rec {
|
||||
|
||||
# Write a new ghci executable that loads all the modules defined in the
|
||||
# module spec
|
||||
ghciWithMain = ghcWith: mainModSpec:
|
||||
let
|
||||
imports = allTransitiveImports [mainModSpec];
|
||||
modSpecs = [mainModSpec] ++ imports;
|
||||
in ghciWithModules ghcWith modSpecs;
|
||||
|
||||
ghciWithModules = ghcWith: modSpecs:
|
||||
let
|
||||
ghcOpts = allTransitiveGhcOpts modSpecs
|
||||
++ (map (x: "-X${x}") (allTransitiveExtensions modSpecs));
|
||||
ghc = ghcWith (allTransitiveDeps modSpecs);
|
||||
ghciArgs = lib.strings.escapeShellArgs
|
||||
(ghcOpts ++ absoluteModuleFiles);
|
||||
absoluteModuleFiles =
|
||||
map
|
||||
(mod:
|
||||
builtins.toString (mod.moduleBase) +
|
||||
"/${moduleToFile mod.moduleName}"
|
||||
)
|
||||
modSpecs;
|
||||
|
||||
dirs = allTransitiveDirectories modSpecs;
|
||||
newGhc =
|
||||
symlinkJoin
|
||||
{ name = "ghci";
|
||||
paths = [ ghc ];
|
||||
postBuild =
|
||||
''
|
||||
wrapProgram "$out/bin/ghci" \
|
||||
--add-flags "${ghciArgs}"
|
||||
'';
|
||||
buildInputs = [makeWrapper];
|
||||
};
|
||||
in
|
||||
# This symlinks the extra dirs to $PWD for GHCi to work
|
||||
writeScriptBin "ghci-with-files"
|
||||
''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
TRAPS=""
|
||||
for i in ${lib.strings.escapeShellArgs dirs}; do
|
||||
if [ "$i" != "$PWD" ]; then
|
||||
for j in $(find "$i" ! -path "$i"); do
|
||||
file=$(basename $j)
|
||||
echo "Temporarily symlinking $j to $file..."
|
||||
ln -s $j $file
|
||||
TRAPS="rm $file ; $TRAPS"
|
||||
trap "$TRAPS" EXIT
|
||||
echo "done."
|
||||
done
|
||||
fi
|
||||
done
|
||||
${newGhc}/bin/ghci
|
||||
'';
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
}:
|
||||
|
||||
with (callPackage ./modules.nix { inherit singleOut; });
|
||||
with (callPackage ./package-spec.nix { inherit singleOut; });
|
||||
with (callPackage ./lib.nix {});
|
||||
|
||||
rec {
|
||||
@ -99,4 +100,39 @@ rec {
|
||||
}
|
||||
modSpecs
|
||||
);
|
||||
|
||||
|
||||
# Takes a package spec and returns (modSpecs -> Fold)
|
||||
modSpecFoldFromPackageSpec = pkgSpec:
|
||||
let
|
||||
baseByModuleName = modName:
|
||||
let res = pkgSpecByModuleName pkgSpec null modName;
|
||||
in if res == null then null else res.packageBase;
|
||||
depsByModuleName = modName:
|
||||
(pkgSpecByModuleName
|
||||
pkgSpec
|
||||
(abort "asking dependencies for external module: ${modName}")
|
||||
modName).packageDependencies
|
||||
modName
|
||||
;
|
||||
extsByModuleName = modName:
|
||||
(pkgSpecByModuleName
|
||||
pkgSpec
|
||||
(abort "asking extensions for external module: ${modName}")
|
||||
modName).packageExtensions;
|
||||
ghcOptsByModuleName = modName:
|
||||
(pkgSpecByModuleName
|
||||
pkgSpec
|
||||
(abort "asking ghc options for external module: ${modName}")
|
||||
modName).packageGhcOpts;
|
||||
in
|
||||
moduleSpecFold
|
||||
{ baseByModuleName = baseByModuleName;
|
||||
filesByModuleName = pkgSpec.packageExtraFiles;
|
||||
dirsByModuleName = pkgSpec.packageExtraDirectories;
|
||||
depsByModuleName = depsByModuleName;
|
||||
extsByModuleName = extsByModuleName;
|
||||
ghcOptsByModuleName = ghcOptsByModuleName;
|
||||
};
|
||||
|
||||
}
|
||||
|
6
tests/packages/package.yaml
Normal file
6
tests/packages/package.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
name: snack-packages-test
|
||||
dependencies:
|
||||
- conduit
|
||||
executable:
|
||||
main: Foo.hs
|
||||
source-dirs: src
|
@ -3,12 +3,29 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
snack build
|
||||
snack run | diff golden -
|
||||
|
||||
TMP_FILE=$(mktemp)
|
||||
test() {
|
||||
$SNACK build
|
||||
$SNACK run | diff golden -
|
||||
|
||||
capture_io "$TMP_FILE" main | snack ghci
|
||||
TMP_FILE=$(mktemp)
|
||||
|
||||
diff golden $TMP_FILE
|
||||
rm $TMP_FILE
|
||||
capture_io "$TMP_FILE" main | snack ghci
|
||||
|
||||
diff golden $TMP_FILE
|
||||
rm $TMP_FILE
|
||||
}
|
||||
|
||||
|
||||
SNACK="snack" test
|
||||
SNACK="snack -s ./snack.nix" test
|
||||
#SNACK="snack --package-yaml ./package.yaml" test
|
||||
#snack build
|
||||
#snack run | diff golden -
|
||||
|
||||
#TMP_FILE=$(mktemp)
|
||||
|
||||
#capture_io "$TMP_FILE" main | snack ghci
|
||||
|
||||
#diff golden $TMP_FILE
|
||||
#rm $TMP_FILE
|
||||
|
Loading…
Reference in New Issue
Block a user