Skip to content
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

Package sets within derivations (i.e. python3.pkgs) are not spliced #211340

Open
rrbutani opened this issue Jan 18, 2023 · 7 comments
Open

Package sets within derivations (i.e. python3.pkgs) are not spliced #211340

rrbutani opened this issue Jan 18, 2023 · 7 comments
Labels
0.kind: bug Something is broken 6.topic: architecture Relating to code and API architecture of Nixpkgs 6.topic: cross-compilation Building packages on a different platform than they will be used on 6.topic: python

Comments

@rrbutani
Copy link
Contributor

I ran into this while trying to get LLVM to eval under cross-compilation as part of #194634.

Apologies if this has already been addressed elsewhere; I know @Artturin has been working on fixes and cleanup for splicing recently but I didn't see this specific issue brought up in any of the existing issues and PRs.

Problem

As detailed in 888f966, packages that are in package sets within derivations (i.e. the psutil package within python3.pkgs where python3 is a derivation) do not get spliced.

This can be observed via __splicedPackages (I know this is meant to be an implementation detail but it is the attrset that top-level callPackage draws from and it's handy for debugging):

> np = (import <nixpkgs> { }).pkgsCross.aarch64-multiplatform

> np.__splicedPackages.hello ? __spliced
true

> np.__splicedPackages.python3Packages.psutil ? __spliced
true (!!)

> np.__splicedPackages.python3.pkgs.psutil ? __spliced
false

Note that accessing the python package set via python3Packages in the above does give you spliced packages.


This happens because of the way splicing operates on derivations:

augmentedValue = defaultValue
# TODO(@Artturin): remove before release 23.05 and only have __spliced.
// (lib.optionalAttrs (pkgsBuildHost ? ${name}) { nativeDrv = lib.warn "use ${name}.__spliced.buildHost instead of ${name}.nativeDrv" valueBuildHost; })
// (lib.optionalAttrs (pkgsHostTarget ? ${name}) { crossDrv = lib.warn "use ${name}.__spliced.hostTarget instead of ${name}.crossDrv" valueHostTarget; })
// {
__spliced =
(lib.optionalAttrs (pkgsBuildBuild ? ${name}) { buildBuild = valueBuildBuild; })
// (lib.optionalAttrs (pkgsBuildHost ? ${name}) { buildHost = valueBuildHost; })
// (lib.optionalAttrs (pkgsBuildTarget ? ${name}) { buildTarget = valueBuildTarget; })
// (lib.optionalAttrs (pkgsHostHost ? ${name}) { hostHost = valueHostHost; })
// (lib.optionalAttrs (pkgsHostTarget ? ${name}) { hostTarget = valueHostTarget; })
// (lib.optionalAttrs (pkgsTargetTarget ? ${name}) {
targetTarget = valueTargetTarget;
});
};
# Get the set of outputs of a derivation. If one derivation fails to
# evaluate we don't want to diverge the entire splice, so we fall back
# on {}
tryGetOutputs = value0:
let
inherit (builtins.tryEval value0) success value;
in
getOutputs (lib.optionalAttrs success value);
getOutputs = value: lib.genAttrs
(value.outputs or (lib.optional (value ? out) "out"))
(output: value.${output});
in
# The derivation along with its outputs, which we recur
# on to splice them together.
if lib.isDerivation defaultValue then augmentedValue // spliceReal {
pkgsBuildBuild = tryGetOutputs valueBuildBuild;
pkgsBuildHost = tryGetOutputs valueBuildHost;
pkgsBuildTarget = tryGetOutputs valueBuildTarget;
pkgsHostHost = tryGetOutputs valueHostHost;
pkgsHostTarget = getOutputs valueHostTarget;
pkgsTargetTarget = tryGetOutputs valueTargetTarget;

Only the output attributes of a derivation are spliced; all other attributes are passed through as is.

Attrsets, on the other hand, are spliced recursively (on all attributes):

# Just recur on plain attrsets
} else if lib.isAttrs defaultValue then
spliceReal
{
pkgsBuildBuild = valueBuildBuild;
pkgsBuildHost = valueBuildHost;
pkgsBuildTarget = valueBuildTarget;
pkgsHostHost = valueHostHost;
pkgsHostTarget = valueHostTarget;
pkgsTargetTarget = valueTargetTarget;
# Don't be fancy about non-derivations. But we could have used used
# `__functor__` for functions instead.
} else defaultValue;
};
in
lib.listToAttrs (map merge (lib.attrNames mash));


Tying this back to the example above, python3Packages (despite being defined as an alias to python3.pkgs) is spliced because the splice function is passed that attrset when recursively processing the top-level package attrset (whereas when the slice function encounters python3 it does not recurse into python3.pkgs because python3 is a derivation).

