diff --git a/builder/comp-builder.nix b/builder/comp-builder.nix index ca50e5da8c..f4a5fecd2a 100644 --- a/builder/comp-builder.nix +++ b/builder/comp-builder.nix @@ -1,4 +1,4 @@ -{ stdenv, buildPackages, ghc, lib, pkgconfig, writeText, runCommand, haskellLib, nonReinstallablePkgs, ghcForComponent, hsPkgs }: +{ stdenv, buildPackages, ghc, lib, pkgconfig, haskellLib, makeConfigFiles, ghcForComponent, hsPkgs }: { componentId , component @@ -39,115 +39,10 @@ let then "${name}-all" else "${name}-${componentId.ctype}-${componentId.cname}"; - flagsAndConfig = field: xs: lib.optionalString (xs != []) '' - echo ${lib.concatStringsSep " " (map (x: "--${field}=${x}") xs)} >> $out/configure-flags - echo "${field}: ${lib.concatStringsSep " " xs}" >> $out/cabal.config - ''; - - flatDepends = - let - makePairs = map (p: rec { key="${val}"; val=(p.components.library or p); }); - closure = builtins.genericClosure { - startSet = makePairs component.depends; - operator = {val,...}: makePairs val.config.depends; - }; - in map ({val,...}: val) closure; - - exactDep = pdbArg: p: '' - if id=$(target-pkg ${pdbArg} field ${p} id --simple-output); then - echo "--dependency=${p}=$id" >> $out/configure-flags - fi - if ver=$(target-pkg ${pdbArg} field ${p} version --simple-output); then - echo "constraint: ${p} == $ver" >> $out/cabal.config - echo "constraint: ${p} installed" >> $out/cabal.config - fi - ''; - - envDep = pdbArg: p: '' - if id=$(target-pkg ${pdbArg} field ${p} id --simple-output); then - echo "package-id $id" >> $out/ghc-environment - fi - ''; - - configFiles = runCommand "${fullName}-config" { nativeBuildInputs = [ghc]; } ('' - mkdir -p $out - - # Calls ghc-pkg for the target platform - target-pkg() { - ${ghc.targetPrefix}ghc-pkg "$@" - } - - target-pkg init $out/package.conf.d - - ${lib.concatStringsSep "\n" (lib.mapAttrsToList flagsAndConfig { - "extra-lib-dirs" = map (p: "${lib.getLib p}/lib") component.libs; - "extra-include-dirs" = map (p: "${lib.getDev p}/include") component.libs; - "extra-framework-dirs" = map (p: "${p}/Library/Frameworks") component.frameworks; - })} - - # Copy over the nonReinstallablePkgs from the global package db. - # Note: we need to use --global-package-db with ghc-pkg to prevent it - # from looking into the implicit global package db when registering the package. - ${lib.concatMapStringsSep "\n" (p: '' - target-pkg describe ${p} | target-pkg --force --global-package-db $out/package.conf.d register - || true - '') nonReinstallablePkgs} - - ${lib.concatMapStringsSep "\n" (p: '' - target-pkg --package-db ${p}/package.conf.d dump | target-pkg --force --package-db $out/package.conf.d register - - '') flatDepends} - - # Note: we pass `clear` first to ensure that we never consult the implicit global package db. - ${flagsAndConfig "package-db" ["clear" "$out/package.conf.d"]} - - echo ${lib.concatStringsSep " " (lib.mapAttrsToList (fname: val: "--flags=${lib.optionalString (!val) "-" + fname}") flags)} >> $out/configure-flags - - # Provide a GHC environment file - cat > $out/ghc-environment <> $out/configure-flags - echo "allow-newer: ${package.identifier.name}:*" >> $out/cabal.config - echo "allow-older: ${package.identifier.name}:*" >> $out/cabal.config - - ${lib.concatMapStringsSep "\n" (p: exactDep "--package-db ${p.components.library}/package.conf.d" p.identifier.name) component.depends} - ${lib.concatMapStringsSep "\n" (exactDep "") nonReinstallablePkgs} - - '' - # This code originates in the `generic-builder.nix` from nixpkgs. However GHC has been fixed - # to drop unused libraries referneced from libraries; and this patch is usually included in the - # nixpkgs's GHC builds. This doesn't sadly make this stupid hack unnecessary. It resurfes in - # the form of Cabal trying to be smart. Cabal when linking a library figures out that you likely - # need those `rpath` entries, and passes `-optl-Wl,-rpath,...` for each dynamic library path to - # GHC, thus subverting the linker and forcing it to insert all those RPATHs weather or not they - # are needed. We therfore reuse the linker hack here to move all al dynamic lirbaries into a - # common folder (as links) and thus prevent Cabal from going nuts. - # - # TODO: Fix Cabal. - # TODO: this is only needed if we do dynamic libraries. - + lib.optionalString stdenv.isDarwin '' - # Work around a limit in the macOS Sierra linker on the number of paths - # referenced by any one dynamic library: - # - # Create a local directory with symlinks of the *.dylib (macOS shared - # libraries) from all the dependencies. - local dynamicLinksDir="$out/lib/links" - mkdir -p $dynamicLinksDir - for d in $(grep dynamic-library-dirs "$out/package.conf.d/"*|awk '{print $2}'|sort -u); do - ln -s "$d/"*.dylib $dynamicLinksDir - done - # Edit the local package DB to reference the links directory. - for f in "$out/package.conf.d/"*.conf; do - sed -i "s,dynamic-library-dirs: .*,dynamic-library-dirs: $dynamicLinksDir," $f - done - '' + '' - target-pkg --package-db $out/package.conf.d recache - '' + '' - target-pkg --package-db $out/package.conf.d check - ''); + configFiles = makeConfigFiles { + inherit (package) identifier; + inherit component fullName flags; + }; finalConfigureFlags = lib.concatStringsSep " " ( [ "--prefix=$out" @@ -176,9 +71,11 @@ let ++ component.configureFlags ); - executableToolDepends = lib.concatMap (c: if c.isHaskell or false + executableToolDepends = + (lib.concatMap (c: if c.isHaskell or false then builtins.attrValues (c.components.exes or {}) - else [c]) component.build-tools; + else [c]) component.build-tools) ++ + lib.optional (component.pkgconfig != []) pkgconfig; # Unfortunately, we need to wrap ghc commands for cabal builds to # work in the nix-shell. See ../doc/removing-with-package-wrapper.md. @@ -209,7 +106,7 @@ stdenv.mkDerivation ({ passthru = { inherit (package) identifier; config = component; - inherit configFiles; + inherit configFiles executableToolDepends; env = shellWrappers; # The directory containing the haddock documentation. @@ -239,7 +136,6 @@ stdenv.mkDerivation ({ nativeBuildInputs = [shellWrappers buildPackages.removeReferencesTo] - ++ lib.optional (component.pkgconfig != []) pkgconfig ++ executableToolDepends; SETUP_HS = setup + /bin/Setup; diff --git a/builder/default.nix b/builder/default.nix index e6c83aef44..eefd949cd9 100644 --- a/builder/default.nix +++ b/builder/default.nix @@ -1,91 +1,35 @@ -{ pkgs, buildPackages, stdenv, lib, haskellLib, ghc, buildGHC, fetchurl, writeText, runCommand, pkgconfig, nonReinstallablePkgs, ghcForComponent, hsPkgs }: - -{ flags -, package -, components -, cabal-generator - -, name -, sha256 -, src -, revision -, revisionSha256 -, patches - -, preUnpack -, postUnpack -, preConfigure -, postConfigure -, preBuild -, postBuild -, preCheck -, postCheck -, preInstall -, postInstall -, preHaddock -, postHaddock - -, shellHook - -, ... -}@config: +{ pkgs, buildPackages, stdenv, lib, haskellLib, ghc, buildGHC, fetchurl, pkgconfig, nonReinstallablePkgs, hsPkgs }: let - cabalFile = if revision == null || revision == 0 then null else - fetchurl { - name = "${name}-${toString revision}.cabal"; - url = "https://hackage.haskell.org/package/${name}/revision/${toString revision}.cabal"; - sha256 = revisionSha256; - }; - - defaultSetupSrc = builtins.toFile "Setup.hs" '' - import Distribution.Simple - main = defaultMain - ''; - defaultSetup = buildPackages.runCommand "default-Setup" { nativeBuildInputs = [buildGHC]; } '' - cat ${defaultSetupSrc} > Setup.hs - mkdir -p $out/bin - ${buildGHC.targetPrefix}ghc Setup.hs --make -o $out/bin/Setup - ''; - - setup = if package.buildType == "Simple" - then defaultSetup - else stdenv.mkDerivation { - name = "${name}-setup"; - nativeBuildInputs = [buildGHC]; - inherit src; - phases = ["unpackPhase" "buildPhase" "installPhase"]; - buildPhase = '' - for f in Setup.hs Setup.lhs; do - if [ -f $f ]; then - echo Compiling package $f - ghc $f --make -o ./Setup - setup=$(pwd)/Setup - fi - done - [ -f ./Setup ] || (echo Failed to build Setup && exit 1) - ''; - - installPhase = '' - mkdir -p $out/bin - install ./Setup $out/bin/Setup - ''; - }; + # Builds a single component of a package. + comp-builder = haskellLib.weakCallPackage pkgs ./comp-builder.nix { + inherit ghc haskellLib makeConfigFiles ghcForComponent hsPkgs; + }; - comp-builder = haskellLib.weakCallPackage pkgs ./comp-builder.nix { inherit ghc haskellLib nonReinstallablePkgs ghcForComponent hsPkgs; }; + # Wraps GHC to provide dependencies in a way that works for both the + # component builder and for nix-shells. + ghcForComponent = import ./ghc-for-component-wrapper.nix { + inherit lib ghc; + inherit (buildPackages) stdenv runCommand makeWrapper; + inherit (buildPackages.xorg) lndir; + }; - buildComp = componentId: component: comp-builder { - inherit componentId component package name src flags setup cabalFile cabal-generator patches revision - preUnpack postUnpack preConfigure postConfigure - preBuild postBuild preCheck postCheck - preInstall postInstall preHaddock postHaddock - shellHook - ; + # Builds a derivation which contains a ghc package-db of + # dependencies for a component. + makeConfigFiles = haskellLib.weakCallPackage pkgs ./make-config-files.nix { + inherit ghc haskellLib nonReinstallablePkgs; }; in { - components = haskellLib.applyComponents buildComp config; - inherit (package) identifier; - inherit setup cabalFile; - isHaskell = true; + # Build a Haskell package from its config. + # TODO: this pkgs is the adjusted pkgs, but pkgs.pkgs is unadjusted + build-package = haskellLib.weakCallPackage pkgs ./hspkg-builder.nix { + inherit haskellLib ghc buildGHC comp-builder; + }; + + # Same as haskellPackages.shellFor in nixpkgs. + shellFor = haskellLib.weakCallPackage pkgs ./shell-for.nix { + inherit hsPkgs ghcForComponent makeConfigFiles; + inherit (buildPackages) glibcLocales; + }; } diff --git a/builder/hspkg-builder.nix b/builder/hspkg-builder.nix new file mode 100644 index 0000000000..d2e5eef7c2 --- /dev/null +++ b/builder/hspkg-builder.nix @@ -0,0 +1,90 @@ +{ pkgs, buildPackages, stdenv, lib, haskellLib, ghc, buildGHC, fetchurl, runCommand, pkgconfig, comp-builder }: + + +{ flags +, package +, components +, cabal-generator + +, name +, sha256 +, src +, revision +, revisionSha256 +, patches + +, preUnpack +, postUnpack +, preConfigure +, postConfigure +, preBuild +, postBuild +, preCheck +, postCheck +, preInstall +, postInstall +, preHaddock +, postHaddock + +, shellHook + +, ... +}@config: + +let + cabalFile = if revision == null || revision == 0 then null else + fetchurl { + name = "${name}-${toString revision}.cabal"; + url = "https://hackage.haskell.org/package/${name}/revision/${toString revision}.cabal"; + sha256 = revisionSha256; + }; + + defaultSetupSrc = builtins.toFile "Setup.hs" '' + import Distribution.Simple + main = defaultMain + ''; + defaultSetup = buildPackages.runCommand "default-Setup" { nativeBuildInputs = [buildGHC]; } '' + cat ${defaultSetupSrc} > Setup.hs + mkdir -p $out/bin + ${buildGHC.targetPrefix}ghc Setup.hs --make -o $out/bin/Setup + ''; + + setup = if package.buildType == "Simple" + then defaultSetup + else stdenv.mkDerivation { + name = "${name}-setup"; + nativeBuildInputs = [buildGHC]; + inherit src; + phases = ["unpackPhase" "buildPhase" "installPhase"]; + buildPhase = '' + for f in Setup.hs Setup.lhs; do + if [ -f $f ]; then + echo Compiling package $f + ghc $f --make -o ./Setup + setup=$(pwd)/Setup + fi + done + [ -f ./Setup ] || (echo Failed to build Setup && exit 1) + ''; + + installPhase = '' + mkdir -p $out/bin + install ./Setup $out/bin/Setup + ''; + }; + + buildComp = componentId: component: comp-builder { + inherit componentId component package name src flags setup cabalFile cabal-generator patches revision + preUnpack postUnpack preConfigure postConfigure + preBuild postBuild preCheck postCheck + preInstall postInstall preHaddock postHaddock + shellHook + ; + }; + +in { + components = haskellLib.applyComponents buildComp config; + inherit (package) identifier; + inherit setup cabalFile; + isHaskell = true; +} diff --git a/builder/make-config-files.nix b/builder/make-config-files.nix new file mode 100644 index 0000000000..c6fa79e8f5 --- /dev/null +++ b/builder/make-config-files.nix @@ -0,0 +1,114 @@ +{ stdenv, lib, haskellLib, ghc, nonReinstallablePkgs, runCommand }: + +let + flagsAndConfig = field: xs: lib.optionalString (xs != []) '' + echo ${lib.concatStringsSep " " (map (x: "--${field}=${x}") xs)} >> $out/configure-flags + echo "${field}: ${lib.concatStringsSep " " xs}" >> $out/cabal.config + ''; + + flatDepends = component: + let + makePairs = map (p: rec { key="${val}"; val=(p.components.library or p); }); + closure = builtins.genericClosure { + startSet = makePairs component.depends; + operator = {val,...}: makePairs val.config.depends; + }; + in map ({val,...}: val) closure; + + exactDep = pdbArg: p: '' + if id=$(target-pkg ${pdbArg} field ${p} id --simple-output); then + echo "--dependency=${p}=$id" >> $out/configure-flags + fi + if ver=$(target-pkg ${pdbArg} field ${p} version --simple-output); then + echo "constraint: ${p} == $ver" >> $out/cabal.config + echo "constraint: ${p} installed" >> $out/cabal.config + fi + ''; + + envDep = pdbArg: p: '' + if id=$(target-pkg ${pdbArg} field ${p} id --simple-output); then + echo "package-id $id" >> $out/ghc-environment + fi + ''; + +in { identifier, component, fullName, flags ? {} }: + + runCommand "${fullName}-config" { nativeBuildInputs = [ghc]; } ('' + mkdir -p $out + + # Calls ghc-pkg for the target platform + target-pkg() { + ${ghc.targetPrefix}ghc-pkg "$@" + } + + target-pkg init $out/package.conf.d + + ${lib.concatStringsSep "\n" (lib.mapAttrsToList flagsAndConfig { + "extra-lib-dirs" = map (p: "${lib.getLib p}/lib") component.libs; + "extra-include-dirs" = map (p: "${lib.getDev p}/include") component.libs; + "extra-framework-dirs" = map (p: "${p}/Library/Frameworks") component.frameworks; + })} + + # Copy over the nonReinstallablePkgs from the global package db. + # Note: we need to use --global-package-db with ghc-pkg to prevent it + # from looking into the implicit global package db when registering the package. + ${lib.concatMapStringsSep "\n" (p: '' + target-pkg describe ${p} | target-pkg --force --global-package-db $out/package.conf.d register - || true + '') nonReinstallablePkgs} + + ${lib.concatMapStringsSep "\n" (p: '' + target-pkg --package-db ${p}/package.conf.d dump | target-pkg --force --package-db $out/package.conf.d register - + '') (flatDepends component)} + + # Note: we pass `clear` first to ensure that we never consult the implicit global package db. + ${flagsAndConfig "package-db" ["clear" "$out/package.conf.d"]} + + echo ${lib.concatStringsSep " " (lib.mapAttrsToList (fname: val: "--flags=${lib.optionalString (!val) "-" + fname}") flags)} >> $out/configure-flags + + # Provide a GHC environment file + cat > $out/ghc-environment <> $out/configure-flags + echo "allow-newer: ${identifier.name}:*" >> $out/cabal.config + echo "allow-older: ${identifier.name}:*" >> $out/cabal.config + + ${lib.concatMapStringsSep "\n" (p: exactDep "--package-db ${p.components.library}/package.conf.d" p.identifier.name) component.depends} + ${lib.concatMapStringsSep "\n" (exactDep "") nonReinstallablePkgs} + + '' + # This code originates in the `generic-builder.nix` from nixpkgs. However GHC has been fixed + # to drop unused libraries referneced from libraries; and this patch is usually included in the + # nixpkgs's GHC builds. This doesn't sadly make this stupid hack unnecessary. It resurfes in + # the form of Cabal trying to be smart. Cabal when linking a library figures out that you likely + # need those `rpath` entries, and passes `-optl-Wl,-rpath,...` for each dynamic library path to + # GHC, thus subverting the linker and forcing it to insert all those RPATHs weather or not they + # are needed. We therfore reuse the linker hack here to move all al dynamic lirbaries into a + # common folder (as links) and thus prevent Cabal from going nuts. + # + # TODO: Fix Cabal. + # TODO: this is only needed if we do dynamic libraries. + + lib.optionalString stdenv.isDarwin '' + # Work around a limit in the macOS Sierra linker on the number of paths + # referenced by any one dynamic library: + # + # Create a local directory with symlinks of the *.dylib (macOS shared + # libraries) from all the dependencies. + local dynamicLinksDir="$out/lib/links" + mkdir -p $dynamicLinksDir + for d in $(grep dynamic-library-dirs "$out/package.conf.d/"*|awk '{print $2}'|sort -u); do + ln -s "$d/"*.dylib $dynamicLinksDir + done + # Edit the local package DB to reference the links directory. + for f in "$out/package.conf.d/"*.conf; do + sed -i "s,dynamic-library-dirs: .*,dynamic-library-dirs: $dynamicLinksDir," $f + done + '' + '' + target-pkg --package-db $out/package.conf.d recache + '' + '' + target-pkg --package-db $out/package.conf.d check + '') diff --git a/builder/shell-for.nix b/builder/shell-for.nix new file mode 100644 index 0000000000..2f959a1000 --- /dev/null +++ b/builder/shell-for.nix @@ -0,0 +1,54 @@ +{ lib, stdenv, glibcLocales, pkgconfig, ghcForComponent, makeConfigFiles, hsPkgs }: + +{ packages, withHoogle ? true, ... } @ args: + +let + selected = packages hsPkgs; + selectedConfigs = map (p: p.components.all.config) selected; + + name = if lib.length selected == 1 + then "ghc-shell-for-${(lib.head selected).name}" + else "ghc-shell-for-packages"; + + # If `packages = [ a b ]` and `a` depends on `b`, don't build `b`, + # because cabal will end up ignoring that built version, assuming + # new-style commands. + packageInputs = lib.filter + (input: lib.all (cfg: input.identifier != cfg.identifier) selected) + (lib.concatMap (cfg: cfg.depends) selectedConfigs); + + # Add the system libraries and build tools of the selected haskell + # packages to the shell. + systemInputs = lib.concatMap (p: p.components.all.buildInputs) selected; + nativeBuildInputs = lib.concatMap (p: p.components.all.executableToolDepends) selected; + + # Set up a "dummy" component to use with ghcForComponent. + configFiles = makeConfigFiles { + fullName = args.name or name; + identifier.name = name; + component = { + depends = packageInputs; + libs = []; + frameworks = []; + doExactConfig = false; + }; + }; + ghcEnv = ghcForComponent { + componentName = name; + inherit configFiles; + }; + mkDrvArgs = builtins.removeAttrs args ["packages" "withHoogle"]; +in + stdenv.mkDerivation (mkDrvArgs // { + name = mkDrvArgs.name or name; + + buildInputs = systemInputs ++ mkDrvArgs.buildInputs or []; + nativeBuildInputs = [ ghcEnv ] ++ nativeBuildInputs ++ mkDrvArgs.nativeBuildInputs or []; + phases = ["installPhase"]; + installPhase = "echo $nativeBuildInputs $buildInputs > $out"; + LANG = "en_US.UTF-8"; + LOCALE_ARCHIVE = lib.optionalString (stdenv.hostPlatform.libc == "glibc") "${glibcLocales}/lib/locale/locale-archive"; + CABAL_CONFIG = "${configFiles}/cabal.config"; + + passthru.ghc = ghcEnv; + }) diff --git a/modules/component-driver.nix b/modules/component-driver.nix index 5eee813d0d..e957cdf10a 100644 --- a/modules/component-driver.nix +++ b/modules/component-driver.nix @@ -1,19 +1,10 @@ { config, pkgs, lib, haskellLib, buildModules, ... }: let - # TODO: this pkgs is the adjusted pkgs, but pkgs.pkgs is unadjusted - new-builder = haskellLib.weakCallPackage pkgs ../builder { + builder = haskellLib.weakCallPackage pkgs ../builder { inherit haskellLib; ghc = config.ghc.package; buildGHC = buildModules.config.ghc.package; inherit (config) nonReinstallablePkgs hsPkgs; - inherit ghcForComponent; - }; - - ghcForComponent = import ../builder/ghc-for-component-wrapper.nix { - inherit lib; - inherit (pkgs.buildPackages) stdenv runCommand makeWrapper; - inherit (pkgs.buildPackages.xorg) lndir; - ghc = config.ghc.package; }; in @@ -49,8 +40,11 @@ in type = lib.types.unspecified; }; - config.hsPkgs = { buildPackages = buildModules.config.hsPkgs; } - // lib.mapAttrs - (name: pkg: if pkg == null then null else new-builder pkg) + config.hsPkgs = + { inherit (builder) shellFor; + buildPackages = buildModules.config.hsPkgs; + } // + lib.mapAttrs + (name: pkg: if pkg == null then null else builder.build-package pkg) (config.packages // lib.genAttrs config.nonReinstallablePkgs (_: null)); } diff --git a/test/default.nix b/test/default.nix index d0c4882600..3889bde126 100644 --- a/test/default.nix +++ b/test/default.nix @@ -17,6 +17,7 @@ in { builder-haddock = haskell.callPackage ./builder-haddock {}; stack-simple = haskell.callPackage ./stack-simple {}; snapshots = haskell.callPackage ./snapshots {}; + shell-for = haskell.callPackage ./shell-for {}; callStackToNix = haskell.callPackage ./callStackToNix {}; callCabalProjectToNix = haskell.callPackage ./call-cabal-project-to-nix {}; diff --git a/test/regen.nix b/test/regen.nix index a5a9d1144c..b5bf3de4dc 100644 --- a/test/regen.nix +++ b/test/regen.nix @@ -57,4 +57,9 @@ writeScript "regen-tests.sh" '' cd stack-simple stack-to-nix -o . cd .. + + cd shell-for + cabal_configure + plan-to-nix -o . --plan-json dist-newstyle/cache/plan.json --cabal-project cabal.project + cd .. '' diff --git a/test/shell-for/.plan.nix/pkga.nix b/test/shell-for/.plan.nix/pkga.nix new file mode 100644 index 0000000000..c9dbf88787 --- /dev/null +++ b/test/shell-for/.plan.nix/pkga.nix @@ -0,0 +1,20 @@ +{ system, compiler, flags, pkgs, hsPkgs, pkgconfPkgs, ... }: + { + flags = {}; + package = { + specVersion = "2.2"; + identifier = { name = "pkga"; version = "0.1.0.0"; }; + license = "LicenseRef-PublicDomain"; + copyright = ""; + maintainer = "rodney.lorrimar@iohk.io"; + author = "Rodney Lorrimar"; + homepage = ""; + url = ""; + synopsis = ""; + description = ""; + buildType = "Simple"; + }; + components = { + "library" = { depends = [ (hsPkgs.base) (hsPkgs.lens) (hsPkgs.text) ]; }; + }; + } // rec { src = (pkgs.lib).mkDefault ../pkga; } \ No newline at end of file diff --git a/test/shell-for/.plan.nix/pkgb.nix b/test/shell-for/.plan.nix/pkgb.nix new file mode 100644 index 0000000000..5706432d54 --- /dev/null +++ b/test/shell-for/.plan.nix/pkgb.nix @@ -0,0 +1,39 @@ +{ system, compiler, flags, pkgs, hsPkgs, pkgconfPkgs, ... }: + { + flags = {}; + package = { + specVersion = "2.2"; + identifier = { name = "pkgb"; version = "0.1.0.0"; }; + license = "LicenseRef-PublicDomain"; + copyright = ""; + maintainer = "rodney.lorrimar@iohk.io"; + author = "Rodney Lorrimar"; + homepage = ""; + url = ""; + synopsis = ""; + description = ""; + buildType = "Simple"; + }; + components = { + "library" = { + depends = [ + (hsPkgs.base) + (hsPkgs.pkga) + (hsPkgs.conduit) + (hsPkgs.conduit-extra) + (hsPkgs.directory) + (hsPkgs.resourcet) + ]; + }; + exes = { + "pkgb" = { + depends = [ + (hsPkgs.base) + (hsPkgs.pkgb) + (hsPkgs.optparse-applicative) + (hsPkgs.text) + ]; + }; + }; + }; + } // rec { src = (pkgs.lib).mkDefault ../pkgb; } \ No newline at end of file diff --git a/test/shell-for/cabal.project b/test/shell-for/cabal.project new file mode 100644 index 0000000000..f035a92f6d --- /dev/null +++ b/test/shell-for/cabal.project @@ -0,0 +1,2 @@ +packages: pkga + pkgb diff --git a/test/shell-for/conduit.hs b/test/shell-for/conduit.hs new file mode 100644 index 0000000000..061470e92d --- /dev/null +++ b/test/shell-for/conduit.hs @@ -0,0 +1,19 @@ +-- https://github.com/snoyberg/conduit#readme + +import Conduit +import System.Directory (removeFile) + +main = do + -- Pure operations: summing numbers. + print $ runConduitPure $ yieldMany [1..10] .| sumC + + -- Exception safe file access: copy a file. + writeFile "input.txt" "This is a test." -- create the source file + runConduitRes $ sourceFileBS "input.txt" .| sinkFile "output.txt" -- actual copying + readFile "output.txt" >>= putStrLn -- prove that it worked + + -- Perform transformations. + print $ runConduitPure $ yieldMany [1..10] .| mapC (+ 1) .| sinkList + + removeFile "input.txt" + removeFile "output.txt" diff --git a/test/shell-for/default.nix b/test/shell-for/default.nix new file mode 100644 index 0000000000..7d35fc6322 --- /dev/null +++ b/test/shell-for/default.nix @@ -0,0 +1,52 @@ +{ stdenv, cabal-install, mkCabalProjectPkgSet }: + +with stdenv.lib; + +let + pkgSet = mkCabalProjectPkgSet { + plan-pkgs = import ./pkgs.nix; + pkg-def-extras = [{ + pkga = ./.plan.nix/pkga.nix; + pkgb = ./.plan.nix/pkgb.nix; + }]; + modules = [{ + # Package has no exposed modules which causes + # haddock: No input file(s) + packages.bytestring-builder.doHaddock = false; + }]; + }; + + env = pkgSet.config.hsPkgs.shellFor { + # Shell will provide the dependencies of pkga and pkgb, but not + # pkga and pkgb themselves. + packages = ps: with ps; [ pkga pkgb ]; + # This adds cabal-install to the shell, which helps tests because + # they use a nix-shell --pure. Normally you would BYO cabal-install. + buildInputs = [ cabal-install ]; + }; + +in + stdenv.mkDerivation { + name = "shell-for-test"; + + buildCommand = '' + ######################################################################## + # test shell-for with an example program + + cp ${./pkgb/src}/*.hs . + + printf "checking that the shell env has the dependencies...\n" >& 2 + ${env.ghc}/bin/runghc conduit.hs + + touch $out + ''; + + meta.platforms = platforms.all; + passthru = { + # Used for debugging with nix repl + inherit pkgSet; + + # Used for testing externally with nix-shell (../tests.sh). + inherit env; + }; +} diff --git a/test/shell-for/pkga/PkgA.hs b/test/shell-for/pkga/PkgA.hs new file mode 100644 index 0000000000..0acf8dbfbd --- /dev/null +++ b/test/shell-for/pkga/PkgA.hs @@ -0,0 +1,16 @@ +module PkgA (decode) where + +import Control.Lens +import Data.Text.Lens +import Data.Char +import Data.Text (Text) + +decode :: Text -> Text +decode = unpacked . mapped %~ rot 13 + +rot :: Int -> Char -> Char +rot n c | c >= 'a' && c <= 'z' = r 'a' 'z' + | c >= 'A' && c <= 'Z' = r 'A' 'Z' + | otherwise = c + where + r a b = chr $ ord a + ((ord c - ord a + n) `mod` (ord b - ord a + 1)) diff --git a/test/shell-for/pkga/Setup.hs b/test/shell-for/pkga/Setup.hs new file mode 100644 index 0000000000..9a994af677 --- /dev/null +++ b/test/shell-for/pkga/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/test/shell-for/pkga/pkga.cabal b/test/shell-for/pkga/pkga.cabal new file mode 100644 index 0000000000..2174a24509 --- /dev/null +++ b/test/shell-for/pkga/pkga.cabal @@ -0,0 +1,20 @@ +cabal-version: 2.2 +-- Initial package description 'pkga.cabal' generated by 'cabal init'. For +-- further documentation, see http://haskell.org/cabal/users-guide/ + +name: pkga +version: 0.1.0.0 +-- synopsis: +-- description: +-- bug-reports: +license: LicenseRef-PublicDomain +author: Rodney Lorrimar +maintainer: rodney.lorrimar@iohk.io +category: Testing + +library + exposed-modules: PkgA + build-depends: base + , lens + , text + default-language: Haskell2010 diff --git a/test/shell-for/pkgb/Setup.hs b/test/shell-for/pkgb/Setup.hs new file mode 100644 index 0000000000..9a994af677 --- /dev/null +++ b/test/shell-for/pkgb/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/test/shell-for/pkgb/app/Main.hs b/test/shell-for/pkgb/app/Main.hs new file mode 100644 index 0000000000..9d381209ca --- /dev/null +++ b/test/shell-for/pkgb/app/Main.hs @@ -0,0 +1,10 @@ +module Main where + +import ConduitExample (example) +import PkgB (message) +import qualified Data.Text.IO as T + +main :: IO () +main = do + T.putStrLn message + example diff --git a/test/shell-for/pkgb/pkgb.cabal b/test/shell-for/pkgb/pkgb.cabal new file mode 100644 index 0000000000..6b87889157 --- /dev/null +++ b/test/shell-for/pkgb/pkgb.cabal @@ -0,0 +1,36 @@ +cabal-version: 2.2 +-- Initial package description 'pkgb.cabal' generated by 'cabal init'. For +-- further documentation, see http://haskell.org/cabal/users-guide/ + +name: pkgb +version: 0.1.0.0 +-- synopsis: +-- description: +-- bug-reports: +license: LicenseRef-PublicDomain +author: Rodney Lorrimar +maintainer: rodney.lorrimar@iohk.io +category: Testing + +library + exposed-modules: ConduitExample + , PkgB + build-depends: base + , pkga + , conduit + , conduit-extra + , directory + , resourcet + hs-source-dirs: src + default-language: Haskell2010 + +executable pkgb + main-is: Main.hs + -- other-modules: + -- other-extensions: + build-depends: base + , pkgb + , optparse-applicative + , text + hs-source-dirs: app + default-language: Haskell2010 diff --git a/test/shell-for/pkgb/src/ConduitExample.hs b/test/shell-for/pkgb/src/ConduitExample.hs new file mode 100644 index 0000000000..a1b1da3955 --- /dev/null +++ b/test/shell-for/pkgb/src/ConduitExample.hs @@ -0,0 +1,21 @@ +-- https://github.com/snoyberg/conduit#readme + +module ConduitExample (example) where + +import Conduit +import System.Directory (removeFile) + +example = do + -- Pure operations: summing numbers. + print $ runConduitPure $ yieldMany [1..10] .| sumC + + -- Exception safe file access: copy a file. + writeFile "input.txt" "This is a test." -- create the source file + runConduitRes $ sourceFileBS "input.txt" .| sinkFile "output.txt" -- actual copying + readFile "output.txt" >>= putStrLn -- prove that it worked + + -- Perform transformations. + print $ runConduitPure $ yieldMany [1..10] .| mapC (+ 1) .| sinkList + + removeFile "input.txt" + removeFile "output.txt" diff --git a/test/shell-for/pkgb/src/PkgB.hs b/test/shell-for/pkgb/src/PkgB.hs new file mode 100644 index 0000000000..1802960dd7 --- /dev/null +++ b/test/shell-for/pkgb/src/PkgB.hs @@ -0,0 +1,7 @@ +{-# LANGUAGE OverloadedStrings #-} + +module PkgB (message) where + +import PkgA (decode) + +message = decode "Guvf vf n pnony cebwrpg!" diff --git a/test/shell-for/pkgb/src/conduit.hs b/test/shell-for/pkgb/src/conduit.hs new file mode 100644 index 0000000000..45a0361579 --- /dev/null +++ b/test/shell-for/pkgb/src/conduit.hs @@ -0,0 +1,5 @@ +module Main where + +import ConduitExample + +main = example diff --git a/test/shell-for/pkgs.nix b/test/shell-for/pkgs.nix new file mode 100644 index 0000000000..8b49a37e4e --- /dev/null +++ b/test/shell-for/pkgs.nix @@ -0,0 +1,191 @@ +{ + pkgs = hackage: + { + packages = { + "void".revision = (((hackage."void")."0.7.3").revisions).default; + "void".flags.safe = false; + "semigroupoids".revision = (((hackage."semigroupoids")."5.3.2").revisions).default; + "semigroupoids".flags.comonad = true; + "semigroupoids".flags.doctests = true; + "semigroupoids".flags.unordered-containers = true; + "semigroupoids".flags.distributive = true; + "semigroupoids".flags.tagged = true; + "semigroupoids".flags.containers = true; + "semigroupoids".flags.contravariant = true; + "free".revision = (((hackage."free")."5.1.1").revisions).default; + "exceptions".revision = (((hackage."exceptions")."0.10.2").revisions).default; + "binary".revision = (((hackage."binary")."0.8.5.1").revisions).default; + "ghc-prim".revision = (((hackage."ghc-prim")."0.5.2.0").revisions).default; + "bifunctors".revision = (((hackage."bifunctors")."5.5.4").revisions).default; + "bifunctors".flags.semigroups = true; + "bifunctors".flags.tagged = true; + "split".revision = (((hackage."split")."0.2.3.3").revisions).default; + "stm".revision = (((hackage."stm")."2.4.5.1").revisions).default; + "unix".revision = (((hackage."unix")."2.7.2.2").revisions).default; + "mtl".revision = (((hackage."mtl")."2.2.2").revisions).default; + "zlib".revision = (((hackage."zlib")."0.6.2").revisions).default; + "zlib".flags.non-blocking-ffi = false; + "zlib".flags.pkg-config = false; + "rts".revision = (((hackage."rts")."1.0").revisions).default; + "adjunctions".revision = (((hackage."adjunctions")."4.4").revisions).default; + "invariant".revision = (((hackage."invariant")."0.5.3").revisions).default; + "distributive".revision = (((hackage."distributive")."0.6").revisions).default; + "distributive".flags.semigroups = true; + "distributive".flags.tagged = true; + "scientific".revision = (((hackage."scientific")."0.3.6.2").revisions).default; + "scientific".flags.integer-simple = false; + "scientific".flags.bytestring-builder = false; + "parallel".revision = (((hackage."parallel")."3.2.2.0").revisions).default; + "deepseq".revision = (((hackage."deepseq")."1.4.3.0").revisions).default; + "random".revision = (((hackage."random")."1.1").revisions).default; + "optparse-applicative".revision = (((hackage."optparse-applicative")."0.14.3.0").revisions).default; + "network".revision = (((hackage."network")."3.1.0.0").revisions).default; + "async".revision = (((hackage."async")."2.2.1").revisions).default; + "async".flags.bench = false; + "conduit".revision = (((hackage."conduit")."1.3.1.1").revisions).default; + "semigroups".revision = (((hackage."semigroups")."0.19").revisions).default; + "semigroups".flags.bytestring = true; + "semigroups".flags.unordered-containers = true; + "semigroups".flags.text = true; + "semigroups".flags.tagged = true; + "semigroups".flags.containers = true; + "semigroups".flags.binary = true; + "semigroups".flags.hashable = true; + "semigroups".flags.transformers = true; + "semigroups".flags.deepseq = true; + "semigroups".flags.bytestring-builder = false; + "semigroups".flags.template-haskell = true; + "parsec".revision = (((hackage."parsec")."3.1.13.0").revisions).default; + "hsc2hs".revision = (((hackage."hsc2hs")."0.68.3").revisions).default; + "hsc2hs".flags.in-ghc-tree = false; + "directory".revision = (((hackage."directory")."1.3.1.5").revisions).default; + "transformers-compat".revision = (((hackage."transformers-compat")."0.6.5").revisions).default; + "transformers-compat".flags.five = false; + "transformers-compat".flags.generic-deriving = true; + "transformers-compat".flags.two = false; + "transformers-compat".flags.five-three = true; + "transformers-compat".flags.mtl = true; + "transformers-compat".flags.four = false; + "transformers-compat".flags.three = false; + "template-haskell".revision = (((hackage."template-haskell")."2.13.0.0").revisions).default; + "mono-traversable".revision = (((hackage."mono-traversable")."1.0.11.0").revisions).default; + "vector".revision = (((hackage."vector")."0.12.0.3").revisions).default; + "vector".flags.unsafechecks = false; + "vector".flags.internalchecks = false; + "vector".flags.wall = false; + "vector".flags.boundschecks = true; + "call-stack".revision = (((hackage."call-stack")."0.1.0").revisions).default; + "primitive".revision = (((hackage."primitive")."0.7.0.0").revisions).default; + "profunctors".revision = (((hackage."profunctors")."5.4").revisions).default; + "ansi-terminal".revision = (((hackage."ansi-terminal")."0.9.1").revisions).default; + "ansi-terminal".flags.example = false; + "tagged".revision = (((hackage."tagged")."0.8.6").revisions).default; + "tagged".flags.transformers = true; + "tagged".flags.deepseq = true; + "lens".revision = (((hackage."lens")."4.17.1").revisions).default; + "lens".flags.j = false; + "lens".flags.test-properties = true; + "lens".flags.old-inline-pragmas = false; + "lens".flags.test-templates = true; + "lens".flags.trustworthy = true; + "lens".flags.test-doctests = true; + "lens".flags.benchmark-uniplate = false; + "lens".flags.inlining = true; + "lens".flags.dump-splices = false; + "lens".flags.test-hunit = true; + "lens".flags.safe = false; + "unliftio-core".revision = (((hackage."unliftio-core")."0.1.2.0").revisions).default; + "containers".revision = (((hackage."containers")."0.5.11.0").revisions).default; + "integer-logarithms".revision = (((hackage."integer-logarithms")."1.0.3").revisions).default; + "integer-logarithms".flags.check-bounds = false; + "integer-logarithms".flags.integer-gmp = true; + "reflection".revision = (((hackage."reflection")."2.1.4").revisions).default; + "reflection".flags.slow = false; + "reflection".flags.template-haskell = true; + "streaming-commons".revision = (((hackage."streaming-commons")."0.2.1.0").revisions).default; + "streaming-commons".flags.use-bytestring-builder = false; + "bytestring".revision = (((hackage."bytestring")."0.10.8.2").revisions).default; + "ansi-wl-pprint".revision = (((hackage."ansi-wl-pprint")."0.6.9").revisions).default; + "ansi-wl-pprint".flags.example = false; + "StateVar".revision = (((hackage."StateVar")."1.1.1.1").revisions).default; + "typed-process".revision = (((hackage."typed-process")."0.2.4.0").revisions).default; + "contravariant".revision = (((hackage."contravariant")."1.5.1").revisions).default; + "contravariant".flags.semigroups = true; + "contravariant".flags.tagged = true; + "contravariant".flags.statevar = true; + "text".revision = (((hackage."text")."1.2.3.1").revisions).default; + "Cabal".revision = (((hackage."Cabal")."2.2.0.1").revisions).default; + "unordered-containers".revision = (((hackage."unordered-containers")."0.2.10.0").revisions).default; + "unordered-containers".flags.debug = false; + "base".revision = (((hackage."base")."4.11.1.0").revisions).default; + "comonad".revision = (((hackage."comonad")."5.0.5").revisions).default; + "comonad".flags.distributive = true; + "comonad".flags.test-doctests = true; + "comonad".flags.containers = true; + "time".revision = (((hackage."time")."1.8.0.2").revisions).default; + "vector-algorithms".revision = (((hackage."vector-algorithms")."0.8.0.1").revisions).default; + "vector-algorithms".flags.unsafechecks = false; + "vector-algorithms".flags.internalchecks = false; + "vector-algorithms".flags.llvm = false; + "vector-algorithms".flags.boundschecks = true; + "vector-algorithms".flags.bench = true; + "vector-algorithms".flags.properties = true; + "transformers".revision = (((hackage."transformers")."0.5.6.2").revisions).default; + "hashable".revision = (((hackage."hashable")."1.2.7.0").revisions).default; + "hashable".flags.sse2 = true; + "hashable".flags.integer-gmp = true; + "hashable".flags.sse41 = false; + "hashable".flags.examples = false; + "attoparsec".revision = (((hackage."attoparsec")."0.13.2.2").revisions).default; + "attoparsec".flags.developer = false; + "colour".revision = (((hackage."colour")."2.3.5").revisions).default; + "transformers-base".revision = (((hackage."transformers-base")."0.4.5.2").revisions).default; + "transformers-base".flags.orphaninstances = true; + "filepath".revision = (((hackage."filepath")."1.4.2").revisions).default; + "process".revision = (((hackage."process")."1.6.5.0").revisions).default; + "kan-extensions".revision = (((hackage."kan-extensions")."5.2").revisions).default; + "resourcet".revision = (((hackage."resourcet")."1.2.2").revisions).default; + "pretty".revision = (((hackage."pretty")."1.1.3.6").revisions).default; + "cabal-doctest".revision = (((hackage."cabal-doctest")."1.0.6").revisions).default; + "ghc-boot-th".revision = (((hackage."ghc-boot-th")."8.4.4").revisions).default; + "base-orphans".revision = (((hackage."base-orphans")."0.8.1").revisions).default; + "th-abstraction".revision = (((hackage."th-abstraction")."0.3.1.0").revisions).default; + "array".revision = (((hackage."array")."0.5.2.0").revisions).default; + "conduit-extra".revision = (((hackage."conduit-extra")."1.3.1.1").revisions).default; + "integer-gmp".revision = (((hackage."integer-gmp")."1.0.2.0").revisions).default; + }; + compiler = { + version = "8.4.4"; + nix-name = "ghc844"; + packages = { + "binary" = "0.8.5.1"; + "ghc-prim" = "0.5.2.0"; + "stm" = "2.4.5.1"; + "unix" = "2.7.2.2"; + "mtl" = "2.2.2"; + "rts" = "1.0"; + "deepseq" = "1.4.3.0"; + "parsec" = "3.1.13.0"; + "directory" = "1.3.1.5"; + "template-haskell" = "2.13.0.0"; + "containers" = "0.5.11.0"; + "bytestring" = "0.10.8.2"; + "text" = "1.2.3.1"; + "Cabal" = "2.2.0.1"; + "base" = "4.11.1.0"; + "time" = "1.8.0.2"; + "transformers" = "0.5.5.0"; + "filepath" = "1.4.2"; + "process" = "1.6.3.0"; + "pretty" = "1.1.3.6"; + "ghc-boot-th" = "8.4.4"; + "array" = "0.5.2.0"; + "integer-gmp" = "1.0.2.0"; + }; + }; + }; + extras = hackage: + { + packages = { pkga = ./.plan.nix/pkga.nix; pkgb = ./.plan.nix/pkgb.nix; }; + }; + } \ No newline at end of file diff --git a/test/tests.sh b/test/tests.sh index 5ed6fefc68..b850e2e07d 100755 --- a/test/tests.sh +++ b/test/tests.sh @@ -54,4 +54,11 @@ nix-shell $NIX_BUILD_ARGS \ --run 'cd cabal-simple && cabal new-build' echo >& 2 +printf "*** Checking shellFor works for a cabal project...\n" >& 2 +nix-shell $NIX_BUILD_ARGS \ + --pure ./default.nix \ + -A shell-for.env \ + --run 'cd shell-for && cabal new-build all' +echo >& 2 + printf "\n*** Finished successfully\n" >& 2