Skip to content

lib: Add attrsets.mainProgram#138418

Closed
nicoonoclaste wants to merge 4 commits intoNixOS:masterfrom
nicoonoclaste:mainProgram/lib
Closed

lib: Add attrsets.mainProgram#138418
nicoonoclaste wants to merge 4 commits intoNixOS:masterfrom
nicoonoclaste:mainProgram/lib

Conversation

@nicoonoclaste
Copy link
Contributor

@nicoonoclaste nicoonoclaste commented Sep 18, 2021

Motivation for this change

Writing "${pkgs.something}/bin/something" is a common idiom, both in nixpkgs itself and Nix users' configuration.
That idiom is brittle, as it will break if the binary is renamed or the package is made multi-output.

Using instead lib.mainProgram pkgs.something avoids both of those issues, and avoids implementing ad-hoc solutions
everywhere, while often being shorter.

As an example, node-packages is simplified and made more uniform; prior to the change, PUPEETER_EXECUTABLE_PATH
was defined in several different — but equivalent — ways using .outPath or not; using lib.mainProgram is
equivalent to both of those and less brittle.

Things done
  • Built on platform(s)
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin
  • For non-Linux: Is sandbox = true set in nix.conf? (See Nix manual)
  • Tested via one or more NixOS test(s) if existing and applicable for the change (look inside nixos/tests)
  • Tested compilation of all packages that depend on this change using nix-shell -p nixpkgs-review --run "nixpkgs-review wip"
  • Tested execution of all binary files (usually in ./result/bin/)
  • 21.11 Release Notes (or backporting 21.05 Release notes)
    • (Package updates) Added a release notes entry if the change is major or breaking
    • (Module updates) Added a release notes entry if the change is significant
    • (Module addition) Added a release notes entry if adding a new NixOS module
  • Fits CONTRIBUTING.md.

@alyssais
Copy link
Member

That idiom is brittle, as it will break if the binary is renamed or the package is made multi-output.

Small correction: conventionally we put binaries in the default output, specifically so this doesn't break. I'm not aware of many cases of binaries being renamed while still being compatible either, fwiw.

@nicoonoclaste
Copy link
Contributor Author

That idiom is brittle, as it will break if the binary is renamed or the package is made multi-output.

Small correction: conventionally we put binaries in the default output, specifically so this doesn't break. I'm not aware of many cases of binaries being renamed while still being compatible either, fwiw.

Thanks for the correction, I amended the PR description.

@alyssais alyssais requested a review from zimbatm September 18, 2021 12:59
@alyssais
Copy link
Member

nix-repl> lib.mainProgram bash
error: attribute 'mainProgram' missing, at /home/src/nixpkgs/lib/attrsets.nix:500:43

I think for this to be useful, it would have to work the same way nix run works in unstable Nix, where it falls back when mainProgram isn't defined.

@nicoonoclaste
Copy link
Contributor Author

nicoonoclaste commented Sep 18, 2021

I think for this to be useful, it would have to work the same way nix run works in unstable Nix, where it falls back when mainProgram isn't defined.

OK; I was thinking of PRing definitions of mainProgram where useful, but I guess falling back to the eponymous program is a smarter move.

Is there a way to check whether the file exists and is executable, so I can provide a nicer error message in that case?

@ofborg ofborg bot added 10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin. 10.rebuild-linux: 0 This PR does not cause any packages to rebuild on Linux. labels Sep 18, 2021
@alyssais
Copy link
Member

OK; I was thinking of PRing definitions of mainProgram where useful, but I guess falling back to the eponymous program is a smarter move.

That's how I'd have preferred it would work, tbh, but that ship seems to have sailed — it wouldn't make sense to have this function work differently from how Nix does it.

Is there a way to check whether the file exists and is executable, so I can provide a nicer error message in that case?

Not in lib, because the package hasn't necessarily been build at eval time.

@nicoonoclaste
Copy link
Contributor Author

