-
-
Notifications
You must be signed in to change notification settings - Fork 18.2k
runInMkShell: init #256153
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
base: master
Are you sure you want to change the base?
runInMkShell: init #256153
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| # pkgs.runInMkShell {#sec-pkgs-runInMkShell} | ||
|
|
||
| `pkgs.runInMkShell` is a function that generates a wrapper script that | ||
| receives the command to be run as arguments and then run it inside a special | ||
| environment created with [`mkShell`](#sec-pkgs-mkShell). | ||
|
||
|
|
||
| It's basically like the `--run` flag of `nix-shell`, but using Nix expressions. | ||
|
||
|
|
||
| `nix-shell` works by setting some specific environment variables and then | ||
| source the setup script from stdenv. | ||
|
|
||
| The setup script from stdenv implements primitives such as the phase and hook | ||
| system that allows modular logic for derivations. | ||
|
|
||
| Because `nix-shell` is coupled with this behaviour that is implemented in | ||
| nixpkgs it isn't used in the "nix3 cli" (in this case, `nix develop` and `nix | ||
| shell`) that uses a much simpler but incomplete approach: just add the | ||
| derivations bin folder to $PATH. | ||
|
|
||
| To have the `nix-shell` behaviour in a `nix3` utility one must wrap a list of | ||
| programs in `mkShell`, which is what `nix-shell` does under the hood | ||
| automatically. | ||
|
|
||
| And this function modularizes it to a wrapper script. It creates a script that | ||
| runs the command passed as arguments in an environment built the same way as | ||
| nix-shell does. | ||
|
|
||
| The generated script can also be embedded in other scripts using the `source` | ||
| command. | ||
|
|
||
| ## Usage {#sec-pkgs-runInMkShell-usage} | ||
|
|
||
| ```nix | ||
| { pkgs ? import <nixpkgs> { } }: | ||
| pkgs.runInMkShell { | ||
| shell = pkgs.bash + "/bin/bash" | ||
|
|
||
| name = "demo-environment"; | ||
|
|
||
| drv = pkgs.mkShell { | ||
| buildInputs = with pkgs.python3Packages; [ numpy pandas ]; | ||
| }; | ||
|
|
||
| prelude = '' | ||
| echo Hello world | ||
| ''; | ||
| }; | ||
| ``` | ||
|
|
||
| ## Attributes {#sec-pkgs-runInMkShell-attributes} | ||
| * `shell` (default: `${pkgs.bash} + "/bin/bash"`): Which shell to use as the | ||
| script shebang. | ||
| * `name` (default: `"${drv.name}-wrapper"`): Name of the derivation for the | ||
| script. | ||
| * `drv`: Result of [`mkShell`](#sec-pkgs-mkShell). Can be also only the | ||
| attrset of the arguments for `mkShell` or the list of the packages. | ||
| * `prelude`: Code to be pasted just before the script runs the command | ||
| passed with the arguments. It has access to the arguments using the | ||
| standard variables such as `$@`. | ||
|
|
||
| ## Function result {#sec-pkgs-runInMkShell-result} | ||
|
|
||
| This function will always generate a folder with a executable script in bin. | ||
|
|
||
| The absolute path of the executable script can always be obtained by using | ||
| `lib.getExe`. | ||
|
|
||
| This executable script will accept a command as the arguments and will setup an | ||
| environment like `nix-shell` does, then run the code in `prelude`, and lastly | ||
| the command passed as parameter. | ||
|
|
||
| Running it without parameters does nothing by default. | ||
|
|
||
| Sourcing the script will run everything but the command that would be passed as | ||
| argument. | ||
|
|
||
| Let the output of the code [right above](#sec-pkgs-runInMkShell-usage) be | ||
| defined as `$script`, an example usage of the script can be: | ||
|
|
||
| ```shell | ||
| $script python -c 'import numpy as np; import pandas as pd; print(np, pd)' | ||
| ``` | ||
|
|
||
| In this case, the script will print "Hello world" because of the prelude, and | ||
| the representations of `np` and `pd` because of Python. | ||
|
|
||
| More usage examples can be found in the function tests at | ||
| `pkgs/build-support/runinmkshell/tests.nix`. All the tests expose the script | ||
| generated in the test. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| { mkShell | ||
| , bash | ||
| , coreutils | ||
| , lib | ||
| , writeScript | ||
| , writeScriptBin | ||
| , writeText | ||
| }: | ||
|
|
||
| # This function creates a wrapper script that runs a command in a shell context. | ||
| { # The shell that will execute the wrapper script | ||
| shell ? bash + "/bin/bash" | ||
| , # The result of calling mkShell | ||
| drv | ||
| , # Which commands to run before the payload call | ||
| prelude ? "" | ||
| , # Name for the script derivation | ||
| name ? null | ||
| , ... | ||
| }: | ||
|
|
||
| let | ||
| shellDrv = | ||
| if lib.isDerivation drv then drv | ||
| else if lib.isList drv then mkShell { buildInputs = drv; } | ||
| else mkShell drv; | ||
|
|
||
| drvName = if name != null then name else "${shellDrv.name}-wrapper"; | ||
|
|
||
| # This function closely mirrors what this Nix code does: | ||
| # https://github.com/NixOS/nix/blob/2.8.0/src/libexpr/primops.cc#L1102 | ||
| # https://github.com/NixOS/nix/blob/2.8.0/src/libexpr/eval.cc#L1981-L2036 | ||
| stringValue = value: | ||
| # We can't just use `toString` on all derivation attributes because that | ||
| # would not put path literals in the closure. So we explicitly copy | ||
| # those into the store here | ||
| if builtins.typeOf value == "path" then "${value}" | ||
| else if builtins.typeOf value == "list" then toString (map stringValue value) | ||
| else toString value; | ||
|
|
||
| # A binary that calls the command to build the derivation | ||
| builder = writeScript "buildDerivation" '' | ||
| exec ${lib.escapeShellArg (stringValue shellDrv.drvAttrs.builder)} ${lib.escapeShellArgs (map stringValue shellDrv.drvAttrs.args)} | ||
| ''; | ||
|
|
||
| # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L992-L1004 | ||
| drvEnv = lib.mapAttrs' (name: value: | ||
| let str = stringValue value; | ||
| in if lib.elem name (shellDrv.drvAttrs.passAsFile or []) | ||
| then lib.nameValuePair "${name}Path" (writeText "pass-as-text-${name}" str) | ||
| else lib.nameValuePair name str | ||
| ) shellDrv.drvAttrs // | ||
| # A mapping from output name to the nix store path where they should end up | ||
| # https://github.com/NixOS/nix/blob/2.8.0/src/libexpr/primops.cc#L1253 | ||
| lib.genAttrs shellDrv.outputs (output: builtins.unsafeDiscardStringContext shellDrv.${output}.outPath); | ||
|
|
||
| staticPath = "${dirOf shell}:${lib.makeBinPath [ builder coreutils ]}"; | ||
|
|
||
| env = drvEnv // { | ||
| # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1027-L1030 | ||
| # PATH = "/path-not-set"; | ||
| # Allows calling bash and `buildDerivation` as the Cmd | ||
| PATH = staticPath; | ||
| }; | ||
|
|
||
| variablePrelude = lib.pipe env [ | ||
| (builtins.mapAttrs (k: v: "export ${k}=${lib.escapeShellArg v}")) | ||
| (builtins.attrValues) | ||
| (builtins.concatStringsSep "\n") | ||
| ]; | ||
|
|
||
| entrypointScript = writeScriptBin drvName '' | ||
| #!${shell} | ||
| unset PATH | ||
| dontAddDisableDepTrack=1 | ||
|
|
||
| ${variablePrelude} | ||
| # Required or source setup fails | ||
| NIX_BUILD_TOP=$(mktemp -d) | ||
|
|
||
| # TODO: https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L506 | ||
| [ -e $stdenv/setup ] && source $stdenv/setup | ||
| PATH=${staticPath}:"$PATH" | ||
| SHELL=${lib.escapeShellArg shell} | ||
| BASH=${lib.escapeShellArg shell} | ||
| set +e | ||
| [ -n "$PS1" -a -z "$NIX_SHELL_PRESERVE_PROMPT" ] && PS1='\n\[\033[1;32m\][nix-shell:\w]\$\[\033[0m\] ' | ||
| if [ "$(type -t runHook)" = function ]; then | ||
| runHook shellHook | ||
| fi | ||
| unset NIX_ENFORCE_PURITY | ||
| shopt -u nullglob | ||
| shopt -s execfail | ||
| ${prelude} | ||
|
|
||
| if [[ "${"$"}{BASH_SOURCE[0]}" == "${"$"}{0}" ]]; then | ||
| "$@" | ||
| fi | ||
| ''; | ||
| in entrypointScript.overrideAttrs (old: { | ||
| passthru = ((shellDrv.passthru or {}) // { | ||
| inherit shellDrv; | ||
| }); | ||
| meta = (old.meta // (shellDrv.meta or {})); | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not quite happy about the name.
runInis a prefix that was also used forrunInLinuxVM, but this is not a derivation wrapper that runs the wrapped builder as part of its builder.MkShellwould suggest that it's specific to that function, but it's more general.mkused to be for functions that are "data constructors"; functions that return their arguments in a specific, checked form.Maybe not the best suggestion, but here's one:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about
buildShellWrapper?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about
buildShellWrapper?