Skip to content

[ifd] cabalProjectToNix #122

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 18, 2019
19 changes: 19 additions & 0 deletions default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ let
# overridden with NIX_PATH.
fetchExternal = import ./lib/fetch-external.nix;

mkHackageIndex = indexState: import ./lib/hackageIndex.nix {
inherit (pkgs) runCommand cabal-install;
inherit indexState;
};

# All packages from Hackage as Nix expressions
hackage = import (fetchExternal {
name = "hackage-exprs-source";
Expand Down Expand Up @@ -123,6 +128,20 @@ let
update-stackage = self.callPackage ./scripts/update-stackage.nix {};
update-pins = self.callPackage ./scripts/update-pins.nix {};
};

# Make this handy overridable fetch function available.
inherit fetchExternal;

# Takes a haskell src directory runs cabal new-configure and plan-to-nix.
# Resulting nix files are added to nix-plan subdirectory.
callCabalProjectToNix = import ./lib/cabalProjectToNix.nix {
inherit mkHackageIndex;
inherit pkgs;
inherit (pkgs) runCommand cabal-install ghc;
inherit (pkgs.haskellPackages) hpack;
inherit (self) nix-tools;
inherit (pkgs) symlinkJoin;
};
});

in
Expand Down
62 changes: 62 additions & 0 deletions lib/cabalProjectToNix.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{ mkHackageIndex, pkgs, runCommand, nix-tools, cabal-install, ghc, hpack, symlinkJoin }:
let defaultGhc = ghc;
defaultCabalInstall = cabal-install;
in { hackageIndexState, src, ghc ? defaultGhc, cabal-install ? defaultCabalInstall }:
let
cabalFiles =
pkgs.lib.cleanSourceWith {
inherit src;
filter = path: type:
type == "directory" ||
pkgs.lib.any (i: (pkgs.lib.hasSuffix i path)) [ ".project" ".cabal" "package.yaml" ];
};
plan = if (builtins.compareVersions cabal-install.version "2.4.0.0") < 0
# cabal-install versions before 2.4 will generate insufficient plan information.
then throw "cabal-install (current version: ${cabal-install.version}) needs to be at least 2.4 for plan-to-nix to work without cabal-to-nix"
else runCommand "plan" {
nativeBuildInputs = [ nix-tools ghc hpack cabal-install pkgs.rsync ];
} ''
tmp=$(mktemp -d)
cd $tmp
cp -r ${cabalFiles}/* .
chmod +w -R .
# warning: this may not generate the proper cabal file.
# hpack allows globbing, and turns that into module lists
# without the source available (we cleaneSourceWith'd it),
# this may not produce the right result.
find . -name package.yaml -exec hpack "{}" \;
HOME=${mkHackageIndex hackageIndexState} cabal new-configure

export LANG=C.utf8 # Needed or stack-to-nix will die on unicode inputs
mkdir -p $out

# ensure we have all our .cabal files (also those generated from package.yaml) files.
# otherwise we'd need to be careful about putting the `cabal-generator = hpack` into
# the nix expression. As we already called `hpack` on all `package.yaml` files we can
# skip that step and just package the .cabal files up as well.
#
# This is also important as `plan-to-nix` will look for the .cabal files when generating
# the relevant `pkgs.nix` file with the local .cabal expressions.
rsync -a --prune-empty-dirs \
--include '*/' --include '*.cabal' --include 'package.yaml' \
--exclude '*' \
$tmp/ $out/

# make sure the path's in the plan.json are relative to $out instead of $tmp
# this is necessary so that plan-to-nix relative path logic can work.
substituteInPlace $tmp/dist-newstyle/cache/plan.json --replace "$tmp" "$out"

# run `plan-to-nix` in $out. This should produce files right there with the
# proper relative paths.
(cd $out && plan-to-nix --plan-json $tmp/dist-newstyle/cache/plan.json -o .)

# move pkgs.nix to default.nix ensure we can just nix `import` the result.
mv $out/pkgs.nix $out/default.nix
'';
in
runCommand "plan-and-src" { nativeBuildInputs = [ pkgs.xorg.lndir pkgs.rsync ]; } ''
mkdir $out
# todo: should we clean `src` to drop any .git, .nix, ... other irelevant files?
lndir -silent "${src}" "$out"
rsync -a ${plan}/ $out/
''
16 changes: 16 additions & 0 deletions lib/hackageIndex.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{ runCommand, cabal-install
, indexState ? "2019-04-24T21:34:04Z"
} :
let
# To avoid downloading more data than necessary this will provide a base.
cachedState = runCommand "hackage-${builtins.substring 0 4 indexState}" {} ''
mkdir -p $out
HOME=$out ${cabal-install}/bin/cabal update --index-state='${builtins.substring 0 4 indexState}-01-01T00:00:00Z'
'';
in runCommand "hackage-${builtins.replaceStrings [":"] [""] indexState}" {} ''
mkdir -p $out
cp -r ${cachedState}/.cabal $out
chmod +w -R $out/.cabal
sed -i.back -e "s|${cachedState}|$out|g" $out/.cabal/config
HOME=$out ${cabal-install}/bin/cabal update --index-state='${indexState}'
''
33 changes: 33 additions & 0 deletions test/call-cabal-project-to-nix/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{ stdenv, mkCabalProjectPkgSet, callCabalProjectToNix }:

with stdenv.lib;

let
pkgSet = mkCabalProjectPkgSet {
plan-pkgs = import (callCabalProjectToNix {
hackageIndexState = "2019-04-24T21:34:04Z";
# reuse the cabal-simple test project
src = ../cabal-simple;
});
};
packages = pkgSet.config.hsPkgs;
in
stdenv.mkDerivation {
name = "callStackToNix-test";

buildCommand = ''
exe="${packages.cabal-simple.components.exes.cabal-simple}/bin/cabal-simple"

printf "checking whether executable runs... " >& 2
$exe

touch $out
'';

meta.platforms = platforms.all;

passthru = {
# Attributes used for debugging with nix repl
inherit pkgSet packages;
};
}
1 change: 1 addition & 0 deletions test/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ in {
builder-haddock = haskell.callPackage ./builder-haddock {};
stack-simple = haskell.callPackage ./stack-simple {};
callStackToNix = haskell.callPackage ./callStackToNix {};
callCabalProjectToNix = haskell.callPackage ./call-cabal-project-to-nix {};

# Run unit tests with: nix-instantiate --eval --strict -A unit
# An empty list means success.
Expand Down