Skip to content

Commit 1293306

Browse files
committed
Fix shellFor not accounting for transitive dependencies
ensure that the invariant that the following invariant is enforced: `shellFor` never adds the selected packages to its environment
1 parent f279cde commit 1293306

File tree

1 file changed

+41
-22
lines changed

1 file changed

+41
-22
lines changed

builder/shell-for.nix

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212
, components ? ps: lib.concatMap haskellLib.getAllComponents (packages ps)
1313
# Additional packages to be added unconditionally
1414
, additional ? _: []
15-
, withHoogle ? true
16-
, exactDeps ? false
15+
, withHoogle ? true , exactDeps ? false
1716
, tools ? {}
1817
, packageSetupDeps ? true
1918
, enableDWARF ? false
@@ -25,39 +24,59 @@ let
2524

2625
selectedPackages = packages hsPkgs;
2726
additionalPackages = additional hsPkgs;
28-
selectedComponents = components hsPkgs;
27+
directlySelectedComponents = components hsPkgs;
2928

30-
# The configs of all the selected components
31-
selectedConfigs = map (c: c.config) selectedComponents
32-
++ lib.optionals packageSetupDeps (map (p: p.setup.config) selectedPackages);
33-
34-
name = if lib.length selectedPackages == 1
35-
then "ghc-shell-for-${(lib.head selectedPackages).identifier.name}"
36-
else "ghc-shell-for-packages";
37-
38-
# Given a `packages = [ a b ]` we construct a shell with the dependencies of `a` and `b`.
29+
# Given `directlySelectedComponents = [ a b ]`, we construct a shell that includes all of their dependencies
3930
#
40-
# But if `a` depends on `b`, we don't want to include `b`, because
31+
# But we want to exclude `a` if it is a transitive dependecy of `b`
32+
# because:
4133
# - cabal will end up ignoring that built version;
4234
# - The user has indicated that's what they're working on, so they probably don't want to have to build
4335
# it first (and it will change often).
44-
# Generally we never want to include any of (the components of) the selected packages as dependencies.
36+
# Generally we never want to include any of `directlySelectedComponents` as dependencies.
37+
#
38+
# We do this by defining a set of `selectedComponents`, where `x` is in `selectedComponents` if and only if:
39+
# - `x` is in `directlySelectedComponents`
40+
# - `x` is a transitive dependency of something in `directlySelectedComponents`
41+
# and `x` transitively depends on something in `directlySelectedComponents`
42+
# We use the dependencies of `selectedComponents` filtering out members of `selectedComponents`
4543
#
4644
# Furthermore, if `a` depends on `b`, `a` will include the library component of `b` in its `buildInputs`
4745
# (to make `propagatedBuildInputs` of `pkgconfig-depends` work). So we also need to filter those
4846
# (the pkgconfig depends of `b` will still be included in the
4947
# system shell's `buildInputs` via `b`'s own `buildInputs`).
50-
# We have to do this slightly differently because we will be looking at the actual components rather
51-
# than the packages.
5248

53-
# Given a list of `depends`, removes those which are components of packages which were selected as part of the shell.
49+
50+
# all packages that are indirectly depended on by `directlySelectedComponents`
51+
# including `directlySelectedComponents`
52+
transitiveDependenciesComponents =
53+
builtins.listToAttrs
54+
(builtins.map (x: lib.nameValuePair (x.name) x)
55+
(haskellLib.flatLibDepends {depends = directlySelectedComponents;}));
56+
57+
selectedComponentsBitmap =
58+
lib.mapAttrs
59+
(_: x: (builtins.any
60+
(dep: selectedComponentsBitmap."${(haskellLib.dependToLib dep).name}") x.config.depends))
61+
transitiveDependenciesComponents
62+
// builtins.listToAttrs (map (x: lib.nameValuePair x.name true) directlySelectedComponents); # base case
63+
64+
selectedComponents =
65+
lib.filter (x: selectedComponentsBitmap."${x.name}") (lib.attrValues transitiveDependenciesComponents);
66+
67+
# Given a list of `depends`, removes those which are selected components
5468
removeSelectedInputs =
55-
# All the components of the selected packages: we shouldn't add any of these as dependencies
56-
let selectedPackageComponents = map (x: x.name) selectedComponents;
57-
in lib.filter (input:
58-
!(builtins.elem ((haskellLib.dependToLib input).name or null) selectedPackageComponents));
69+
lib.filter (input: !(selectedComponentsBitmap."${((haskellLib.dependToLib input).name or null)}"));
70+
71+
# The configs of all the selected components
72+
selectedConfigs = map (c: c.config) selectedComponents
73+
++ lib.optionals packageSetupDeps (map (p: p.setup.config) selectedPackages);
74+
75+
name = if lib.length selectedPackages == 1
76+
then "ghc-shell-for-${(lib.head selectedPackages).identifier.name}"
77+
else "ghc-shell-for-packages";
5978

60-
# We need to remove any dependencies which are selected packages (see above).
79+
# We need to remove any dependencies which would bring in selected components (see above).
6180
packageInputs = removeSelectedInputs (lib.concatMap (cfg: cfg.depends) selectedConfigs)
6281
++ additionalPackages;
6382

0 commit comments

Comments
 (0)