Skip to content

Commit 61fbe40

Browse files
authored
Use unit id for package key (#2239)
Currently haskell.nix uses the package name as the key for `hsPkgs`. This means that only one package with a given name can exist for a given plan. When the cabal planner makes a plan it often includes more than one version of a given package. For instance if a package is needed for a `build-tool-depends` it is likely that it may have requirements that do not match the rest of the project. When there are two versions of the same package in the plan haskell.nix currently chooses the most recent one. This is often the correct choice for the main plan (though it may not always be), but it can sometimes be the wrong choice for the `build-tool-depends`. This PR aims to resolve this issue by using the unit ID from the `plan.json` file as the key for `hsPkgs`. This means that we can much more closely match the plan. * Use the `plan.json` as much as possible (including dependencies and cabal flag settings). * Fall back on existing sources for information not in `plan.json`. * Include mappings from `hsPkgs.${pkg-name}` to unit ID based entries. * Support overrides of the form `packages.${pkg-name}...` (these are applied to all the versions of the package). * Per-component `pre-existing` packages based on the `plan.json` dependencies.
1 parent a921f73 commit 61fbe40

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+8535
-181
lines changed

.github/workflows/pipeline.yml

+15-24
Original file line numberDiff line numberDiff line change
@@ -139,49 +139,40 @@ jobs:
139139

140140
hydra-ifdLevel-0-and-1:
141141
runs-on: [self-hosted, linux]
142+
timeout-minutes: 20
142143
steps:
143144
- uses: actions/checkout@v4
144145
- name: "Check that jobset will evaluate in Hydra at ifdLevel 0 and 1"
145146
run: |
146147
nix-build build.nix -A maintainer-scripts.check-hydra -o check-hydra.sh
147-
./check-hydra.sh --arg ifdLevel 0
148-
./check-hydra.sh --arg ifdLevel 1
148+
sed -i 's/runningHydraEvalTest = false;/runningHydraEvalTest = true;/' flake.nix
149+
sed -i 's/ifdLevel = 3;/ifdLevel = 0;/' flake.nix
150+
./check-hydra.sh
151+
sed -i 's/ifdLevel = 0;/ifdLevel = 1;/' flake.nix
152+
./check-hydra.sh
149153
150154
hydra-ifdLevel-2:
151155
runs-on: [self-hosted, linux]
156+
timeout-minutes: 20
152157
steps:
153158
- uses: actions/checkout@v4
154159
- name: "Check that jobset will evaluate in Hydra at ifdLevel 2"
155160
run: |
156161
nix-build build.nix -A maintainer-scripts.check-hydra -o check-hydra.sh
157-
./check-hydra.sh --arg ifdLevel 2
162+
sed -i 's/runningHydraEvalTest = false;/runningHydraEvalTest = true;/' flake.nix
163+
sed -i 's/ifdLevel = 3;/ifdLevel = 2;/' flake.nix
164+
./check-hydra.sh
158165
159-
hydra-ifdLevel-3-and-ghc-8-10:
166+
hydra-ifdLevel-3:
160167
runs-on: [self-hosted, linux]
168+
timeout-minutes: 30
161169
steps:
162170
- uses: actions/checkout@v4
163-
- name: "Check that jobset will evaluate in Hydra at ifdLevel 3 and ghc 8.10"
171+
- name: "Check that jobset will evaluate in Hydra at ifdLevel 3"
164172
run: |
165173
nix-build build.nix -A maintainer-scripts.check-hydra -o check-hydra.sh
166-
./check-hydra.sh --arg ifdLevel 3 --arg include 'x: __substring 0 6 x == "ghc810"'
167-
168-
hydra-ifdLevel-3-and-ghc-9-2:
169-
runs-on: [self-hosted, linux]
170-
steps:
171-
- uses: actions/checkout@v4
172-
- name: "Check that jobset will evaluate in Hydra at ifdLevel 3 and ghc 9.2"
173-
run: |
174-
nix-build build.nix -A maintainer-scripts.check-hydra -o check-hydra.sh
175-
./check-hydra.sh --arg ifdLevel 3 --arg include 'x: __substring 0 5 x == "ghc92"'
176-
177-
hydra-ifdLevel-3-and-not-ghc-8-10-or-9-2:
178-
runs-on: [self-hosted, linux]
179-
steps:
180-
- uses: actions/checkout@v4
181-
- name: "Check that jobset will evaluate in Hydra at ifdLevel 3 and not (ghc 8.10 or ghc 9.2)"
182-
run: |
183-
nix-build build.nix -A maintainer-scripts.check-hydra -o check-hydra.sh
184-
./check-hydra.sh --arg ifdLevel 3 --arg include 'x: !(__substring 0 6 x == "ghc810" || __substring 0 5 x == "ghc92")'
174+
sed -i 's/runningHydraEvalTest = false;/runningHydraEvalTest = true;/' flake.nix
175+
./check-hydra.sh
185176
186177
closure-size:
187178
runs-on: [self-hosted, linux]

builder/comp-builder.nix

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ let self =
2828
# (not just the one we are building).
2929
# Enable for tests in packages that use cabal-doctest.
3030
( haskellLib.isTest componentId &&
31-
lib.any (x: x.identifier.name or "" == "cabal-doctest") package.setup-depends
31+
lib.any (x: x.identifier.name or "" == "cabal-doctest") (package.setup-depends ++ setup.config.depends or [])
3232
)
3333
, allComponent # Used when `configureAllComponents` is set to get a suitable configuration.
3434

builder/make-config-files.nix

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
let
66
# Sort and remove duplicates from nonReinstallablePkgs.
77
# That way changes to the order of nonReinstallablePkgs does not require rebuilds.
8-
nonReinstallablePkgs' = __attrNames (lib.genAttrs nonReinstallablePkgs (x: x));
8+
nonReinstallablePkgs' = __attrNames (lib.genAttrs (component.pre-existing or [] ++ nonReinstallablePkgs) (x: x));
99

1010
ghc = if enableDWARF then defaults.ghc.dwarf else defaults.ghc;
1111

builder/shell-for.nix

+6-4
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ let
6868
selectedComponents =
6969
lib.filter isSelectedComponent (lib.attrValues transitiveDependenciesComponents);
7070

71-
allHsPkgsComponents = lib.concatMap haskellLib.getAllComponents (builtins.attrValues hsPkgs);
71+
allHsPkgsComponents = lib.concatMap haskellLib.getAllComponents
72+
(lib.filter (x: !(x.isRedirect or false)) (builtins.attrValues hsPkgs));
7273

7374
# Given a list of `depends`, removes those which are selected components
7475
removeSelectedInputs =
@@ -114,9 +115,10 @@ let
114115
# Set up a "dummy" component to use with ghcForComponent.
115116
component = {
116117
depends = packageInputs;
117-
libs = [];
118-
pkgconfig = [];
119-
frameworks = [];
118+
pre-existing = lib.concatMap (x: (haskellLib.dependToLib x).config.pre-existing or []) packageInputs;
119+
libs = lib.concatMap (x: (haskellLib.dependToLib x).config.libs or []) packageInputs;
120+
pkgconfig = lib.concatMap (x: (haskellLib.dependToLib x).config.pkgconfig or []) packageInputs;
121+
frameworks = lib.concatMap (x: (haskellLib.dependToLib x).config.frameworks or []) packageInputs;
120122
doExactConfig = false;
121123
};
122124
configFiles = makeConfigFiles {

changelog.md

+76
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,82 @@
11
This file contains a summary of changes to Haskell.nix and `nix-tools`
22
that will impact users.
33

4+
## Sep 17, 2024
5+
6+
Cabal projects now use the more granular Unit IDs from plan.json
7+
to identify packages. This allows for different versions of a
8+
package to be used when building `built-tool-depends` and setup
9+
dependencies.
10+
11+
Overrides in the `modules` argument apply to all versions of
12+
the package. However to make this work we needed to make
13+
each `packages.somepackage` an option (instead of using an
14+
`attrsOf` the submodule type).
15+
16+
It is now an error to override a package that is not in the
17+
plan. This can be a problem if different GHC versions, target
18+
platforms, or cabal flag settings cause the package to be
19+
excluded from the plan. Adding `package-keys` can tell
20+
haskell.nix to include the option anyway:
21+
22+
```
23+
modules = [{
24+
# Tell haskell.nix that `somepackage` may exist.
25+
package-keys = ["somepackage"];
26+
# Now the following will not cause an error even
27+
# if `somepackage` is not in the plan
28+
packages.somepackage.flags.someflag = true;
29+
}];
30+
```
31+
32+
There is a helper function you can use to add `package-keys`
33+
for all of the `builtins.attrNames` of `packages`:
34+
35+
```
36+
modules = [(pkgs.haskell-nix.haskellLib.addPackageKeys {
37+
packages.somepackage.flags.someflag = true;
38+
})];
39+
```
40+
41+
Do not use the module's `pkgs` arg to look `addPackageKeys` up
42+
though or it will result an `infinite recursion` error.
43+
44+
Code that uses `options.packages` will also need to be updated.
45+
For instance the following code that uses `options.packages`
46+
to set `--Werror` for local packages:
47+
48+
```
49+
({ lib, ... }: {
50+
options.packages = lib.mkOption {
51+
type = lib.types.attrsOf (lib.types.submodule (
52+
{ config, lib, ... }:
53+
lib.mkIf config.package.isLocal
54+
{
55+
configureFlags = [ "--ghc-option=-Werror"];
56+
}
57+
));
58+
};
59+
})
60+
```
61+
62+
Now needs to do it for each of the entry in `config.package-keys`
63+
instead of using `attrsOf`:
64+
65+
```
66+
({ config, lib, ... }: {
67+
options.packages = lib.genAttrs config.package-keys (_:
68+
lib.mkOption {
69+
type = lib.types.submodule (
70+
{ config, lib, ... }:
71+
lib.mkIf config.package.isLocal
72+
{
73+
configureFlags = [ "--ghc-option=-Werror"];
74+
}
75+
);
76+
});
77+
})
78+
```
79+
480
## Jun 5, 2024
581

682
Haskell.nix now respects the `pre-existing` packages selected

flake.lock

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

+8-5
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"hls-2.6" = { url = "github:haskell/haskell-language-server/2.6.0.0"; flake = false; };
2323
"hls-2.7" = { url = "github:haskell/haskell-language-server/2.7.0.0"; flake = false; };
2424
"hls-2.8" = { url = "github:haskell/haskell-language-server/2.8.0.0"; flake = false; };
25-
"hls-2.9" = { url = "github:haskell/haskell-language-server/2.9.0.0"; flake = false; };
25+
"hls-2.9" = { url = "github:haskell/haskell-language-server/2.9.0.1"; flake = false; };
2626
hydra.url = "hydra";
2727
hackage = {
2828
url = "github:input-output-hk/hackage.nix";
@@ -91,6 +91,7 @@
9191
callFlake = import flake-compat;
9292

9393
ifdLevel = 3;
94+
runningHydraEvalTest = false;
9495
compiler = "ghc928";
9596
config = import ./config.nix;
9697

@@ -108,9 +109,10 @@
108109
# systems supported by haskell.nix
109110
systems = [
110111
"x86_64-linux"
112+
] ++ (if runningHydraEvalTest then [] else [
111113
"x86_64-darwin"
112114
"aarch64-darwin"
113-
];
115+
]);
114116

115117
nixpkgsArgs = {
116118
inherit config;
@@ -282,13 +284,14 @@
282284
traceHydraJobs (lib.recursiveUpdate flake (lib.optionalAttrs (ifdLevel > 2) {
283285
hydraJobs.nix-tools = pkgs.releaseTools.aggregate {
284286
name = "nix-tools";
285-
constituents = [
287+
constituents = (if runningHydraEvalTest then [] else [
286288
"aarch64-darwin.nix-tools.static.zipped.nix-tools-static"
287289
"x86_64-darwin.nix-tools.static.zipped.nix-tools-static"
288-
"x86_64-linux.nix-tools.static.zipped.nix-tools-static"
289-
"x86_64-linux.nix-tools.static.zipped.nix-tools-static-arm64"
290290
"aarch64-darwin.nix-tools.static.zipped.nix-tools-static-no-ifd"
291291
"x86_64-darwin.nix-tools.static.zipped.nix-tools-static-no-ifd"
292+
]) ++ [
293+
"x86_64-linux.nix-tools.static.zipped.nix-tools-static"
294+
"x86_64-linux.nix-tools.static.zipped.nix-tools-static-arm64"
292295
"x86_64-linux.nix-tools.static.zipped.nix-tools-static-no-ifd"
293296
"x86_64-linux.nix-tools.static.zipped.nix-tools-static-arm64-no-ifd"
294297
(writeText "gitrev" (self.rev or "0000000000000000000000000000000000000000"))

lib/call-cabal-project-to-nix.nix

+13-9
Original file line numberDiff line numberDiff line change
@@ -538,11 +538,11 @@ let
538538
'';
539539
};
540540

541-
plan-nix = materialize ({
541+
plan-json = materialize ({
542542
inherit materialized;
543543
sha256 = plan-sha256;
544544
sha256Arg = "plan-sha256";
545-
this = "project.plan-nix" + (if name != null then " for ${name}" else "");
545+
this = "project.plan-json" + (if name != null then " for ${name}" else "");
546546
} // pkgs.lib.optionalAttrs (checkMaterialization != null) {
547547
inherit checkMaterialization;
548548
}) (evalPackages.runCommand (nameAndSuffix "plan-to-nix-pkgs") {
@@ -564,7 +564,6 @@ let
564564
# These two output will be present if in cabal configure failed.
565565
# They are used to provide passthru.json and passthru.freeze that
566566
# check first for cabal configure failure.
567-
"json" # The `plan.json` file generated by cabal and used for `plan-to-nix` input
568567
"freeze" # The `cabal.project.freeze` file created by `cabal v2-freeze`
569568
];
570569
} ''
@@ -619,7 +618,7 @@ let
619618
export SSL_CERT_FILE=${cacert}/etc/ssl/certs/ca-bundle.crt
620619
export GIT_SSL_CAINFO=${cacert}/etc/ssl/certs/ca-bundle.crt
621620
622-
CABAL_DIR=${
621+
export CABAL_DIR=${
623622
# This creates `.cabal` directory that is as it would have
624623
# been at the time `cached-index-state`. We may include
625624
# some packages that will be excluded by `index-state-max`
@@ -630,7 +629,9 @@ let
630629
index-state = cached-index-state;
631630
sha256 = index-sha256-found;
632631
}
633-
} make-install-plan ${
632+
}
633+
634+
make-install-plan ${
634635
# Setting the desired `index-state` here in case it is not
635636
# in the cabal.project file. This will further restrict the
636637
# packages used by the solver (cached-index-state >= index-state-max).
@@ -676,17 +677,20 @@ let
676677
# proper relative paths.
677678
(cd $out${subDir'} && plan-to-nix --full ${if ignorePackageYaml then "--ignore-package-yaml" else ""} --plan-json $tmp${subDir'}/dist-newstyle/cache/plan.json -o .)
678679
680+
substituteInPlace $tmp${subDir'}/dist-newstyle/cache/plan.json --replace "$out" "."
681+
substituteInPlace $tmp${subDir'}/dist-newstyle/cache/plan.json --replace "$CABAL_DIR" ""
682+
679683
# Replace the /nix/store paths to minimal git repos with indexes (that will work with materialization).
680684
${fixedProject.replaceLocations}
681685
682-
# Make the plan.json file available in case we need to debug plan-to-nix
683-
cp $tmp${subDir'}/dist-newstyle/cache/plan.json $json
684-
685686
# Remove the non nix files ".project" ".cabal" "package.yaml" files
686687
# as they should not be in the output hash (they may change slightly
687688
# without affecting the nix).
688689
find $out \( -type f -or -type l \) ! -name '*.nix' -delete
689690
691+
# Make the plan.json file available in case we need to debug plan-to-nix
692+
cp $tmp${subDir'}/dist-newstyle/cache/plan.json $out
693+
690694
# Make the revised cabal files available (after the delete step avove)
691695
echo "Moving cabal files from $tmp${subDir'}/dist-newstyle/cabal-files to $out${subDir'}/cabal-files"
692696
mv $tmp${subDir'}/dist-newstyle/cabal-files $out${subDir'}/cabal-files
@@ -698,7 +702,7 @@ let
698702
mv $out${subDir'}/pkgs.nix $out${subDir'}/default.nix
699703
'');
700704
in {
701-
projectNix = plan-nix;
705+
projectNix = plan-json;
702706
inherit index-state-max src;
703707
inherit (fixedProject) sourceRepos extra-hackages;
704708
}

lib/cover-project.nix

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ in pkgs.runCommand "project-coverage-report"
8282
fi
8383
8484
# Copy mix, tix, and html information over from each report
85-
for f in $report/share/hpc/vanilla/mix/$identifier*; do
85+
for f in $report/share/hpc/vanilla/mix/*; do
8686
cp -Rn $f $out/share/hpc/vanilla/mix
8787
done
8888
cp -R $report/share/hpc/vanilla/tix/* $out/share/hpc/vanilla/tix/

lib/default.nix

+6-3
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,13 @@ in {
9898
# Was there a reference to the package source in the `cabal.project` or `stack.yaml` file.
9999
# This is used to make the default `packages` list for `shellFor`.
100100
isLocalPackage = p: p.isLocal or false;
101-
selectLocalPackages = lib.filterAttrs (_n: p: p != null && isLocalPackage p);
101+
isRedirectPackage = p: p.isRedirect or false;
102+
selectLocalPackages = lib.filterAttrs (_n: p: p != null && isLocalPackage p && !isRedirectPackage p);
102103

103104
# if it's a project package it has a src attribute set with an origSubDir attribute.
104105
# project packages are a subset of localPackages
105106
isProjectPackage = p: p.isProject or false;
106-
selectProjectPackages = lib.filterAttrs (_n: p: p != null && isLocalPackage p && isProjectPackage p);
107+
selectProjectPackages = lib.filterAttrs (_n: p: p != null && isLocalPackage p && isProjectPackage p && !isRedirectPackage p);
107108

108109
# Format a componentId as it should appear as a target on the
109110
# command line of the setup script.
@@ -341,7 +342,7 @@ in {
341342
# Converts from a `compoent.depends` value to a library derivation.
342343
# In the case of sublibs the `depends` value should already be the derivation.
343344
dependToLib = d:
344-
# Do simplify this to `d.components.library or d`, as that
345+
# Do not simplify this to `d.components.library or d`, as that
345346
# will not give a good error message if the `.library`
346347
# is missing (happens if the package is unplanned,
347348
# but has overrides).
@@ -613,4 +614,6 @@ in {
613614
}";
614615

615616
types = import ./types.nix { inherit lib; };
617+
618+
addPackageKeys = x: x // { package-keys = builtins.attrNames x.packages; };
616619
}

0 commit comments

Comments
 (0)