Potential Solutions

1: Have pkgs be already-spliced in such places

i.e.: instead of relying on the top-level splicing to splice such package sets, have this be the responsibility of the package set's scope.

Tangent: Splicing and Scopes

There's already some precedent for this; today scopes that use makeScopeWithSplicing are automatically given a newScope and a callPackage that contains spliced versions of the packages in the scope:

makeScopeWithSplicing = splicePackages: newScope: otherSplices: keep: extra: f:
let
spliced0 = splicePackages {
pkgsBuildBuild = otherSplices.selfBuildBuild;
pkgsBuildHost = otherSplices.selfBuildHost;
pkgsBuildTarget = otherSplices.selfBuildTarget;
pkgsHostHost = otherSplices.selfHostHost;
pkgsHostTarget = self; # Not `otherSplices.selfHostTarget`;
pkgsTargetTarget = otherSplices.selfTargetTarget;
};
spliced = extra spliced0 // spliced0 // keep self;
self = f self // {
newScope = scope: newScope (spliced // scope);
callPackage = newScope spliced; # == self.newScope {};
# N.B. the other stages of the package set spliced in are *not*
# overridden.
overrideScope = g: makeScopeWithSplicing
splicePackages
newScope
otherSplices
keep
extra
(lib.fixedPoints.extends g f);
packages = f;
};
in self;

Here's how the python scope is set up, for example:

  • the set of python overlays and the python package sets from the other pkgsBuildBuild, pkgsBuildHost, etc. sets are given to makeScopeWithSplicing; this yields the pythonPackage fixed point:
    # Function that when called
    # - imports python-packages.nix
    # - adds spliced package sets to the package set
    # - applies overrides from `packageOverrides` and `pythonPackagesOverlays`.
    ({ pkgs, stdenv, python, overrides }: let
    pythonPackagesFun = import ./python-packages-base.nix {
    inherit stdenv pkgs lib;
    python = self;
    };
    otherSplices = {
    selfBuildBuild = pythonOnBuildForBuild.pkgs;
    selfBuildHost = pythonOnBuildForHost.pkgs;
    selfBuildTarget = pythonOnBuildForTarget.pkgs;
    selfHostHost = pythonOnHostForHost.pkgs;
    selfTargetTarget = pythonOnTargetForTarget.pkgs or {}; # There is no Python TargetTarget.
    };
    hooks = import ./hooks/default.nix;
    keep = lib.extends hooks pythonPackagesFun;
    extra = _: {};
    optionalExtensions = cond: as: if cond then as else [];
    pythonExtension = import ../../../top-level/python-packages.nix;
    python2Extension = import ../../../top-level/python2-packages.nix;
    extensions = lib.composeManyExtensions ([
    pythonExtension
    ] ++ (optionalExtensions (!self.isPy3k) [
    python2Extension
    ]) ++ pythonPackagesExtensions ++ [
    overrides
    ]);
    aliases = self: super: lib.optionalAttrs config.allowAliases (import ../../../top-level/python-aliases.nix lib self super);
    in makeScopeWithSplicing
    otherSplices
    keep
    extra
    (lib.extends (lib.composeExtensions aliases extensions) keep))
  • that's then exported as a passthru on the python3 derivation as pkgs:

Crucially, the spliced packages makeScopeWithSplicing produces are not made available in the actual scope (i.e. the package set; python3.pkgs in the above) itself but are made available to packages within the scope via the callPackage machinery.

This actually means that the contents of scopes like python3Packages (i.e. scopes that are exposed directly – not via a derivation – and are spliced at the top-level) are actually spliced "twice": once as part of the makeScopeWithSplicing call (accessed by members of the scope) and then again at the top-level (accessed by members of the outermost scope, the top-level).

It would be nice if we could reuse the splicing.

Using the outer scope's splicing within the inner scope is easy to do:

  • we can:
    • pass in the path to the scope being constructed + __splicedPackages to this function
    • and then use __splicedPackages. ... in lieu of spliced0

But this is problematic in cases where the scope is overriden (i.e. overrideScope) outside of an overlay-like context (where the top-level binding of that scope is also updated). In such cases, the scope's callPackage will continue using the original pre-overrideScope version of the packages within the scope because the splicing would still be pulling things from the top-level __splicedPackages. Put another way: overlays = [(f: p: { python3Packages = p.python3Packages.overrideScope (_: _: { ... }); })] would be okay because nixpkgs.python3Packages is updated to point to the overriden scope but just doing (nixpkgs.python3Packages.overrideScope (_: _: { ... })).some-package would not be okay.

