Skip to content

Add getComponent to project and package #1060

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 7 commits into from
Mar 4, 2021
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
10 changes: 5 additions & 5 deletions ci.nix
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ dimension "Nixpkgs version" nixpkgsVersions (nixpkgsName: nixpkgs-pin:
} // pkgs.lib.optionalAttrs runTests {
inherit (build) tests tools maintainer-scripts maintainer-script-cache;
} // pkgs.lib.optionalAttrs (ifdLevel >= 1) {
iserv-proxy = pkgs.ghc-extra-packages."${compiler-nix-name}".iserv-proxy.components.exes.iserv-proxy;
iserv-proxy = pkgs.ghc-extra-projects."${compiler-nix-name}".getComponent "iserv-proxy:exe:iserv-proxy";
} // pkgs.lib.optionalAttrs (ifdLevel >= 3) {
hello = (pkgs.haskell-nix.hackage-package { name = "hello"; version = "1.0.0.2"; inherit compiler-nix-name; }).components.exes.hello;
hello = (pkgs.haskell-nix.hackage-package { name = "hello"; version = "1.0.0.2"; inherit compiler-nix-name; }).getComponent "exe:hello";
});
}
//
Expand All @@ -102,10 +102,10 @@ dimension "Nixpkgs version" nixpkgsVersions (nixpkgsName: nixpkgs-pin:
inherit (build) tests;
}) // pkgs.lib.optionalAttrs (ifdLevel >= 2 && crossSystemName != "ghcjs") {
# GHCJS builds its own template haskell runner.
remote-iserv = pkgs.ghc-extra-packages."${compiler-nix-name}".remote-iserv.components.exes.remote-iserv;
iserv-proxy = pkgs.ghc-extra-packages."${compiler-nix-name}".iserv-proxy.components.exes.iserv-proxy;
remote-iserv = pkgs.ghc-extra-projects."${compiler-nix-name}".getComponent "remote-iserv:exe:remote-iserv";
iserv-proxy = pkgs.ghc-extra-projects."${compiler-nix-name}".getComponent "iserv-proxy:exe:iserv-proxy";
} // pkgs.lib.optionalAttrs (ifdLevel >= 3) {
hello = (pkgs.haskell-nix.hackage-package { name = "hello"; version = "1.0.0.2"; inherit compiler-nix-name; }).components.exes.hello;
hello = (pkgs.haskell-nix.hackage-package { name = "hello"; version = "1.0.0.2"; inherit compiler-nix-name; }).getComponent "exe:hello";
})
))
)
Expand Down
26 changes: 13 additions & 13 deletions compiler/ghcjs/ghcjs.nix
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,21 @@ let
all-ghcjs = pkgs.buildPackages.symlinkJoin {
name = "ghcjs-${ghcjsVersion}-symlinked";
paths = [
ghcjs.components.exes.ghcjs
ghcjs.components.exes.ghcjs-pkg
ghcjs.components.exes.ghcjs-boot
ghcjs.components.exes.ghcjs-dumparchive
(ghcjs.getComponent "exe:ghcjs")
(ghcjs.getComponent "exe:ghcjs-pkg")
(ghcjs.getComponent "exe:ghcjs-boot")
(ghcjs.getComponent "exe:ghcjs-dumparchive")
] ++ (if isGhcjs88
then [
ghcjs.components.exes.haddock
ghcjs.components.exes.private-ghcjs-run
ghcjs.components.exes.private-ghcjs-unlit
ghcjs.components.exes.private-ghcjs-hsc2hs
(ghcjs.getComponent "exe:haddock")
(ghcjs.getComponent "exe:private-ghcjs-run")
(ghcjs.getComponent "exe:private-ghcjs-unlit")
(ghcjs.getComponent "exe:private-ghcjs-hsc2hs")
]
else [
ghcjs.components.exes.haddock-ghcjs
ghcjs.components.exes.hsc2hs-ghcjs
ghcjs.components.exes.ghcjs-run
(ghcjs.getComponent "exe:haddock-ghcjs")
(ghcjs.getComponent "exe:hsc2hs-ghcjs")
(ghcjs.getComponent "exe:ghcjs-run")
]);
};
libexec = "libexec/${builtins.replaceStrings ["darwin" "i686"] ["osx" "i386"] pkgs.stdenv.buildPlatform.system}-${ghc.name}/ghcjs-${ghcVersion}";
Expand Down Expand Up @@ -68,9 +68,9 @@ let
lndir ${all-ghcjs}/bin $out/bin
chmod -R +w $out/bin
rm $out/bin/ghcjs-boot
cp ${ghcjs.components.exes.ghcjs-boot}/bin/ghcjs-boot $out/bin
cp ${ghcjs.getComponent "exe:ghcjs-boot"}/bin/ghcjs-boot $out/bin
rm $out/bin/haddock
cp ${ghcjs.components.exes.haddock}/bin/haddock $out/bin
cp ${ghcjs.getComponent "exe:haddock"}/bin/haddock $out/bin

