shellFor: Refactor for consistency and cross

This makes it work like work-on-multi from Reflex Platform. In
particular, rather than making `.env` from `shellFor`, we make `.env`
the primitive, and `shellFor` works by combining together the arguments
of all the packages to `generic-builder` and taking the `.env` of the
resulting mashup-package.

There are 2 benefits of this:

1. The dependency logic is deduplicated. generic builder just concatted
   lists, whereas all the envs until now would sieve apart haskell and
   system build inputs. Now, they both decide haskell vs system the same
   way: according to the argument list and without reflection.
   Consistency is good, especially because it mean that if the build
   works, the shell is more likely to work.

2. Cross is handled better. For native builds, because the
   `ghcWithPackages` calls would shadow, we through both the regular
   component (lib, exe, test, bench) haskell deps and Setup.hs haskell
   deps in the same `ghcWithPackages` call. But for cross builds we use
   `buildPackages.ghcWithPackages` to get the setup deps. This ensures
   everything works correctly.
This commit is contained in:
Jacquin Mininger
2020-01-17 10:46:29 -05:00
committed by John Ericson
parent ba9066abca
commit 7d67db3919
4 changed files with 186 additions and 58 deletions
@@ -38,12 +38,12 @@ let
inherit (stdenv) buildPlatform hostPlatform;
inherit (stdenv.lib) fix' extends makeOverridable;
inherit (haskellLib) overrideCabal getBuildInputs;
inherit (haskellLib) overrideCabal;
mkDerivationImpl = pkgs.callPackage ./generic-builder.nix {
inherit stdenv;
nodejs = buildPackages.nodejs-slim;
inherit (self) buildHaskellPackages ghc shellFor;
inherit (self) buildHaskellPackages ghc ghcWithHoogle ghcWithPackages;
inherit (self.buildHaskellPackages) jailbreak-cabal;
hscolour = overrideCabal self.buildHaskellPackages.hscolour (drv: {
isLibrary = false;
@@ -255,6 +255,8 @@ in package-set { inherit pkgs stdenv callPackage; } self // {
# packages themselves. Using nix-shell on this derivation will
# give you an environment suitable for developing the listed
# packages with an incremental tool like cabal-install.
# In addition to the "packages" arg and "withHoogle" arg, anything that
# can be passed into stdenv.mkDerivation can be included in the input attrset
#
# # default.nix
# with import <nixpkgs> {};
@@ -265,9 +267,11 @@ in package-set { inherit pkgs stdenv callPackage; } self // {
# })
#
# # shell.nix
# let pkgs = import <nixpkgs> {} in
# (import ./.).shellFor {
# packages = p: [p.frontend p.backend p.common];
# withHoogle = true;
# buildInputs = [ pkgs.python ];
# }
#
# -- cabal.project
@@ -277,49 +281,41 @@ in package-set { inherit pkgs stdenv callPackage; } self // {
# common/
#
# bash$ nix-shell --run "cabal new-build all"
# bash$ nix-shell --run "python"
shellFor = { packages, withHoogle ? false, ... } @ args:
let
selected = packages self;
combinedPackageFor = packages:
let
selected = packages self;
packageInputs = map getBuildInputs selected;
pname = if pkgs.lib.length selected == 1
then (pkgs.lib.head selected).name
else "packages";
name = if pkgs.lib.length selected == 1
then "ghc-shell-for-${(pkgs.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.
combinedPackages = pkgs.lib.filter
(input: pkgs.lib.all (p: input.outPath or null != p.outPath) selected);
# 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.
haskellInputs = pkgs.lib.filter
(input: pkgs.lib.all (p: input.outPath != p.outPath) selected)
(pkgs.lib.concatMap (p: p.haskellBuildInputs) packageInputs);
systemInputs = pkgs.lib.concatMap (p: p.systemBuildInputs) packageInputs;
# Returns an attrset containing a combined list packages' inputs for each
# stage of the build process
packageInputs = pkgs.lib.zipAttrsWith
(_: pkgs.lib.concatMap combinedPackages)
(map (p: p.getCabalDeps) selected);
withPackages = if withHoogle then self.ghcWithHoogle else self.ghcWithPackages;
ghcEnv = withPackages (p: haskellInputs);
nativeBuildInputs = pkgs.lib.concatMap (p: p.nativeBuildInputs) selected;
genericBuilderArgs = {
inherit pname;
version = "0";
license = null;
} // packageInputs;
ghcCommand' = if ghc.isGhcjs or false then "ghcjs" else "ghc";
ghcCommand = "${ghc.targetPrefix}${ghcCommand'}";
ghcCommandCaps= pkgs.lib.toUpper ghcCommand';
in self.mkDerivation genericBuilderArgs;
mkDrvArgs = builtins.removeAttrs args ["packages" "withHoogle"];
in pkgs.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 = pkgs.lib.optionalString (stdenv.hostPlatform.libc == "glibc") "${buildPackages.glibcLocales}/lib/locale/locale-archive";
"NIX_${ghcCommandCaps}" = "${ghcEnv}/bin/${ghcCommand}";
"NIX_${ghcCommandCaps}PKG" = "${ghcEnv}/bin/${ghcCommand}-pkg";
# TODO: is this still valid?
"NIX_${ghcCommandCaps}_DOCDIR" = "${ghcEnv}/share/doc/ghc/html";
"NIX_${ghcCommandCaps}_LIBDIR" = if ghc.isHaLVM or false
then "${ghcEnv}/lib/HaLVM-${ghc.version}"
else "${ghcEnv}/lib/${ghcCommand}-${ghc.version}";
envFuncArgs = builtins.removeAttrs args [ "packages" ];
in (combinedPackageFor packages).env.overrideAttrs (old: envFuncArgs // {
nativeBuildInputs = old.nativeBuildInputs ++ envFuncArgs.nativeBuildInputs or [];
buildInputs = old.buildInputs ++ envFuncArgs.buildInputs or [];
});
ghc = ghc // {