{ pkgs, lib, gawk, gnused, gixy }: with lib; rec { # Base implementation for non-compiled executables. # Takes an interpreter, for example `${pkgs.bash}/bin/bash` # # Examples: # writeBash = makeScriptWriter { interpreter = "${pkgs.bash}/bin/bash"; } # makeScriptWriter { interpreter = "${pkgs.dash}/bin/dash"; } "hello" "echo hello world" makeScriptWriter = { interpreter, check ? "" }: nameOrPath: content: assert lib.or (types.path.check nameOrPath) (builtins.match "([0-9A-Za-z._])[0-9A-Za-z._-]*" nameOrPath != null); assert lib.or (types.path.check content) (types.str.check content); let name = last (builtins.split "/" nameOrPath); in pkgs.runCommandLocal 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.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 ${optionalString (types.path.check nameOrPath) '' mv $out tmp mkdir -p $out/$(dirname "${nameOrPath}") mv tmp $out/${nameOrPath} ''} ''; # Base implementation for compiled executables. # Takes a compile script, which in turn takes the name as an argument. # # Examples: # writeSimpleC = makeBinWriter { compileScript = name: "gcc -o $out $contentPath"; } makeBinWriter = { compileScript, strip ? true }: nameOrPath: content: assert lib.or (types.path.check nameOrPath) (builtins.match "([0-9A-Za-z._])[0-9A-Za-z._-]*" nameOrPath != null); assert lib.or (types.path.check content) (types.str.check content); let name = last (builtins.split "/" nameOrPath); in pkgs.runCommand name (if (types.str.check content) then { inherit content; passAsFile = [ "content" ]; } else { contentPath = content; }) '' ${compileScript} ${lib.optionalString strip "${pkgs.binutils-unwrapped}/bin/strip --strip-unneeded $out"} ${optionalString (types.path.check nameOrPath) '' mv $out tmp mkdir -p $out/$(dirname "${nameOrPath}") mv tmp $out/${nameOrPath} ''} ''; # Like writeScript but the first line is a shebang to bash # # Example: # writeBash "example" '' # echo hello world # '' writeBash = makeScriptWriter { interpreter = "${pkgs.bash}/bin/bash"; }; # Like writeScriptBIn but the first line is a shebang to bash writeBashBin = name: writeBash "/bin/${name}"; # Like writeScript but the first line is a shebang to dash # # Example: # writeDash "example" '' # echo hello world # '' writeDash = makeScriptWriter { interpreter = "${pkgs.dash}/bin/dash"; }; # Like writeScriptBin but the first line is a shebang to dash writeDashBin = name: writeDash "/bin/${name}"; # writeHaskell takes a name, an attrset with libraries and haskell version (both optional) # and some haskell source code and returns an executable. # # Example: # writeHaskell "missiles" { libraries = [ pkgs.haskellPackages.acme-missiles ]; } '' # import Acme.Missiles # # main = launchMissiles # ''; writeHaskell = name: { libraries ? [], ghc ? pkgs.ghc, ghcArgs ? [], strip ? true }: makeBinWriter { compileScript = '' cp $contentPath tmp.hs ${ghc.withPackages (_: libraries )}/bin/ghc ${lib.escapeShellArgs ghcArgs} tmp.hs mv tmp $out ''; inherit strip; } name; # writeHaskellBin takes the same arguments as writeHaskell but outputs a directory (like writeScriptBin) writeHaskellBin = name: writeHaskell "/bin/${name}"; writeRust = name: { rustc ? pkgs.rustc, rustcArgs ? [], strip ? true }: makeBinWriter { compileScript = '' cp "$contentPath" tmp.rs PATH=${makeBinPath [pkgs.gcc]} ${lib.getBin rustc}/bin/rustc ${lib.escapeShellArgs rustcArgs} -o "$out" tmp.rs ''; inherit strip; } name; writeRustBin = name: writeRust "/bin/${name}"; # writeJS takes a name an attributeset with libraries and some JavaScript sourcecode and # returns an executable # # Example: # writeJS "example" { libraries = [ pkgs.nodePackages.uglify-js ]; } '' # var UglifyJS = require("uglify-js"); # var code = "function add(first, second) { return first + second; }"; # var result = UglifyJS.minify(code); # console.log(result.code); # '' writeJS = name: { libraries ? [] }: content: let node-env = pkgs.buildEnv { name = "node"; paths = libraries; pathsToLink = [ "/lib/node_modules" ]; }; in writeDash name '' export NODE_PATH=${node-env}/lib/node_modules exec ${pkgs.nodejs}/bin/node ${pkgs.writeText "js" content} ''; # writeJSBin takes the same arguments as writeJS but outputs a directory (like writeScriptBin) writeJSBin = name: writeJS "/bin/${name}"; awkFormatNginx = builtins.toFile "awkFormat-nginx.awk" '' awk -f {sub(/^[ \t]+/,"");idx=0} /\{/{ctx++;idx=1} /\}/{ctx--} {id="";for(i=idx;i $out gixy $out ''; # writePerl takes a name an attributeset with libraries and some perl sourcecode and # returns an executable # # Example: # writePerl "example" { libraries = [ pkgs.perlPackages.boolean ]; } '' # use boolean; # print "Howdy!\n" if true; # '' writePerl = name: { libraries ? [] }: makeScriptWriter { interpreter = "${pkgs.perl.withPackages (p: libraries)}/bin/perl"; } name; # writePerlBin takes the same arguments as writePerl but outputs a directory (like writeScriptBin) writePerlBin = name: writePerl "/bin/${name}"; # makePythonWriter takes python and compatible pythonPackages and produces python script writer, # which validates the script with flake8 at build time. If any libraries are specified, # python.withPackages is used as interpreter, otherwise the "bare" python is used. makePythonWriter = python: pythonPackages: name: { libraries ? [], flakeIgnore ? [] }: let ignoreAttribute = optionalString (flakeIgnore != []) "--ignore ${concatMapStringsSep "," escapeShellArg flakeIgnore}"; in makeScriptWriter { interpreter = if libraries == [] then "${python}/bin/python" else "${python.withPackages (ps: libraries)}/bin/python" ; check = writeDash "python2check.sh" '' exec ${pythonPackages.flake8}/bin/flake8 --show-source ${ignoreAttribute} "$1" ''; } name; # writePython2 takes a name an attributeset with libraries and some python2 sourcecode and # returns an executable # # Example: # writePython2 "test_python2" { libraries = [ pkgs.python2Packages.enum ]; } '' # from enum import Enum # # class Test(Enum): # a = "success" # # print Test.a # '' writePython2 = makePythonWriter pkgs.python2 pkgs.python2Packages; # writePython2Bin takes the same arguments as writePython2 but outputs a directory (like writeScriptBin) writePython2Bin = name: writePython2 "/bin/${name}"; # writePython3 takes a name an attributeset with libraries and some python3 sourcecode and # returns an executable # # Example: # writePython3 "test_python3" { libraries = [ pkgs.python3Packages.pyyaml ]; } '' # import yaml # # y = yaml.load(""" # - test: success # """) # print(y[0]['test']) # '' writePython3 = makePythonWriter pkgs.python3 pkgs.python3Packages; # writePython3Bin takes the same arguments as writePython3 but outputs a directory (like writeScriptBin) writePython3Bin = name: writePython3 "/bin/${name}"; }