Going the other way (using the scope's splicing for the top-level) seems a little trickier but wouldn't have this issue. We'd need to have the makeScopeWithSplicing expose the spliced attrset with something like a __splicedPackages attr and we'd then want spliceReal's handling of attrset to check for such an attr and use it instead of redoing the splicing itself. I don't think this runs into any recursion issues but I have not tested this yet.

Ultimately this (splicing scopes multiple times) is somewhat orthogonal to this issue but the above has some overlap with the potential solutions below and might influence a decision there.


Now that we know how scopes get spliced:

The most straight-forward way to have our scope yield an already-spliced pkgs attr is probably to get makeScopeWithSpliced to give us it's spliced attrset. We can modify makeScopeWithSpliced to expose the attrset as __splicedPackages = spliced (as discussed above) and then swap out this line:

for pkgs = pythonPackages.__splicedPackages.

We would also have to replicate this change for all other users of makeScopeWithSpliced that export their package set as part of a derivation's attrs.

2: Adjust spliceReal's handling of derivations

Recursing on all of the attributes of every derivation seems fraught but maybe it's safe to recurse on drv.passthru or an opt-in list of attributes (i.e. we could have spliceReal look for a passthru attr named __spliceRecurseAttrs on derivations) or maybe even just pkgs (since that seems to be the convention used).

This has the benefit of not requiring any changes from users of makeScopeWithSpliced and handling splicing for packages that are referenced via a derivation's attrs (depending on how general we adjust spliceReal to be on derivation attrs).

3: Discourage using package sets like python3.pkgs "directly"

(and instead push people to use python3Packages, lua5Packages, etc. in nixpkgs)

This seems suboptimal, both because this will be another thing that'd need to be enforced in nixpkgs to have cross work for packages and because the foo.pkgs.bar pattern (where foo is a derivation) seems pretty pervasive in nixpkgs (python, lua, perl, postgresql, etc.).


A version of option 2 (with the __splicedPackages attr for deduplicating the work of splicing scopes as a follow-up PR if it doesn't cause breakage) seems like the least-worst fix to me but I'm not particularly satisfied with any of these solutions; hopefully there's a more elegant solution that I'm missing 🤞.

cc: @Artturin

@rrbutani rrbutani added 0.kind: bug Something is broken 6.topic: cross-compilation Building packages on a different platform than they will be used on labels Jan 18, 2023
@Artturin Artturin mentioned this issue Jan 21, 2023
13 tasks
rrbutani added a commit to rrbutani/nixpkgs that referenced this issue Jan 27, 2023
…lation

The two scenarios described within where splicing doesn't handle
selecting the right package for us are observable in the following
(nix repl session):
```
> np = import <nixpkgs> { system = "x86_64-linux"; crossSystem = { config = "aarch64-linux"; }; }

> np.__splicedPackages.hello ? __spliced
true

> np.__splicedPackages.python3Packages.psutil ? __spliced
true

> np.__splicedPackages.python3.pkgs.psutil ? __spliced
false

> (np.__splicedPackages.python3.withPackages (ps: with ps; [psutil])) ? __spliced
false
```

See: NixOS#211340
github-actions bot pushed a commit that referenced this issue Jan 28, 2023
…lation

The two scenarios described within where splicing doesn't handle
selecting the right package for us are observable in the following
(nix repl session):
```
> np = import <nixpkgs> { system = "x86_64-linux"; crossSystem = { config = "aarch64-linux"; }; }

> np.__splicedPackages.hello ? __spliced
true

> np.__splicedPackages.python3Packages.psutil ? __spliced
true

> np.__splicedPackages.python3.pkgs.psutil ? __spliced
false

> (np.__splicedPackages.python3.withPackages (ps: with ps; [psutil])) ? __spliced
false
```

See: #211340
(cherry picked from commit 3436075)
K900 pushed a commit that referenced this issue Jan 28, 2023
…lation

The two scenarios described within where splicing doesn't handle
selecting the right package for us are observable in the following
(nix repl session):
```
> np = import <nixpkgs> { system = "x86_64-linux"; crossSystem = { config = "aarch64-linux"; }; }

> np.__splicedPackages.hello ? __spliced
true

> np.__splicedPackages.python3Packages.psutil ? __spliced
true

> np.__splicedPackages.python3.pkgs.psutil ? __spliced
false

> (np.__splicedPackages.python3.withPackages (ps: with ps; [psutil])) ? __spliced
false
```

See: #211340
xanderio pushed a commit to xanderio/nixpkgs that referenced this issue Feb 13, 2023
…lation

The two scenarios described within where splicing doesn't handle
selecting the right package for us are observable in the following
(nix repl session):
```
> np = import <nixpkgs> { system = "x86_64-linux"; crossSystem = { config = "aarch64-linux"; }; }

> np.__splicedPackages.hello ? __spliced
true

> np.__splicedPackages.python3Packages.psutil ? __spliced
true

> np.__splicedPackages.python3.pkgs.psutil ? __spliced
false

> (np.__splicedPackages.python3.withPackages (ps: with ps; [psutil])) ? __spliced
false
```

See: NixOS#211340
@ghost
Copy link

ghost commented Apr 20, 2023

Potential Solutions

4. Reconsider whether or not splicing was a good idea

There has got to be a simpler way of doing all of this. The fact that splicing breaks randomly in weird ways and hardly anybody understands it is a major contributor to people hating on cross compilation. There has got to be a simpler way to do all of this.

@Artturin
Copy link
Member

Potential Solutions

4. Reconsider whether or not splicing was a good idea

There has got to be a simpler way of doing all of this. The fact that splicing breaks randomly in weird ways and hardly anybody understands it is a major contributor to people hating on cross compilation. There has got to be a simpler way to do all of this.

#204303 (comment)

Artturin added a commit to Artturin/nixpkgs that referenced this issue Jul 26, 2023
`lib.extends hooks pythonPackagesFun` includes python which we want to
splice

what this fixes:
```
nix-repl> lib.elemAt pkgsCross.aarch64-multiplatform.python3Packages.xpybutil.nativeBuildInputs 0
«derivation /nix/store/39dkb51rciw6zwg0c2c44gpmpjapddxc-python3-aarch64-unknown-linux-gnu-3.10.12.drv»
```
to
```
nix-repl> lib.elemAt pkgsCross.aarch64-multiplatform.python3Packages.xpybutil.nativeBuildInputs 0
«derivation /nix/store/21ldw2dp26xvv9iyxn9x77a8yh4waqz5-python3-3.10.12.drv»
```

Before NixOS#194205 The keep python used to work by accident because self was passed from
__splicedPackages https://github.com/NixOS/nixpkgs/pull/196052/files#diff-44ce3495c4f983ce64dd47c86a9d3e77bad210b2709c098a3806998dcd9b000bR213
But now it does not work because overrideAttrs is used on python in
python-packages-base.nix

Preferably we would [ splice the hooks too ](NixOS#228139) but we cannot do that until [Package sets within derivations (i.e. python3.pkgs) are not spliced](NixOS#211340) is fixed, because people often use `python3.pkgs.wrapPython` in `nativeBuildInputs` (it's correct but python3.pkgs should be python3Packages to get splicing.
@FRidh
Copy link
Member

FRidh commented Jul 26, 2023

3: Discourage using package sets like python3.pkgs "directly"

I think about 8 years ago I introduced this and it has been a clear mistake. Since then this pattern was copied throughout. We should indeed go to pythonPackages and also move the helpers such as buildEnv and withPackages into the package set, like haskell already had before.

@AndersonTorres
Copy link
Member

@FRidh what are the costs of reverting it?

@FRidh
Copy link
Member

FRidh commented Aug 16, 2023

@FRidh what are the costs of reverting it?

  • moving all the helpers (then we can also start using .overrideScope)
  • adjusting to python3Packages throughout
  • keeping the old way of working in place for a release or 2

Basically people willing to put in that effort.

@AndersonTorres
Copy link
Member

"Willing" also includes having some theoretical knowledge?

NickCao added a commit to NickCao/nixpkgs that referenced this issue Aug 18, 2023
…compilation

See also: Package sets within derivations (i.e. python3.pkgs) are not spliced
          NixOS#211340
bjornfor pushed a commit that referenced this issue Aug 18, 2023
…compilation

See also: Package sets within derivations (i.e. python3.pkgs) are not spliced
          #211340
@Artturin Artturin mentioned this issue Sep 2, 2023
12 tasks
superherointj pushed a commit that referenced this issue Apr 17, 2024
* plasma-hud: fix runtime errors

* plasma-hud: clean up installPhase

* plasma-hud: fix scoping for `propagatedBuildInputs`

* plasma-hud: formatting

* plasma-hud: clean up src

* plasma-hud: remove `with lib;`

* plasma-hud: fix potential splicing issue (see #211340)
@amarshall amarshall mentioned this issue Apr 30, 2024
13 tasks
@Qyriad Qyriad mentioned this issue May 23, 2024
13 tasks
@Qyriad Qyriad added the 6.topic: architecture Relating to code and API architecture of Nixpkgs label May 25, 2024
@nixos-discourse
Copy link

This issue has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/frustrations-about-splicing/49607/1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
0.kind: bug Something is broken 6.topic: architecture Relating to code and API architecture of Nixpkgs 6.topic: cross-compilation Building packages on a different platform than they will be used on 6.topic: python
Projects
None yet
Development

No branches or pull requests

7 participants