OK; I was thinking of PRing definitions of mainProgram where useful, but I guess falling back to the eponymous program is a smarter move.

That's how I'd have preferred it would work, tbh, but that ship seems to have sailed — it wouldn't make sense to have this function work differently from how Nix does it.

Same, I just didn't remember in the moment that nix run is a thing.

Is there a way to check whether the file exists and is executable, so I can provide a nicer error message in that case?

Not in lib, because the package hasn't necessarily been build at eval time.

That's what I guessed, but I'm not super-familiar w/ some language details so I figured I'd rather ask. Thanks for confirming 💜

@figsoda
Copy link
Member

figsoda commented Sep 18, 2021

should this be renamed to getMainProgram instead?

@alyssais
Copy link
Member

should this be renamed to getMainProgram instead?

mainProgram is pretty clear to me — is there something else you'd expect a function with that name to do?

@nicoonoclaste
Copy link
Contributor Author

should this be renamed to getMainProgram instead?

As Alyssa said, that seemed unambiguous and clear, while still being concise.
I could change it if it is ambiguous to others or inconsistent with other lib functions, though.

@figsoda
Copy link
Member

figsoda commented Sep 18, 2021

I feel like getMainProgram is more consistent with the existing getName, getVersion, and other get*s
mainProgram is not very ambiguous, but there are lib.maintainers and lib.platforms which are also meta attributes

@nicoonoclaste
Copy link
Contributor Author

mainProgram is not very ambiguous, but there are lib.maintainers and lib.platforms which are also meta attributes

I don't understand what you mean here, this seems like a nonsequitur to me.
How do lib.{maintainers, platforms} (which are attrsets) relate to the proposed function?

@figsoda
Copy link
Member

figsoda commented Sep 18, 2021

I think mainProgram is fine, I just personally prefer getMainProgram

@nicoonoclaste
Copy link
Contributor Author

I think mainProgram is fine, I just personally prefer getMainProgram

'k, thanks for clarifying :)

@roberth
Copy link
Member

roberth commented Sep 19, 2021

I'd prefer mainProgram to be a recursively defined passthru attribute that already includes the correct output path. Requires #119942. It'd look like

passthru.mainProgram = "${self}/bin/main";

and

runCommand "greeting-text" { } ''
  ${hello.mainProgram} >$out
''

No function required.

Also nativeBuildInputs exists and should be used because it makes cross compilation work. Of course that doesn't always apply, but it is noteworthy.

@roberth
Copy link
Member

roberth commented Sep 19, 2021

I guess mainProgram could be considered "taken" with the specific meaning that it already has. exe would be a short and sweet alternative.
I find "${hello.exe} >$out" oddly satisfying.

EDIT: I wasn't paying attention. It's in meta, so passthru.mainProgram is an option, albeit a verbose one. Clear names are generally preferable, but I suppose this could be an exception where the cost of brevity is minimal.

@nicoonoclaste
Copy link
Contributor Author

I'd prefer mainProgram to be a recursively defined passthru attribute that already includes the correct output path. Requires #119942. It'd look like [...]

runCommand "greeting-text" { } ''
  ${hello.mainProgram} >$out
''

That's an interesting approach, but #119942 has received no attention from reviewers since May, and currently has merge conflicts, so it seems inappropriate to block other changes on it.

This helper function provides the path to a package's main program, as a string.
It handles correctly multi-output packages, and relies on the package's
`meta.mainProgram` attribute.
Makes things more readable & uniform (see inconsistent use of `.outPath`) and
robust against `chromium` becoming a multi-output package.

