{ pkgs, buildPackages, lib, stdenv, libiconv, mkNugetDeps, mkNugetSource, gixy }: let inherit (lib) concatMapStringsSep elem escapeShellArg last optionalString stringLength strings types ; in 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 ( lib.optionalAttrs (nameOrPath == "/bin/${name}") { 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.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; }) // lib.optionalAttrs (stdenv.hostPlatform.isDarwin && stdenv.hostPlatform.isAarch64) { # post-link-hook expects codesign_allocate to be in PATH # https://github.com/NixOS/nixpkgs/issues/154203 # https://github.com/NixOS/nixpkgs/issues/148189 nativeBuildInputs = [ stdenv.cc.bintools ]; } // lib.optionalAttrs (nameOrPath == "/bin/${name}") { meta.mainProgram = name; }) '' ${compileScript} ${lib.optionalString strip "${lib.getBin buildPackages.bintools-unwrapped}/bin/${buildPackages.bintools-unwrapped.targetPrefix}strip -S $out"} # Sometimes binaries produced for darwin (e. g. by GHC) won't be valid # mach-o executables from the get-go, but need to be corrected somehow # which is done by fixupPhase. ${lib.optionalString pkgs.stdenvNoCC.hostPlatform.isDarwin "fixupPhase"} ${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}"; # Like writeScript but the first line is a shebang to fish # # Example: # writeFish "example" '' # echo hello world # '' writeFish = makeScriptWriter { interpreter = "${pkgs.fish}/bin/fish --no-config"; check = "${pkgs.fish}/bin/fish --no-config --no-execute"; # syntax check only }; # Like writeScriptBin but the first line is a shebang to fish writeFishBin = name: writeFish "/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 ? [], threadedRuntime ? true, strip ? true }: let appendIfNotSet = el: list: if elem el list then list else list ++ [ el ]; ghcArgs' = if threadedRuntime then appendIfNotSet "-threaded" ghcArgs else ghcArgs; in 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 }: let darwinArgs = lib.optionals stdenv.isDarwin [ "-L${lib.getLib libiconv}/lib" ]; in makeBinWriter { compileScript = '' cp "$contentPath" tmp.rs PATH=${lib.makeBinPath [pkgs.gcc]} ${lib.getBin rustc}/bin/rustc ${lib.escapeShellArgs rustcArgs} ${lib.escapeShellArgs darwinArgs} -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: buildPythonPackages: name: { libraries ? [], flakeIgnore ? [] }: let ignoreAttribute = optionalString (flakeIgnore != []) "--ignore ${concatMapStringsSep "," escapeShellArg flakeIgnore}"; in makeScriptWriter { interpreter = if libraries == [] then python.interpreter else (python.withPackages (ps: libraries)).interpreter ; check = optionalString python.isPy3k (writeDash "pythoncheck.sh" '' exec ${buildPythonPackages.flake8}/bin/flake8 --show-source ${ignoreAttribute} "$1" ''); } name; # writePyPy2 takes a name an attributeset with libraries and some pypy2 sourcecode and # returns an executable # # Example: # writePyPy2 "test_pypy2" { libraries = [ pkgs.pypy2Packages.enum ]; } '' # from enum import Enum # # class Test(Enum): # a = "success" # # print Test.a # '' writePyPy2 = makePythonWriter pkgs.pypy2 pkgs.pypy2Packages buildPackages.pypy2Packages; # writePyPy2Bin takes the same arguments as writePyPy2 but outputs a directory (like writeScriptBin) writePyPy2Bin = name: writePyPy2 "/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 buildPackages.python3Packages; # writePython3Bin takes the same arguments as writePython3 but outputs a directory (like writeScriptBin) writePython3Bin = name: writePython3 "/bin/${name}"; # writePyPy3 takes a name an attributeset with libraries and some pypy3 sourcecode and # returns an executable # # Example: # writePyPy3 "test_pypy3" { libraries = [ pkgs.pypy3Packages.pyyaml ]; } '' # import yaml # # y = yaml.load(""" # - test: success # """) # print(y[0]['test']) # '' writePyPy3 = makePythonWriter pkgs.pypy3 pkgs.pypy3Packages buildPackages.pypy3Packages; # writePyPy3Bin takes the same arguments as writePyPy3 but outputs a directory (like writeScriptBin) writePyPy3Bin = name: writePyPy3 "/bin/${name}"; makeFSharpWriter = { dotnet-sdk ? pkgs.dotnet-sdk, fsi-flags ? "", libraries ? _: [] }: nameOrPath: let fname = last (builtins.split "/" nameOrPath); path = if strings.hasSuffix ".fsx" nameOrPath then nameOrPath else "${nameOrPath}.fsx"; _nugetDeps = mkNugetDeps { name = "${fname}-nuget-deps"; nugetDeps = libraries; }; nuget-source = mkNugetSource { name = "${fname}-nuget-source"; description = "A Nuget source with the dependencies for ${fname}"; deps = [ _nugetDeps ]; }; fsi = writeBash "fsi" '' export HOME=$NIX_BUILD_TOP/.home export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 export DOTNET_CLI_TELEMETRY_OPTOUT=1 export DOTNET_NOLOGO=1 script="$1"; shift ${dotnet-sdk}/bin/dotnet fsi --quiet --nologo --readline- ${fsi-flags} "$@" < "$script" ''; in content: makeScriptWriter { interpreter = fsi; } path '' #i "nuget: ${nuget-source}/lib" ${ content } exit 0 ''; writeFSharp = makeFSharpWriter {}; writeFSharpBin = name: writeFSharp "/bin/${name}"; }