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

__cleanAttrs: Clean packages that don't expose all internals #217243

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ef06fe8
make-derivation.nix: Inline the call to derivation
roberth Feb 19, 2023
db2571b
lib.extendDerivation: Allow derivationStrict-style simple outputs
roberth Feb 19, 2023
3cbfa15
make-derivation.nix: Remove intermediate derivation output wrappers
roberth Feb 19, 2023
59db5a5
mkDerivation: Add __cleanAttrs ? false
roberth Feb 19, 2023
89ff705
mkDerivation: Do not expose passthru input attribute when __cleanAttrs
roberth Feb 19, 2023
9b45ecd
mkDerivation: Remove inputDerivation from __cleanAttrs packages
roberth Feb 19, 2023
b9e7258
mkDerivation: Add name and version to __cleanAttrs packages
roberth Feb 19, 2023
7a0bba8
lib.inspectMkDerivationArgs: init, alternative to .drvAttrs for __cle…
roberth Feb 19, 2023
3abc47f
pkgs.inputDerivation: Add pkg.inputDerivation alternative for __clean…
roberth Feb 19, 2023
416741d
mkDerivation: Add outputs to __cleanAttrs packages
roberth Feb 20, 2023
dc32ea5
lib.hydraJob: Accept __cleanAttrs packages
roberth Feb 20, 2023
cd0de0e
hello: Expose src
roberth Feb 20, 2023
d02174b
mkDerivation: Keep legacy name semantics
roberth Feb 20, 2023
2377feb
make-derivation.nix: Remove a `//`
roberth Feb 20, 2023
fac41dd
make-derivation.nix: Remove a `let`
roberth Feb 20, 2023
b7477b9
cutomisation.nix: Remove a `//`
roberth Feb 20, 2023
bc10d59
lib/customisation.nix: Refactor: convert if to or
roberth Feb 20, 2023
2db600e
make-derivation.nix: Inline genAttrs
roberth Feb 20, 2023
7458ee0
make-derivation.nix: Save another `//`
roberth Feb 20, 2023
b2c0a99
hello: Simplify
roberth Feb 24, 2023
36582b0
salve-mundi: init
roberth Feb 24, 2023
2295009
Revert "pkgs.inputDerivation: Add pkg.inputDerivation alternative for…
roberth Mar 1, 2023
165a2de
make-derivation.nix: Use formal for __cleanAttrs
roberth Mar 1, 2023
ce39083
make-derivation.nix: Do expose `internals` when `__cleanAttrs`
roberth Mar 1, 2023
c24b772
Revert "lib.inspectMkDerivationArgs: init, alternative to .drvAttrs f…
roberth Mar 1, 2023
c09aa93
salve-mundi.nix: Explain what the file is for
roberth Mar 12, 2023
dbb8745
doc/stdenv: Document __cleanAttrs
roberth Mar 12, 2023
b38a364
doc/stdenv: Document __structuredAttrs
roberth Mar 12, 2023
854693c
pkgs.tests.stdenv: Test __cleanAttrs
roberth Mar 12, 2023
a4655ce
doc/rl-2305: Add __cleanAttrs
roberth Mar 12, 2023
56a4141
mkDerivation: Add `__cleanAttrs = "warn";`
roberth Mar 14, 2023
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
40 changes: 39 additions & 1 deletion doc/stdenv/stdenv.chapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ The standard build environment in the Nix Packages collection provides an enviro

## Using `stdenv` {#sec-using-stdenv}

To build a package with the standard environment, you use the function `stdenv.mkDerivation`, instead of the primitive built-in function `derivation`, e.g.
To build a package with the standard environment, you use the function `stdenv.mkDerivation`. It calls the built-in `derivation` function for you, and turns its result into a package attribute set.

A minimal invocation looks as follows.