No functional change in the current state of nixpkgs:

	nix-repl> :l <nixpkgs>
	Added 14970 variables.

	nix-repl> "${pkgs.chromium}/bin/chromium"
	"/nix/store/4lrnzi90yv1fhlip0l26xb6p2n7402aj-chromium-93.0.4577.82/bin/chromium"

	nix-repl> "${pkgs.chromium.outPath}/bin/chromium"
	"/nix/store/4lrnzi90yv1fhlip0l26xb6p2n7402aj-chromium-93.0.4577.82/bin/chromium"

	nix-repl> lib.mainProgram pkgs.chromium
	"/nix/store/4lrnzi90yv1fhlip0l26xb6p2n7402aj-chromium-93.0.4577.82/bin/chromium"
This behaviour is the same as `nix run`; thanks to @alyssais for the
suggestion.
@nicoonoclaste
Copy link
Contributor Author

Rebased to deal with merge conflict

@nicoonoclaste
Copy link
Contributor Author

@zimbatm I believe Alyssa would like your opinion prior to merging this.

@infinisil
Copy link
Member

I'd prefer mainProgram to be a recursively defined passthru attribute that already includes the correct output path. Requires #119942. It'd look like

passthru.mainProgram = "${self}/bin/main";

and

runCommand "greeting-text" { } ''
  ${hello.mainProgram} >$out
''

No function required.

I really like this, but we don't even need it to be this verbose. Support for this can be embedded into the mkDerivation function itself, such that it automatically sets passthru.mainProgram = "${self}/bin/${meta.mainProgram}"

Since then there would be both bash.mainProgram and bash.meta.mainProgram, we should probably rename one of them so it's not confusing. E.g. we could call the latter meta.mainProgramName instead.

@infinisil
Copy link
Member

Oh, I didn't realize meta.mainProgram is already a thing, never mind the renaming then

@infinisil
Copy link
Member

Something like this seems to work well:

diff --git a/pkgs/stdenv/generic/make-derivation.nix b/pkgs/stdenv/generic/make-derivation.nix
index 56cfa0c503f..01a474ba26c 100644
--- a/pkgs/stdenv/generic/make-derivation.nix
+++ b/pkgs/stdenv/generic/make-derivation.nix
@@ -370,9 +370,8 @@ else let
                    else true);
     };
 
-in
 
-lib.extendDerivation
+self = lib.extendDerivation
   validity.handled
   ({
      overrideAttrs = f: stdenv.mkDerivation (attrs // (f attrs));
@@ -402,10 +401,13 @@ lib.extendDerivation
        args = [ "-c" "export > $out" ];
      });
 
+     mainProgram = "${lib.getBin self.outPath}/bin/${meta.mainProgram or derivationArg.pname or (builtins.parseDrvName derivationArg.name).name}";
+
      inherit meta passthru;
    } //
    # 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)
+  (derivation derivationArg);
+ in self

Copy link
Member

@zimbatm zimbatm left a comment

Choose a reason for hiding this comment

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

Looks good to me. nixpkgs can benefit a lot from having a utility like that.

getMan = getOutput "man";

/* Get the path to a package's main executable */
mainProgram = pkg:
Copy link
Member

Choose a reason for hiding this comment

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

What do you think mainBin to make it a bit shorter? I know it's bike-shedding territory, feel free to ignore.

Copy link
Member

Choose a reason for hiding this comment

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

Please let's keep bin as the directory. The executable files can be exe if we want to keep it short.

@roberth
Copy link
Member

roberth commented Dec 23, 2021

That's an interesting approach, but #119942 has received no attention from reviewers

Then why not review it?

since May, and currently has merge conflicts, so it seems inappropriate to block other changes on it.

Why would that be?


I think we should ask ourselves the question whether we want Nixpkgs to have a good design or just pile convenience upon convenience until we have a big pile of unnecessary code.

Copy link
Member

@roberth roberth left a comment

Choose a reason for hiding this comment

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

This should be solved with an attribute, not a function.

@roberth
Copy link
Member

roberth commented Dec 23, 2021

Actually you don't have to wait for #119942 because you can implement the logic for this directly in mkDerivation, based on meta.mainProgram.

