Skip to content

Add shellFor multi-package development environments #121

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 6 commits into from
May 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 10 additions & 114 deletions builder/comp-builder.nix
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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 <<EOF
package-db $out/package.conf.d
EOF
${lib.concatMapStringsSep "\n" (p: envDep "--package-db ${p.components.library or p}/package.conf.d" p.identifier.name) component.depends}
${lib.concatMapStringsSep "\n" (envDep "") (lib.remove "ghc" nonReinstallablePkgs)}

'' + lib.optionalString component.doExactConfig ''
echo "--exact-configuration" >> $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"
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -239,7 +136,6 @@ stdenv.mkDerivation ({

nativeBuildInputs =
[shellWrappers buildPackages.removeReferencesTo]
++ lib.optional (component.pkgconfig != []) pkgconfig
++ executableToolDepends;

SETUP_HS = setup + /bin/Setup;
Expand Down
110 changes: 27 additions & 83 deletions builder/default.nix
Original file line number Diff line number Diff line change
@@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleting shellHook from the comp-builder.nix input will break my (already merged) changes for configurable shellHooks per-package: #117

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code has been moved not deleted. You should find shellHook in hspkg-builder.nix.


, ...
}@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;
};
}
90 changes: 90 additions & 0 deletions builder/hspkg-builder.nix
Original file line number Diff line number Diff line change
@@ -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;
}
Loading