about summary refs log tree commit diff
path: root/pkgs/build-support
diff options
context:
space:
mode:
authorgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>2024-03-04 06:10:33 +0000
committerGitHub <noreply@github.com>2024-03-04 06:10:33 +0000
commit1a3380fec6e3c2d539e58c4be6a4a094d7d14aea (patch)
tree5126600c4a1bdd78816b3d58848d4d7717af84ba /pkgs/build-support
parent7869e723ac038acb7b62bb1112b70bc217f2341b (diff)
parent0d58a849c9c0c6ba8102f0968aa07a64e10eca6e (diff)
Merge master into staging-next
Diffstat (limited to 'pkgs/build-support')
-rw-r--r--pkgs/build-support/writers/scripts.nix498
-rw-r--r--pkgs/build-support/writers/test.nix88
2 files changed, 435 insertions, 151 deletions
diff --git a/pkgs/build-support/writers/scripts.nix b/pkgs/build-support/writers/scripts.nix
index 8a23e5dd4a66d..edc7ede4d5bd2 100644
--- a/pkgs/build-support/writers/scripts.nix
+++ b/pkgs/build-support/writers/scripts.nix
@@ -1,4 +1,14 @@
-{ pkgs, buildPackages, lib, stdenv, libiconv, mkNugetDeps, mkNugetSource, gixy }:
+{
+  buildPackages,
+  gixy,
+  lib,
+  libiconv,
+  makeBinaryWrapper,
+  mkNugetDeps,
+  mkNugetSource,
+  pkgs,
+  stdenv,
+}:
 let
   inherit (lib)
     concatMapStringsSep
@@ -6,7 +16,6 @@ let
     escapeShellArg
     last
     optionalString
-    stringLength
     strings
     types
     ;
