query
On this page

makeScriptWriter

pkgs.writers.makeScriptWriter

Functor
Docs pulled from | This Revision | about 1 hour ago


makeScriptWriter returns a derivation which creates an executable script.

Inputs

config (AttrSet)
interpreter (String)
the interpreter to use for the script.
check (String)
A command to check the script. For example, this could be a linting check.
makeWrapperArgs (Optional, [ String ], Default: [])
Arguments forwarded to (makeWrapper)[#fun-makeWrapper].
nameOrPath (String)
The name of the script or the path to the script. When a string starting with "/" is passed, the script will be created at the specified path in $out. I.e. "/bin/hello" will create a script at $out/bin/hello. Any other string is interpreted as a filename. It must be a POSIX filename starting with a letter, digit, dot, or underscore. Spaces or special characters are not allowed.
content (String)
The content of the script.

This function is used as base implementation for other high-level writer functions.

For example, writeBash can (roughly) be implemented as:

writeBash = makeScriptWriter { interpreter = "${pkgs.bash}/bin/bash"; }

Examples

pkgs.writers.makeScriptWriter dash example

:b makeScriptWriter { interpreter = "${pkgs.dash}/bin/dash"; } "hello" "echo hello world"
-> /nix/store/indvlr9ckmnv4f0ynkmasv2h4fxhand0-hello

The above example creates a script named hello that outputs hello world when executed.

> /nix/store/indvlr9ckmnv4f0ynkmasv2h4fxhand0-hello
hello world

pkgs.writers.makeScriptWriter python example

:b makeScriptWriter { interpreter = "${pkgs.python3}/bin/python"; } "python-hello" "print('hello world')"
-> /nix/store/4kvby1hqr45ffcdrvfpnpj62hanskw93-python-hello
> /nix/store/4kvby1hqr45ffcdrvfpnpj62hanskw93-python-hello
hello world

Noogle detected

This is a Functor

Learn about functors

Implementation

The following is the current implementation of this function.

makeScriptWriter =
    {
      interpreter,
      check ? "",
      makeWrapperArgs ? [ ],
    }:
    nameOrPath: content:
    assert
      (types.path.check nameOrPath)
      || (builtins.match "([0-9A-Za-z._])[0-9A-Za-z._-]*" nameOrPath != null);
    assert (types.path.check content) || (types.str.check content);
    let
      nameIsPath = types.path.check nameOrPath;
      name = last (builtins.split "/" nameOrPath);
      path = if nameIsPath then nameOrPath else "/bin/${name}";
      # The inner derivation which creates the executable under $out/bin (never at $out directly)
      # This is required in order to support wrapping, as wrapped programs consist of
      # at least two files: the executable and the wrapper.
      inner =
        pkgs.runCommandLocal name
          (
            {
              inherit makeWrapperArgs;
              nativeBuildInputs = [ makeBinaryWrapper ];
              meta.mainProgram = name;
            }
            // (
              if (types.str.check content) then
                {
                  inherit content interpreter;
                  passAsFile = [ "content" ];
                }
              else
                {
                  inherit interpreter;
                  contentPath = content;
                }
            )
          )
          ''
            # On darwin a script cannot be used as an interpreter in a shebang but
            # there doesn't seem to be a limit to the size of shebang and multiple
            # arguments to the interpreter are allowed.
            if [[ -n "${toString pkgs.stdenvNoCC.hostPlatform.isDarwin}" ]] && isScript $interpreter
            then
              wrapperInterpreterLine=$(head -1 "$interpreter" | tail -c+3)
              # Get first word from the line (note: xargs echo remove leading spaces)
              wrapperInterpreter=$(echo "$wrapperInterpreterLine" | xargs echo | cut -d " " -f1)

              if isScript $wrapperInterpreter
              then
                echo "error: passed interpreter ($interpreter) is a script which has another script ($wrapperInterpreter) as an interpreter, which is not supported."
                exit 1
              fi

              # This should work as long as wrapperInterpreter is a shell, which is
              # the case for programs wrapped with makeWrapper, like
              # python3.withPackages etc.
              interpreterLine="$wrapperInterpreterLine $interpreter"
            else
              interpreterLine=$interpreter
            fi

            echo "#! $interpreterLine" > $out
            cat "$contentPath" >> $out
            ${optionalString (check != "") ''
              ${check} $out
            ''}
            chmod +x $out

            # Relocate executable
            # Wrap it if makeWrapperArgs are specified
            mv $out tmp
              mkdir -p $out/$(dirname "${path}")
              mv tmp $out/${path}
            if [ -n "''${makeWrapperArgs+''${makeWrapperArgs[@]}}" ]; then
                wrapProgram $out/${path} ''${makeWrapperArgs[@]}
            fi
          '';
    in
    if nameIsPath then
      inner
    # In case nameOrPath is a name, the user intends the executable to be located at $out.
    # This is achieved by creating a separate derivation containing a symlink at $out linking to ${inner}/bin/${name}.
    # This breaks the override pattern.
    # In case this turns out to be a problem, we can still add more magic
    else
      pkgs.runCommandLocal name { } ''
        ln -s ${inner}/bin/${name} $out
      '';