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 otherstring
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
'';