@@ -18,137 +27,285 @@ rec {
   # Examples:
   #   writeBash = makeScriptWriter { interpreter = "${pkgs.bash}/bin/bash"; }
   #   makeScriptWriter { interpreter = "${pkgs.dash}/bin/dash"; } "hello" "echo hello world"
-  makeScriptWriter = { interpreter, check ? "" }: nameOrPath: content:
+  makeScriptWriter = { interpreter, check ? "", makeWrapperArgs ? [], }: 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
+      nameIsPath = types.path.check nameOrPath;
       name = last (builtins.split "/" nameOrPath);
-    in
+      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.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
+        '';
 
-    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:
+  makeBinWriter = { compileScript, strip ? true, makeWrapperArgs ? [] }: 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
+      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;
+            passAsFile = [ "content" ];
+          } else {
+            contentPath = content;
+                  }
+          )
+        )
+        ''
+          ${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"}
+          mv $out tmp
+          mkdir -p $out/$(dirname "${path}")
+          mv tmp $out/${path}
+          if [ -n "''${makeWrapperArgs+''${makeWrapperArgs[@]}}" ]; then
+            wrapProgram $out/${path} ''${makeWrapperArgs[@]}
+          fi
+        '';
     in
-    pkgs.runCommand name ((if (types.str.check content) then {
-      inherit content;
-      passAsFile = [ "content" ];
-    } else {
-      contentPath = content;
-    }) // 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}
-      ''}
-    '';
+      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
+      '';
 
   # Like writeScript but the first line is a shebang to bash
   #
-  # Example:
+  # Can be called with or without extra arguments.
+  #
+  # Example without arguments:
   #   writeBash "example" ''
   #     echo hello world
   #   ''
-  writeBash = makeScriptWriter {
-    interpreter = "${lib.getExe pkgs.bash}";
-  };
+  #
+  # Example with arguments:
+  #   writeBash "example"
+  #     {
+  #       makeWrapperArgs = [
+  #         "--prefix" "PATH" ":" "${pkgs.hello}/bin"
+  #       ];
+  #     }
+  #     ''
+  #       hello
+  #     ''
+  writeBash = name: argsOrScript:
+    if lib.isAttrs argsOrScript && ! lib.isDerivation argsOrScript
+    then makeScriptWriter (argsOrScript // { interpreter = "${lib.getExe pkgs.bash}"; }) name
+    else makeScriptWriter { interpreter = "${lib.getExe pkgs.bash}"; } name argsOrScript;
 
   # Like writeScriptBin but the first line is a shebang to bash
+  #
+  # Can be called with or without extra arguments.
+  #
+  # Example without arguments:
+  #   writeBashBin "example" ''
+  #     echo hello world
+  #   ''
+  #
+  # Example with arguments:
+  #  writeBashBin "example"
+  #    {
+  #      makeWrapperArgs = [
+  #        "--prefix", "PATH", ":", "${pkgs.hello}/bin",
+  #      ];
+  #    }
+  #    ''
+  #      hello
+  #    ''
   writeBashBin = name:
     writeBash "/bin/${name}";
 
   # Like writeScript but the first line is a shebang to dash
   #
-  # Example:
+  # Can be called with or without extra arguments.
+  #
+  # Example without arguments:
   #   writeDash "example" ''
   #     echo hello world
   #   ''
-  writeDash = makeScriptWriter {
-    interpreter = "${lib.getExe pkgs.dash}";
-  };
+  #
+  # Example with arguments:
+  #   writeDash "example"
+  #     {
+  #       makeWrapperArgs = [
+  #         "--prefix", "PATH", ":", "${pkgs.hello}/bin",
+  #       ];
+  #     }
+  #     ''
+  #       hello
+  #     ''
+  writeDash = name: argsOrScript:
+    if lib.isAttrs argsOrScript && ! lib.isDerivation argsOrScript
+    then makeScriptWriter (argsOrScript // { interpreter = "${lib.getExe pkgs.dash}"; }) name
+    else makeScriptWriter { interpreter = "${lib.getExe pkgs.dash}"; } name argsOrScript;
 
   # Like writeScriptBin but the first line is a shebang to dash
+  #
+  # Can be called with or without extra arguments.
+  #
+  # Example without arguments:
+  #   writeDashBin "example" ''
+  #     echo hello world
+  #   ''
+  #
+  # Example with arguments:
+  #  writeDashBin "example"
+  #    {
+  #      makeWrapperArgs = [
+  #        "--prefix", "PATH", ":", "${pkgs.hello}/bin",
+  #      ];
+  #    }
+  #    ''
+  #      hello
+  #    ''
   writeDashBin = name:
     writeDash "/bin/${name}";
 
   # Like writeScript but the first line is a shebang to fish
   #
-  # Example:
+  # Can be called with or without extra arguments.
+  #
+  # Example without arguments:
   #   writeFish "example" ''
   #     echo hello world
   #   ''
-  writeFish = makeScriptWriter {
-    interpreter = "${lib.getExe pkgs.fish} --no-config";
-    check = "${lib.getExe pkgs.fish} --no-config --no-execute";  # syntax check only
-  };
+  #
+  # Example with arguments:
+  #   writeFish "example"
+  #     {
+  #       makeWrapperArgs = [
+  #         "--prefix", "PATH", ":", "${pkgs.hello}/bin",
+  #       ];
+  #     }
+  #     ''
+  #       hello
+  #     ''
+  writeFish = name: argsOrScript:
+    if lib.isAttrs argsOrScript && ! lib.isDerivation argsOrScript
+    then makeScriptWriter (argsOrScript // {
+      interpreter = "${lib.getExe pkgs.fish} --no-config";
+      check = "${lib.getExe pkgs.fish} --no-config --no-execute";  # syntax check only
+    }) name
+    else makeScriptWriter {
+      interpreter = "${lib.getExe pkgs.fish} --no-config";
+      check = "${lib.getExe pkgs.fish} --no-config --no-execute";  # syntax check only
+    } name argsOrScript;
 
   # Like writeScriptBin but the first line is a shebang to fish
+  #
+  # Can be called with or without extra arguments.
+  #
+  # Example without arguments:
+  #   writeFishBin "example" ''
+  #     echo hello world
+  #   ''
+  #
+  # Example with arguments:
+  #   writeFishBin "example"
+  #     {
+  #       makeWrapperArgs = [
+  #         "--prefix", "PATH", ":", "${pkgs.hello}/bin",
+  #       ];
+  #     }
+  #     ''
+  #       hello
+  #     ''
   writeFishBin = name:
     writeFish "/bin/${name}";
 
@@ -162,11 +319,12 @@ rec {
   #     main = launchMissiles
   #   '';
   writeHaskell = name: {
-    libraries ? [],
     ghc ? pkgs.ghc,
     ghcArgs ? [],
+    libraries ? [],
+    makeWrapperArgs ? [],
+    strip ? true,
     threadedRuntime ? true,
-    strip ? true
   }:
     let
       appendIfNotSet = el: list: if elem el list then list else list ++ [ el ];
@@ -178,7 +336,7 @@ rec {
         ${(ghc.withPackages (_: libraries ))}/bin/ghc ${lib.escapeShellArgs ghcArgs'} tmp.hs
         mv tmp $out
       '';
-      inherit strip;
+      inherit makeWrapperArgs strip;
     } name;
 
   # writeHaskellBin takes the same arguments as writeHaskell but outputs a directory (like writeScriptBin)
@@ -187,36 +345,72 @@ rec {
 
   # Like writeScript but the first line is a shebang to nu
   #
-  # Example:
+  # Can be called with or without extra arguments.
+  #
+  # Example without arguments:
   #   writeNu "example" ''
   #     echo hello world
   #   ''
-  writeNu = makeScriptWriter {
-    interpreter = "${lib.getExe pkgs.nushell} --no-config-file";
-  };
+  #
+  # Example with arguments:
+  #   writeNu "example"
+  #     {
+  #       makeWrapperArgs = [
+  #         "--prefix", "PATH", ":", "${pkgs.hello}/bin",
+  #       ];
+  #     }
+  #     ''
+  #       hello
+  #     ''
+  writeNu = name: argsOrScript:
+    if lib.isAttrs argsOrScript && ! lib.isDerivation argsOrScript
+    then makeScriptWriter (argsOrScript // { interpreter = "${lib.getExe pkgs.nushell} --no-config-file"; }) name
+    else makeScriptWriter { interpreter = "${lib.getExe pkgs.nushell} --no-config-file"; } name argsOrScript;
+
 
   # Like writeScriptBin but the first line is a shebang to nu
+  #
+  # Can be called with or without extra arguments.
+  #
+  # Example without arguments:
+  #   writeNuBin "example" ''
+  #     echo hello world
+  #   ''
+  #
+  # Example with arguments:
+  #   writeNuBin "example"
+  #     {
+  #       makeWrapperArgs = [
+  #         "--prefix", "PATH", ":", "${pkgs.hello}/bin",
+  #       ];
+  #     }
+  #    ''
+  #      hello
+  #    ''
   writeNuBin = name:
     writeNu "/bin/${name}";
 
   # makeRubyWriter takes ruby and compatible rubyPackages and produces ruby script writer,
   # If any libraries are specified, ruby.withPackages is used as interpreter, otherwise the "bare" ruby is used.
-  makeRubyWriter = ruby: rubyPackages: buildRubyPackages: name: { libraries ? [], }:
-  makeScriptWriter {
-    interpreter =
-      if libraries == []
-      then "${ruby}/bin/ruby"
-      else "${(ruby.withPackages (ps: libraries))}/bin/ruby";
-    # Rubocop doesnt seem to like running in this fashion.
-    #check = (writeDash "rubocop.sh" ''
-    #  exec ${lib.getExe buildRubyPackages.rubocop} "$1"
-    #'');
-  } name;
+  makeRubyWriter = ruby: rubyPackages: buildRubyPackages: name: { libraries ? [], ... } @ args:
+  makeScriptWriter (
+    (builtins.removeAttrs args ["libraries"])
+    // {
+      interpreter =
+        if libraries == []
+        then "${ruby}/bin/ruby"
+        else "${(ruby.withPackages (ps: libraries))}/bin/ruby";
+      # Rubocop doesn't seem to like running in this fashion.
+      #check = (writeDash "rubocop.sh" ''
+      #  exec ${lib.getExe buildRubyPackages.rubocop} "$1"
+      #'');
+    }
+  ) name;
 
   # Like writeScript but the first line is a shebang to ruby
   #
   # Example:
-  #   writeRuby "example" ''
+  #   writeRuby "example" { libraries = [ pkgs.rubyPackages.git ]; } ''
   #    puts "hello world"
   #   ''
   writeRuby = makeRubyWriter pkgs.ruby pkgs.rubyPackages buildPackages.rubyPackages;
@@ -227,17 +421,20 @@ rec {
   # makeLuaWriter takes lua and compatible luaPackages and produces lua script writer,
   # which validates the script with luacheck at build time. If any libraries are specified,
   # lua.withPackages is used as interpreter, otherwise the "bare" lua is used.
-  makeLuaWriter = lua: luaPackages: buildLuaPackages: name: { libraries ? [], }:
-  makeScriptWriter {
-    interpreter = lua.interpreter;
-      # if libraries == []
-      # then lua.interpreter
-      # else (lua.withPackages (ps: libraries)).interpreter
-      # This should support packages! I just cant figure out why some dependency collision happens whenever I try to run this.
-    check = (writeDash "luacheck.sh" ''
-      exec ${buildLuaPackages.luacheck}/bin/luacheck "$1"
-    '');
-  } name;
+  makeLuaWriter = lua: luaPackages: buildLuaPackages: name: { libraries ? [], ... } @ args:
+  makeScriptWriter (
+    (builtins.removeAttrs args ["libraries"])
+    // {
+      interpreter = lua.interpreter;
+        # if libraries == []
+        # then lua.interpreter
+        # else (lua.withPackages (ps: libraries)).interpreter
+        # This should support packages! I just cant figure out why some dependency collision happens whenever I try to run this.
+      check = (writeDash "luacheck.sh" ''
+        exec ${buildLuaPackages.luacheck}/bin/luacheck "$1"
+      '');
+    }
+   ) name;
 
   # writeLua takes a name an attributeset with libraries and some lua source code and
   # returns an executable (should also work with luajit)
@@ -265,9 +462,10 @@ rec {
     writeLua "/bin/${name}";
 
   writeRust = name: {
-      rustc ? pkgs.rustc,
-      rustcArgs ? [],
-      strip ? true
+    makeWrapperArgs ? [],
+    rustc ? pkgs.rustc,
+    rustcArgs ? [],
+    strip ? true,
   }:
   let
     darwinArgs = lib.optionals stdenv.isDarwin [ "-L${lib.getLib libiconv}/lib" ];
@@ -277,7 +475,7 @@ rec {
         cp "$contentPath" tmp.rs
         PATH=${lib.makeBinPath [pkgs.gcc]} ${rustc}/bin/rustc ${lib.escapeShellArgs rustcArgs} ${lib.escapeShellArgs darwinArgs} -o "$out" tmp.rs
       '';
-      inherit strip;
+      inherit makeWrapperArgs strip;
     } name;
 
   writeRustBin = name:
@@ -337,10 +535,13 @@ rec {
   #     use boolean;
   #     print "Howdy!\n" if true;
   #   ''
-  writePerl = name: { libraries ? [] }:
-    makeScriptWriter {
-      interpreter = "${lib.getExe (pkgs.perl.withPackages (p: libraries))}";
-    } name;
+  writePerl = name: { libraries ? [], ... } @ args:
+    makeScriptWriter (
+      (builtins.removeAttrs args ["libraries"])
+      // {
+        interpreter = "${lib.getExe (pkgs.perl.withPackages (p: libraries))}";
+      }
+    ) name;
 
   # writePerlBin takes the same arguments as writePerl but outputs a directory (like writeScriptBin)
   writePerlBin = name:
@@ -349,22 +550,27 @@ rec {
   # 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 ? [] }:
+  makePythonWriter = python: pythonPackages: buildPythonPackages: name: { libraries ? [], flakeIgnore ? [], ... } @ args:
   let
     ignoreAttribute = optionalString (flakeIgnore != []) "--ignore ${concatMapStringsSep "," escapeShellArg flakeIgnore}";
   in
-  makeScriptWriter {
-    interpreter =
-      if pythonPackages != pkgs.pypy2Packages || pythonPackages != pkgs.pypy3Packages then
-        if libraries == []
-        then python.interpreter
-        else (python.withPackages (ps: libraries)).interpreter
-      else python.interpreter
-    ;
-    check = optionalString python.isPy3k (writeDash "pythoncheck.sh" ''
-      exec ${buildPythonPackages.flake8}/bin/flake8 --show-source ${ignoreAttribute} "$1"
-    '');
-  } name;
+  makeScriptWriter
+    (
+      (builtins.removeAttrs args ["libraries" "flakeIgnore"])
+      // {
+        interpreter =
+          if pythonPackages != pkgs.pypy2Packages || pythonPackages != pkgs.pypy3Packages then
+            if libraries == []
+            then python.interpreter
+            else (python.withPackages (ps: libraries)).interpreter
+          else python.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
@@ -421,7 +627,7 @@ rec {
     writePyPy3 "/bin/${name}";
 
 
-  makeFSharpWriter = { dotnet-sdk ? pkgs.dotnet-sdk, fsi-flags ? "", libraries ? _: [] }: nameOrPath:
+  makeFSharpWriter = { dotnet-sdk ? pkgs.dotnet-sdk, fsi-flags ? "", libraries ? _: [], ... } @ args: nameOrPath:
   let
     fname = last (builtins.split "/" nameOrPath);
     path = if strings.hasSuffix ".fsx" nameOrPath then nameOrPath else "${nameOrPath}.fsx";
@@ -442,9 +648,12 @@ rec {
       ${lib.getExe dotnet-sdk} fsi --quiet --nologo --readline- ${fsi-flags} "$@" < "$script"
     '';
 
-  in content: makeScriptWriter {
-    interpreter = fsi;
-  } path
+  in content: makeScriptWriter (
+    (builtins.removeAttrs args ["dotnet-sdk" "fsi-flags" "libraries"])
+    // {
+      interpreter = fsi;
+    }
+  ) path
   ''
     #i "nuget: ${nuget-source}/lib"
     ${ content }
@@ -456,5 +665,4 @@ rec {
 
   writeFSharpBin = name:
     writeFSharp "/bin/${name}";
-
 }
diff --git a/pkgs/build-support/writers/test.nix b/pkgs/build-support/writers/test.nix
index 982c550d28e08..df0eb340d9ae4 100644
--- a/pkgs/build-support/writers/test.nix
+++ b/pkgs/build-support/writers/test.nix
@@ -1,13 +1,8 @@
-{ glib
-, haskellPackages
+{ haskellPackages
 , lib
 , nodePackages
 , perlPackages
-, pypy2Packages
 , python3Packages
-, pypy3Packages
-, luaPackages
-, rubyPackages
 , runCommand
 , testers
 , writers
@@ -310,4 +305,85 @@ lib.recurseIntoAttrs {
       expected = "hello: world\n";
     };
   };
+
+  wrapping = lib.recurseIntoAttrs {
+    bash-bin = expectSuccessBin (
+      writeBashBin "test-writers-wrapping-bash-bin"
+        {
+          makeWrapperArgs = [
+            "--set"
+            "ThaigerSprint"
+            "Thailand"
+          ];
+        }
+        ''
+          if [[ "$ThaigerSprint" == "Thailand" ]]; then
+            echo "success"
+          fi
+        ''
+    );
+
+    bash = expectSuccess (
+      writeBash "test-writers-wrapping-bash"
+        {
+          makeWrapperArgs = [
+            "--set"
+            "ThaigerSprint"
+            "Thailand"
+          ];
+        }
+        ''
+          if [[ "$ThaigerSprint" == "Thailand" ]]; then
+            echo "success"
+          fi
+        ''
+    );
+
+    python = expectSuccess (
+      writePython3 "test-writers-wrapping-python"
+        {
+          makeWrapperArgs = [
+            "--set"
+            "ThaigerSprint"
+            "Thailand"
+          ];
+        }
+        ''
+          import os
+
+          if os.environ.get("ThaigerSprint") == "Thailand":
+              print("success")
+        ''
+    );
+
+    rust = expectSuccess (
+      writeRust "test-writers-wrapping-rust"
+        {
+          makeWrapperArgs = [
+            "--set"
+            "ThaigerSprint"
+            "Thailand"
+          ];
+        }
+        ''
+          fn main(){
+            if std::env::var("ThaigerSprint").unwrap() == "Thailand" {
+              println!("success")
+            }
+          }
+        ''
+    );
+
+    no-empty-wrapper = let
+      bin = writeBashBin "bin" { makeWrapperArgs = []; } ''true'';
+    in runCommand "run-test-writers-wrapping-no-empty-wrapper" {} ''
+      ls -A ${bin}/bin
+      if [ $(ls -A ${bin}/bin | wc -l) -eq 1 ]; then
+        touch $out
+      else
+        echo "Error: Empty wrapper was created" >&2
+        exit 1
+      fi
+    '';
+  };
 }