wrapProgram $out/bin/ghcjs --add-flags "-B$out/lib"
wrapProgram $out/bin/haddock --add-flags "-B$out/lib"
Expand Down
55 changes: 45 additions & 10 deletions lib/call-cabal-project-to-nix.nix
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ in

let
forName = pkgs.lib.optionalString (name != null) (" for " + name);
nameAndSuffix = suffix: if name == null then suffix else name + "-" + suffix;

ghc' =
if ghcOverride != null
Expand Down Expand Up @@ -378,18 +379,40 @@ let
else null;
} // pkgs.lib.optionalAttrs (checkMaterialization != null) {
inherit checkMaterialization;
}) (pkgs.evalPackages.runCommand (if name == null then "plan-to-nix-pkgs" else name + "-plan-to-nix-pkgs") {
}) (pkgs.evalPackages.runCommand (nameAndSuffix "plan-to-nix-pkgs") {
nativeBuildInputs = [ nix-tools dummy-ghc dummy-ghc-pkg cabal-install pkgs.evalPackages.rsync ];
# Needed or stack-to-nix will die on unicode inputs
LOCALE_ARCHIVE = pkgs.lib.optionalString (pkgs.evalPackages.stdenv.buildPlatform.libc == "glibc") "${pkgs.evalPackages.glibcLocales}/lib/locale/locale-archive";
LANG = "en_US.UTF-8";
meta.platforms = pkgs.lib.platforms.all;
preferLocalBuild = false;
outputs = [
"out" # The results of plan-to-nix
"json" # The `plan.json` file generated by cabal and used for `plan-to-nix` input
"freeze" # The `cabal.project.freeze` file created by `cabal v2-freeze`
"out" # The results of plan-to-nix
# These two output will be present if in cabal configure failed.
# They are used to provide passthru.json and passthru.freeze that
# check first for cabal configure failure.
"maybeJson" # The `plan.json` file generated by cabal and used for `plan-to-nix` input
"maybeFreeze" # The `cabal.project.freeze` file created by `cabal v2-freeze`
];
passthru =
let
checkCabalConfigure = ''
if [[ -f ${plan-nix}/cabal-configure.out ]]; then
cat ${plan-nix}/cabal-configure.out
exit 1
fi
'';
in {
# These check for cabal configure failure
json = pkgs.evalPackages.runCommand (nameAndSuffix "plan-json") {} ''
${checkCabalConfigure}
cp ${plan-nix.maybeJson} $out
'';
freeze = pkgs.evalPackages.runCommand (nameAndSuffix "plan-freeze") {} ''
${checkCabalConfigure}
cp ${plan-nix.maybeFreeze} $out
'';
};
} ''
tmp=$(mktemp -d)
cd $tmp
Expand Down Expand Up @@ -430,12 +453,14 @@ let
export SSL_CERT_FILE=${cacert}/etc/ssl/certs/ca-bundle.crt
export GIT_SSL_CAINFO=${cacert}/etc/ssl/certs/ca-bundle.crt

mkdir -p $out

# Using `cabal v2-freeze` will configure the project (since
# it is not configured yet), taking the existing `cabal.project.freeze`
# file into account. Then it "writes out a freeze file which
# records all of the versions and flags that are picked" (from cabal docs).
echo "Using index-state ${index-state-found}"
HOME=${
if(HOME=${
# This creates `.cabal` directory that is as it would have
# been at the time `cached-index-state`. We may include
# some packages that will be excluded by `index-state-found`
Expand All @@ -460,14 +485,12 @@ let
--enable-benchmarks \
${pkgs.lib.optionalString (ghc.targetPrefix == "js-unknown-ghcjs-")
"--ghcjs --with-ghcjs=js-unknown-ghcjs-ghc --with-ghcjs-pkg=js-unknown-ghcjs-ghc-pkg"} \
${configureArgs}
${configureArgs} 2>&1 | tee -a cabal-configure.out); then

cp cabal.project.freeze $freeze
cp cabal.project.freeze $maybeFreeze
# Not needed any more (we don't want it to wind up in the $out hash)
rm cabal.project.freeze

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
Expand All @@ -489,7 +512,7 @@ let
(cd $out${subDir'} && plan-to-nix --full --plan-json $tmp${subDir'}/dist-newstyle/cache/plan.json -o .)

# Make the plan.json file available in case we need to debug plan-to-nix
cp $tmp${subDir'}/dist-newstyle/cache/plan.json $json
cp $tmp${subDir'}/dist-newstyle/cache/plan.json $maybeJson

# Remove the non nix files ".project" ".cabal" "package.yaml" files
# as they should not be in the output hash (they may change slightly
Expand All @@ -504,6 +527,18 @@ let

# move pkgs.nix to default.nix ensure we can just nix `import` the result.
mv $out${subDir'}/pkgs.nix $out${subDir'}/default.nix
else
# When cabal configure fails copy the output that we captured above and
# use `failed-cabal-configure.nix` to make a suitable derviation with.
cp cabal-configure.out $out
cp ${./failed-cabal-configure.nix} $out/default.nix

# These should only be used indirectly by `passthru.json` and `passthru.freeze`.
# Those derivations will check for `cabal-configure.out` out first to see if
# it is ok to use these files.
echo "Cabal configure failed see $out/cabal-configure.out for details" > $maybeJson
echo "Cabal configure failed see $out/cabal-configure.out for details" > $maybeFreeze
fi
'');
in {
projectNix = plan-nix;
Expand Down
6 changes: 6 additions & 0 deletions lib/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ in {
benchmarks = "bench";
};

# For looking up the components attribute based on the cabal component type
prefixComponent =
lib.listToAttrs (
lib.mapAttrsToList (value: name: { inherit name value; })
componentPrefix);

applyComponents = f: config:
let
comps = config.components;
Expand Down
9 changes: 9 additions & 0 deletions lib/failed-cabal-configure.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let
configurationError = ./cabal-configure.out;
in
# Trace the error output to make sure the user has a chance to see it
# (even if the choose not to build anything from the project)
__trace ''
ERROR: cabal configure failed with:
${__readFile configurationError}
'' { inherit configurationError; }
23 changes: 19 additions & 4 deletions overlays/bootstrap.nix
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ in {
cabal-install = final.evalPackages.haskell-nix.cabal-install-unchecked.${compiler-nix-name};
nix-tools = final.evalPackages.haskell-nix.nix-tools-unchecked.${compiler-nix-name};
materialized = ../materialized + "/${compiler-nix-name}/cabal-install";
} // args)).components.exes.cabal;
} // args)).getComponent "exe:cabal";
nix-tools-set = { compiler-nix-name, ... }@args:
let
project =
Expand Down Expand Up @@ -617,7 +617,22 @@ in {
];
}];
} // args);
exes = project.nix-tools.components.exes // project.hpack.components.exes;
exes =
let
package = project.getPackage "nix-tools";
in (builtins.map (name: package.getComponent "exe:${name}") [
"cabal-to-nix"
"hashes-to-nix"
"plan-to-nix"
"hackage-to-nix"
"lts-to-nix"
"stack-to-nix"
"truncate-index"
"stack-repos"
"cabal-name"
]) ++ [
(project.getComponent "hpack:exe:hpack")
];
tools = [
final.buildPackages.nix
# Double buildPackages is intentional, see comment in lib/default.nix for details.
Expand All @@ -626,7 +641,7 @@ in {
in
final.symlinkJoin {
name = "nix-tools";
paths = builtins.attrValues exes;
paths = exes;
buildInputs = [ final.makeWrapper ];
meta.platforms = final.lib.platforms.all;
# We wrap the -to-nix executables with the executables from `tools` (e.g. nix-prefetch-git)
Expand Down Expand Up @@ -774,7 +789,7 @@ in {
version = "1.24.4";
inherit ghcOverride nix-tools cabal-install index-state;
materialized = ../materialized/bootstrap + "/${buildBootstrapper.compilerNixName}/hscolour";
} // args)).components.exes.HsColour;
} // args)).getComponent "exe:HsColour";
hscolour = bootstrap.packages.hscolour-tool {};
hscolour-unchecked = bootstrap.packages.hscolour-tool { checkMaterialization = false; };
};
Expand Down
74 changes: 67 additions & 7 deletions overlays/haskell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ final: prev: {
hackage-package =
{ name, compiler-nix-name, ... }@args':
let args = { caller = "hackage-package"; } // args';
in (hackage-project args).hsPkgs.${name};
in (hackage-project args).getPackage name;
hackage-project =
{ name
, compiler-nix-name
Expand Down Expand Up @@ -500,11 +500,19 @@ final: prev: {
let
args = { caller = "cabalProject'"; } // args';
callProjectResults = callCabalProjectToNix args;
in let pkg-set = mkCabalProjectPkgSet
{ inherit compiler-nix-name;
plan-pkgs = importAndFilterProject {
inherit (callProjectResults) projectNix sourceRepos src;
plan-pkgs = importAndFilterProject {
inherit (callProjectResults) projectNix sourceRepos src;
};
pkg-set = if plan-pkgs ? configurationError
then {
inherit (plan-pkgs) configurationError;
config = {
compiler.nix-name = compiler-nix-name;
hsPkgs = {};
};
}
else mkCabalProjectPkgSet
{ inherit compiler-nix-name plan-pkgs;
pkg-def-extras = args.pkg-def-extras or [];
modules = (args.modules or [])
++ final.lib.optional (args ? ghcOverride || args ? ghc)
Expand All @@ -514,7 +522,7 @@ final: prev: {
extra-hackages = args.extra-hackages or [];
};

project = addProjectAndPackageAttrs rec {
project = addProjectAndPackageAttrs rec {
inherit (pkg-set.config) hsPkgs;
inherit pkg-set;
plan-nix = callProjectResults.projectNix;
Expand All @@ -535,7 +543,7 @@ final: prev: {
final.lib.fix (project':
let project = project' // { recurseForDerivations = false; };
in rawProject // rec {
hsPkgs = final.lib.mapAttrs (n: package':
hsPkgs = final.lib.mapAttrs (packageName: package':
if package' == null
then null
else
Expand All @@ -548,6 +556,16 @@ final: prev: {
) package'.components;
inherit project;

# Look up a component in the package based on ctype:name
getComponent = componentName:
let m = builtins.match "(lib|flib|exe|test|bench):([^:]*)" componentName;
in
assert final.lib.asserts.assertMsg (m != null)
"Invalid package component name ${componentName}. Expected it to start with one of lib: flib: exe: test: or bench:";
if builtins.elemAt m 0 == "lib" && builtins.elemAt m 1 == packageName
then components.library
else components.${haskellLib.prefixComponent.${builtins.elemAt m 0}}.${builtins.elemAt m 1};

coverageReport = haskellLib.coverageReport (rec {
name = package.identifier.name + "-" + package.identifier.version;
library = if components ? library then components.library else null;
Expand All @@ -568,6 +586,48 @@ final: prev: {
rawProject.projectFunction pkgs.haskell-nix rawProject.projectArgs
) final.pkgsCross) // { recurseForDerivations = false; };

# Like `.hsPkgs.${packageName}` but when compined with `getComponent` any
# cabal configure errors are defered until the components derivation builds.
getPackage = packageName:
if rawProject.pkg-set ? configurationError
then
# A minimal proxy for a package when cabal configure failed
let package = {
# Including the project so that things like:
# (p.getPackage "hello").project.tool "hlint" "latest"
# will still work even if "hello" failed to configure.
inherit project;

# Defer configure time errors for the library component
# (p.getPackage "hello").components.library
components.library = package.getComponent "lib:${packageName}";

# This procide a derivation (even though the component may
# not exist at all). The derivation will never build
# and simple outputs the result of cabal configure.
getComponent = componentName:
final.evalPackages.runCommand "cabal-configure-error" {
passthru = {
inherit project package;
};
} ''
cat ${rawProject.pkg-set.configurationError}
echo Unable to find component ${packageName}:${componentName} \
due to the above cabal configuration error
exit 1
'';
};
in package
else project.hsPkgs.${packageName};

# Look a component in the project based on `pkg:ctype:name`
getComponent = componentName:
let m = builtins.match "([^:]*):(lib|flib|exe|test|bench):([^:]*)" componentName;
in
assert final.lib.asserts.assertMsg (m != null)
"Invalid package component name ${componentName}. Expected package:ctype:component (where ctype is one of lib, flib, exe, test, or bench)";
(getPackage (builtins.elemAt m 0)).getComponent "${builtins.elemAt m 1}:${builtins.elemAt m 2}";

# Helper function that can be used to make a Nix Flake out of a project
# by including a flake.nix. See docs/tutorials/getting-started-flakes.md
# for an example flake.nix file.
Expand Down
2 changes: 1 addition & 1 deletion overlays/tools.nix
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ in { haskell-nix = prev.haskell-nix // {
{ configureArgs = "--disable-benchmarks --disable-tests"; }
// args
// { name = final.haskell-nix.toolPackageName.${name} or name; }))
.components.exes."${final.haskell-nix.packageToolName.${name} or name}";
.getComponent "exe:${final.haskell-nix.packageToolName.${name} or name}";

tool = compiler-nix-name: name: versionOrArgs:
let
Expand Down
4 changes: 2 additions & 2 deletions scripts/check-compiler-materialization/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
let
eval = (import ../../. {}).pkgs;
linux = (import ../../. { checkMaterialization = true; system = "x86_64-linux"; }).pkgs;
darwin = (import ../../. { checkMaterialization = true; system = "x86_64-darwin"; }).pkgs;
darwin = (import ../../. { checkMaterialization = true; system = "x86_64-darwin"; }).pkgs-unstable;
in eval.linkFarm "check-${compiler-nix-name}" [
# This set of derivations should be enough to ensure all the materialized files for a
# given GHC version are checked.
{ name = "linux-cabal-install"; path = linux.haskell-nix.cabal-install.${compiler-nix-name}; }
# { name = "darwin-cabal-install"; path = darwin.haskell-nix.cabal-install.${compiler-nix-name}; }
{ name = "darwin-cabal-install"; path = darwin.haskell-nix.cabal-install.${compiler-nix-name}; }
{ name = "linux-nix-tools"; path = linux.haskell-nix.nix-tools.${compiler-nix-name}; }
{ name = "linux"; path = linux.ghc-extra-projects.${compiler-nix-name}.plan-nix; }
# In some cased you may need comment out one or more of these if the GHC version needed cannot be built.
Expand Down
Loading