Details I don't think it's the best way to do it, but here you go.
diff --git a/pkgs/stdenv/generic/make-derivation.nix b/pkgs/stdenv/generic/make-derivation.nix
index 8fa30637049..1a8ff222673 100644
--- a/pkgs/stdenv/generic/make-derivation.nix
+++ b/pkgs/stdenv/generic/make-derivation.nix
@@ -411,4 +411,4 @@ lib.extendDerivation
    # should be made available to Nix expressions using the
    # derivation (e.g., in assertions).
    passthru)
-  (derivation derivationArg)
+  (let d = derivation derivationArg; in d // { exe = lib.getBin d + "/bin/${meta.mainProgram}"; } )

@adrian-gierakowski
Copy link
Contributor

I'd prefer mainProgram to be a recursively defined passthru attribute that already includes the correct output path. Requires #119942. It'd look like

passthru.mainProgram = "${self}/bin/main";

and

runCommand "greeting-text" { } ''
  ${hello.mainProgram} >$out
''

No function required.

Also nativeBuildInputs exists and should be used because it makes cross compilation work. Of course that doesn't always apply, but it is noteworthy.

I've been using the same approach via a helper function which takes a derivation and adds passthru.defaultBinPath to it. I'd love to see this become a standard in nixpkgs

@roberth roberth mentioned this pull request Feb 7, 2022
14 tasks
@roberth
Copy link
Member

roberth commented Feb 7, 2022

I don't think it's the best way to do it, but here you go.

This is: #158461

@roberth
Copy link
Member

roberth commented Apr 5, 2022

Let's get this unstuck.
My idea got some pushback (#158461) and there's obviously still a need (#167247).
So to summarize

  • mainProgram is not suitable as shorthand
  • .exe is considered not to be worth the extra memory consumption (an attr and a thunk for each package)

Middle ground seems to be an exe function:

#!${exe bash}
echo hello world

While it doesn't help with my Windows 95 LARP, it is similarly short and therefore usable as a shorthand notation rather than just a utility function for generic code.

@infinisil
Copy link
Member

Being worried about the performance of adding an attribute to each derivation makes me wonder about the optimization potential by removing existing attributes:

nix-repl> lib.attrNames hello 
[ "__ignoreNulls" "all" "args" "buildInputs" "builder" "configureFlags" "depsBuildBuild" "depsBuildBuildPropagated" "depsBuildTarget" "depsBuildTargetPropagated" "depsHostHost" "depsHostHostPropagated" "depsTargetTarget" "depsTargetTargetPropagated" "doCheck" "doInstallCheck" "drvAttrs" "drvPath" "inputDerivation" "meta" "name" "nativeBuildInputs" "out" "outPath" "outputName" "outputs" "override" "overrideAttrs" "overrideDerivation" "passthru" "patches" "pname" "propagatedBuildInputs" "propagatedNativeBuildInputs" "src" "stdenv" "strictDeps" "system" "tests" "type" "userHook" "version" ]

Many of those probably aren't really needed. Note that these are added by the derivation primop itself, but we could theoretically sidestep that by using the derivationStrict primop directly.

@roberth
Copy link
Member

roberth commented Apr 5, 2022

Many of those probably aren't really needed.

Those guts should never have been exposed.
Also we should program with proper composition and less removeAttrs args. I suspect that will save a lot too. It's the kind of "radical" change we need for outputOf anyway.

Maybe create an issue if you want to discuss this more? It's a bit off topic here.

@Artturin Artturin closed this Apr 26, 2022
@Artturin
Copy link
Member

merged #167247

@roberth
Copy link
Member

roberth commented Apr 26, 2022

Thank you @nbraud for starting the conversation and doing the work, even if another implementation got merged in the end.

@nicoonoclaste nicoonoclaste deleted the mainProgram/lib branch August 23, 2023 21:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin. 10.rebuild-linux: 0 This PR does not cause any packages to rebuild on Linux.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants