diff --git a/default.nix b/default.nix index 20c57fd093..b612e796cb 100644 --- a/default.nix +++ b/default.nix @@ -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"; @@ -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 diff --git a/lib/cabalProjectToNix.nix b/lib/cabalProjectToNix.nix new file mode 100644 index 0000000000..26fd5da044 --- /dev/null +++ b/lib/cabalProjectToNix.nix @@ -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/ + '' diff --git a/lib/hackageIndex.nix b/lib/hackageIndex.nix new file mode 100644 index 0000000000..14d6d4b460 --- /dev/null +++ b/lib/hackageIndex.nix @@ -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}' + '' diff --git a/test/call-cabal-project-to-nix/default.nix b/test/call-cabal-project-to-nix/default.nix new file mode 100644 index 0000000000..c18b6a3363 --- /dev/null +++ b/test/call-cabal-project-to-nix/default.nix @@ -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; + }; + } diff --git a/test/default.nix b/test/default.nix index 0789f84ab3..601339fe4d 100644 --- a/test/default.nix +++ b/test/default.nix @@ -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.