```nix
stdenv.mkDerivation {
Expand Down Expand Up @@ -362,6 +364,42 @@ Unless set to `false`, some build systems with good support for parallel buildin

### Special variables {#special-variables}

#### `__cleanAttrs` {#var-__cleanAttrs}

By default, `mkDerivation` will expose its arguments in the returned package attribute set. This is unnecessary and leads to some confusion and doubt.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
By default, `mkDerivation` will expose its arguments in the returned package attribute set. This is unnecessary and leads to some confusion and doubt.
By default, `mkDerivation` will expose its arguments in the returned package attribute set.
This is often unnecessary and may lead to confusion about what's relevant for consumers.

We should soften that a bit, because I don't see evidence for the originally definitive-sounding statement.


New packages may pass `__cleanAttrs = true;` to `mkDerivation`, so that it will return a minimal set of package attributes, which package authors can extend via [`passthru`](#var-stdenv-passthru).

Existing packages may be modified to pass `__cleanAttrs = "warn";`, so that the legacy attributes remain available, but they will print a helpful warning when they are accessed. Doing this is best avoided until the packaging function used supports recursively defined arguments like [`mkDerivation` does](#mkderivation-recursive-attributes).
Copy link
Member

Choose a reason for hiding this comment

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

Doing this is best avoided until the packaging function used supports recursively defined arguments like mkDerivation does

Which packaging function?


Benefits of `__cleanAttrs`:

- Users don't have to sift through unnecessary attributes when exploring a package in the `nix repl`.
- Users can confidently use non-standard attributes that a package provides.
- Package authors know which variables are intended for use by the builder. They can change the builder environment with confidence that they don't break consumers of their package.
- It is a little bit more efficient in terms of CPU cycles and memory use.

##### What to do when warned {#warning-package-attr-impl-detail}

If you encounter the warning `The attribute ... of package ... is an implementation detail`, you are invited to help us explicitly support ways in which a package attribute set may used, so that Nixpkgs contributors and users will be aware of your use case.
Copy link
Contributor

Choose a reason for hiding this comment

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

This could be phrased to be more immediately actionable.

Suggested change
If you encounter the warning `The attribute ... of package ... is an implementation detail`, you are invited to help us explicitly support ways in which a package attribute set may used, so that Nixpkgs contributors and users will be aware of your use case.
If you encounter the warning `The attribute ... of package ... is an implementation detail`, please make Nixpkgs contributors and users aware of your particular use case, and help providing explicit ways to use package attribute sets, by opening an [issue](https://github.com/NixOS/nixpkgs/issues) or (https://github.com/NixOS/nixpkgs/pulls).


Ideally the package can add support for whatever is the high level goal you are trying to achieve. This usually involves adding an attribute explicitly to the package attribute set using [`passthru`](#var-stdenv-passthru). Perhaps some commonly applied logic can be added to its value. The public attributes defined in `passthru` can make use of a [recursive package definition](#mkderivation-recursive-attributes) in order to access other parts of the `mkDerivation`-based package in a way that works with `overrideAttrs`.

If you do not know why you got the warning, you may set environment variable `NIX_ABORT_ON_WARN=true` and pass `--show-trace` to Nix. This will cause an evaluation trace to be printed for the location of the warning. From this trace, you can usually derive which expression is responsible for getting the attribute. If you use the `nix-command` experimental feature, you also need to pass `--impure`.

If the attribute can not reasonably be supported by the community, you may use the `internals` attribute to find the same value without the warning. We hope that this will be rare, and if you need `internals` to make some temporary hack bearable, it's perfectly reasonable to use it.

#### `__structuredAttrs` {#var-__structuredAttrs}

When `__structuredAttrs = true` is passed to `mkDerivation`, derivation attributes are serialized in JSON format and made available to the builder through a `.attrs.json` file. This will

- allow large values to be passed,
- eliminate the need for `passAsFile`,
- allow for finer grained settings such as [`outputChecks`](https://nixos.org/manual/nix/unstable/language/advanced-attributes.html?highlight=__struct#adv-attr-outputChecks),
- turn non-nested arrays into bash arrays rather than a concatenated string.

See [`__structuredAttrs` in the Nix manual](https://nixos.org/manual/nix/unstable/language/advanced-attributes.html#adv-attr-structuredAttrs).

#### `passthru` {#var-stdenv-passthru}

This is an attribute set which can be filled with arbitrary values. For example:
Expand Down
26 changes: 21 additions & 5 deletions lib/customisation.nix
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
{ lib }:

let
inherit (lib)
isAttrs
isString
;
in
rec {


Expand Down Expand Up @@ -209,10 +214,19 @@ rec {
outputToAttrListElement = outputName:
{ name = outputName;
value = commonAttrs // {
inherit (drv.${outputName}) type outputName;
type = drv.${outputName}.type or "derivation";
inherit outputName;
outputSpecified = true;
drvPath = assert condition; drv.${outputName}.drvPath;
outPath = assert condition; drv.${outputName}.outPath;
drvPath = assert condition;
drv.${outputName}.drvPath or (
assert isString drv.drvPath;
drv.drvPath
);
outPath = assert condition;
drv.${outputName}.outPath or (
assert isString drv.${outputName};
drv.${outputName}
);
} //
# TODO: give the derivation control over the outputs.
# `overrideAttrs` may not be the only attribute that needs
Expand All @@ -238,7 +252,9 @@ rec {
outputs = drv.outputs or ["out"];

commonAttrs =
{ inherit (drv) name system meta; inherit outputs; }
{ inherit (drv) name meta; inherit outputs;
${if drv?system then "system" else null} = drv.system;
}
// lib.optionalAttrs (drv._hydraAggregate or false) {
_hydraAggregate = true;
constituents = map hydraJob (lib.flatten drv.constituents);
Expand Down
4 changes: 4 additions & 0 deletions nixos/doc/manual/release-notes/rl-2305.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ In addition to numerous new and upgraded packages, this release has the followin

- Pantheon now defaults to Mutter 42 and GNOME settings daemon 42, all Pantheon packages are now tracking elementary OS 7 updates.

- `mkDerivation` does not have to leak implementation details anymore.

Passing [`__cleanAttrs = true`](https://nixos.org/manual/nixpkgs/unstable/#var-__cleanAttrs) to `mkDerivation` returns a minimal set of package attributes that can be extended via [`passthru`](https://nixos.org/manual/nixpkgs/unstable/#var-stdenv-passthru). This reduces confusion for package users and allows for confident use of non-standard attributes provided by a package. It also gives package authors more confidence in modifying the builder environment without breaking consumers of their package, and it is slightly more efficient.
Copy link
Member

Choose a reason for hiding this comment

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

I really don't like the wording of this. It encourages people to just set cleanAttrs for all their packages, only to later get feedback from people that they need x, y, z back.

Copy link
Member Author

Choose a reason for hiding this comment

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

How about this?

Suggested change
Passing [`__cleanAttrs = true`](https://nixos.org/manual/nixpkgs/unstable/#var-__cleanAttrs) to `mkDerivation` returns a minimal set of package attributes that can be extended via [`passthru`](https://nixos.org/manual/nixpkgs/unstable/#var-stdenv-passthru). This reduces confusion for package users and allows for confident use of non-standard attributes provided by a package. It also gives package authors more confidence in modifying the builder environment without breaking consumers of their package, and it is slightly more efficient.
Passing [`__cleanAttrs = true`](https://nixos.org/manual/nixpkgs/unstable/#var-__cleanAttrs) to `mkDerivation` returns a minimal set of package attributes that can be extended via [`passthru`](https://nixos.org/manual/nixpkgs/unstable/#var-stdenv-passthru). For new packages, this reduces confusion for package users and allows for confident use of non-standard attributes provided by a package. It also gives package authors more confidence in modifying the builder environment without breaking consumers of their package, and it is slightly more efficient. It may be applied to existing packages with great care, as other users may unknowingly rely on internals. To uncover such usages without breaking their code, you may first pass `__cleanAttrs = "warn"` instead.

Copy link
Member

Choose a reason for hiding this comment

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

That sounds better. 👍🏼


- The module for the application firewall `opensnitch` got the ability to configure rules. Available as [services.opensnitch.rules](#opt-services.opensnitch.rules)

- The module `usbmuxd` now has the ability to change the package used by the daemon. In case you're experiencing issues with `usbmuxd` you can try an alternative program like `usbmuxd2`. Available as [services.usbmuxd.package](#opt-services.usbmuxd.package)
Expand Down
10 changes: 1 addition & 9 deletions pkgs/applications/misc/hello/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
, lib
, stdenv
, fetchurl
, nixos
, testers
, hello
}:
Expand All @@ -20,16 +19,9 @@ stdenv.mkDerivation (finalAttrs: {

passthru.tests = {
version = testers.testVersion { package = hello; };

invariant-under-noXlibs =
testers.testEqualDerivation
"hello must not be rebuilt when environment.noXlibs is set."
hello
(nixos { environment.noXlibs = true; }).pkgs.hello;
run = callPackage ./test.nix { hello = finalAttrs.finalPackage; };
};

passthru.tests.run = callPackage ./test.nix { hello = finalAttrs.finalPackage; };

meta = with lib; {
description = "A program that produces a familiar, friendly greeting";
longDescription = ''
Expand Down
98 changes: 98 additions & 0 deletions pkgs/applications/misc/hello/salve-mundi.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
This file showcases various techniques and features of Nixpkgs, whereas the
regular hello serves as more of an introductory package definition.
Salve mundi is latin for hello world.
*/
{ callPackage
, lib
, stdenv
, fetchurl
, nixos
, testers
, salve-mundi
, makeWrapper
, runCommand
}:

stdenv.mkDerivation (finalAttrs: {
pname = "salve-mundi";
version = "2.12.1";

src = fetchurl {
url = "mirror://gnu/hello/hello-${finalAttrs.version}.tar.gz";
sha256 = "sha256-jZkUKv2SV28wsM18tCqNxoCZmLxdYH2Idh9RLibH2yA=";
};

doCheck = true;


/* Wrap the program */

postInstall = lib.optionalString (finalAttrs.passthru.greeting != null) ''
wrapProgram $out/bin/hello --append-flags --greeting=${lib.escapeShellArg (lib.escapeShellArg finalAttrs.passthru.greeting)}
'';
nativeBuildInputs = [ makeWrapper ];
passthru.greeting = "SALVE MUNDI";


/* Tidy up the package attributes and make them useful */

__cleanAttrs = true;
passthru.exe = lib.getExe finalAttrs.finalPackage;
# We allow `hello.src` to be used in tests and examples, despite __cleanAttrs
passthru.src = finalAttrs.src;
Comment on lines +42 to +43
Copy link
Member

Choose a reason for hiding this comment

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

IMO src should always be passed through. eg you sometimes want to (re-)build it and as I understand it, that would after that no longer be possible which would suck.

Copy link
Member Author

Choose a reason for hiding this comment

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

src -> internals.src should be fine for individual "override" packages you write.

There is no general way to get all sources in using just the expression language, so we don't lose the capability to generically retrieve all sources, because we never had that in the language. Simply put, not all packages have a src, and not all sources appear in src, and you can hide "anything" in a dependency of a derivation a string context.
This task is better achieved by analyzing the .drv graph, you can find all the derivation input sources (file references and builtin fetcher results) and fixed output derivations. You may even do this from within a derivation.



/* Strict deps enforce a separation of concerns that also benefits cross compilation
and, for shells, shell completions support via nativeBuildInputs */

strictDeps = true;


/* A fairly extensive suite of extra tests that we like to hold either for
the package in the Nixpkgs package set, or even for all possible overrides
of the package. */

passthru.tests = {
version = testers.testVersion { package = salve-mundi; };

# We use Nixpkgs attributes instead of `finalAttrs.finalPackage` here
# because overriding is not supported. Running the test on an overridden
# finalPackage wouldn't work, and is a bit unnecessary anyway.
invariant-under-noXlibs =
testers.testEqualDerivation
"hello must not be rebuilt when environment.noXlibs is set."
salve-mundi
(nixos { environment.noXlibs = true; }).pkgs.salve-mundi;

run = runCommand "salve-mundi-test-run" {
nativeBuildInputs = [ finalAttrs.finalPackage ];
} ''
diff -U3 --color=auto <(hello) <(echo 'SALVE MUNDI')
touch $out
'';

restore-default-greeting = callPackage ./test.nix {
hello = finalAttrs.finalPackage.overrideAttrs (o: {
passthru = o.passthru // {
greeting = null;
};
});
};
Comment on lines +75 to +81
Copy link
Member

Choose a reason for hiding this comment

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

What's this for?

Copy link
Contributor

Choose a reason for hiding this comment

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

not everyone speaks latin 😄

Copy link
Member Author

Choose a reason for hiding this comment

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

That is a good point. It could come across as elitist, especially in this context now that I think about it more.
Something like Esperanto would be nicer, or Lojban, or Tolkien's Elvish. Or maybe it should just be hello_with_bells_and_whistles, although I don't know if _ for variations should be followed by snake case or camelCase.
Why is this hard...

May the first confirmation win:

  • suilad-ambar: is this a correct Elvish package name?
  • coi-munje: is this a correct Lojban package name?

I don't like the Esperanto version because it's basically Romance language vocabulary. Nothing wrong with that, but if it's not going to be English, I don't want to bias it. Sue me. ;)

};

meta = with lib; {
description = "A showcase of Nixpkgs features that produces an unfamiliar, archaic greeting";
longDescription = ''
GNU Hello is a program that prints "Hello, world!" when you run it.
It is fully customizable. This package proves it, and showcases some
of the fancier things you can do with Nixpkgs.
'';
mainProgram = "hello";
homepage = "https://www.gnu.org/software/hello/manual/";
changelog = "https://git.savannah.gnu.org/cgit/hello.git/plain/NEWS?h=v${finalAttrs.version}";
license = licenses.gpl3Plus;
maintainers = [ maintainers.roberth ];
platforms = platforms.all;
};
})
67 changes: 61 additions & 6 deletions pkgs/stdenv/generic/make-derivation.nix
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ let
# but for anything complex, be prepared to debug if enabling.
, __structuredAttrs ? config.structuredAttrsByDefault or false

# Opt-in for those who want a clear separation between attributes that should
# be used and those that are internal. Also expected to perform slightly better
# when applied widely.
, __cleanAttrs ? false

, env ? { }

, ... } @ attrs:
Expand Down Expand Up @@ -288,6 +293,7 @@ else let
"nativeCheckInputs" "nativeInstallCheckInputs"
"__darwinAllowLocalNetworking"
"__impureHostDeps" "__propagatedImpureHostDeps"
"__cleanAttrs"
"sandboxProfile" "propagatedSandboxProfile"]
++ lib.optional (__structuredAttrs || envIsExportable) "env"))
// (lib.optionalAttrs (attrs ? name || (attrs ? pname && attrs ? version)) {
Expand Down Expand Up @@ -542,11 +548,37 @@ else let
"The ‘env’ attribute set can only contain derivation, string, boolean or integer attributes. The ‘${n}’ attribute is of type ${builtins.typeOf v}."; v)
env;

drvAttrs = derivationArg // lib.optionalAttrs envIsExportable checkedEnv;
strict = builtins.derivationStrict drvAttrs;
outputName = lib.head outputs;

name = attrs.name or attrs.pname;

# begin __cleanAttrs helpers

whatNow =
(
if meta.maintainers != []
then "You may work with the maintainer(s) of ${name}: ${lib.concatMapStringsSep ", " (m: m.github or m.name or m.email) meta.maintainers} (and/or the community), to add explicit support for your use case."
else "You may work with the community to add explicit support for your use case to ${name}."
) +
" See https://nixos.org/manual/nixpkgs/unstable/#warning-package-attr-impl-detail"
;

warnCleanAttrs = lib.mapAttrs (k: lib.warn "The attribute ${lib.strings.escapeNixIdentifier k} of package ${name} ${if k == "passthru" then "is" else "seems to be"} an implementation detail and may be removed from the package attribute set in the future. ${whatNow}");

warnCleanDrvAttrs = lib.warn "The attribute drvAttrs of package ${name} is an implementation detail and may be removed from the package attribute set in the future. ${whatNow}";

maybeWarnCleanAttrs = if __cleanAttrs == "warn" then warnCleanAttrs else x: x;

# end __cleanAttrs helpers

in

lib.extendDerivation
validity.handled
({
(
lib.optionalAttrs (__cleanAttrs != true) (maybeWarnCleanAttrs {
Copy link
Member

Choose a reason for hiding this comment

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

This removes inputDerivation when __cleanAttrs == true, I'm not sure if that makes sense, since .inputDerivation is meant to be a public stable interface. While it might not be needed anymore since mkShell is now buildable, I'm not sure if this should be decided here.

# A derivation that always builds successfully and whose runtime
# dependencies are the original derivations build time dependencies
# This allows easy building and distributing of all derivations
Expand Down Expand Up @@ -577,14 +609,37 @@ lib.extendDerivation
disallowedReferences = [ ];
disallowedRequisites = [ ];
});

inherit meta passthru overrideAttrs;
} //
inherit passthru;
})
// {
inherit meta overrideAttrs;
}
# Pass through extra attributes that are not inputs, but
# should be made available to Nix expressions using the
# derivation (e.g., in assertions).
passthru)
(derivation (derivationArg // lib.optionalAttrs envIsExportable checkedEnv));
// passthru)
(
(
if __cleanAttrs == true
then builtins.intersectAttrs { version = null; } drvAttrs
Copy link
Member

Choose a reason for hiding this comment

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

Why is version propagated? I guess it's somewhat important and stable enough. Should definitely be a comment here

else if __cleanAttrs == "warn"
then warnCleanAttrs drvAttrs
else drvAttrs
)
// builtins.listToAttrs (map (outputName: {
name = outputName;
value = strict.${outputName};
}) outputs)
// {
type = "derivation";
inherit outputName outputs;
inherit (strict) drvPath;
inherit (drvAttrs) name;
${if __cleanAttrs != false then "internals" else "drvAttrs"} = if __cleanAttrs == "warn" then warnCleanDrvAttrs drvAttrs else drvAttrs;
${if __cleanAttrs == "warn" then "drvAttrs" else null} = warnCleanDrvAttrs drvAttrs;
outPath = strict.${outputName};
}
);

in
fnOrAttrs:
Expand Down
Loading