about summary refs log tree commit diff
path: root/pkgs/build-support
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/build-support')
-rw-r--r--pkgs/build-support/appimage/default.nix2
-rw-r--r--pkgs/build-support/bintools-wrapper/default.nix3
-rw-r--r--pkgs/build-support/build-fhsenv-bubblewrap/buildFHSEnv.nix4
-rw-r--r--pkgs/build-support/build-fhsenv-bubblewrap/default.nix6
-rw-r--r--pkgs/build-support/build-maven.nix88
-rw-r--r--pkgs/build-support/deterministic-uname/default.nix22
-rw-r--r--pkgs/build-support/deterministic-uname/deterministic-uname.sh2
-rw-r--r--pkgs/build-support/dlang/builddubpackage/default.nix37
-rw-r--r--pkgs/build-support/dlang/dub-to-nix/default.nix9
-rw-r--r--pkgs/build-support/dlang/dub-to-nix/dub-to-nix.py62
-rw-r--r--pkgs/build-support/dotnet/build-dotnet-global-tool/default.nix2
-rw-r--r--pkgs/build-support/dotnet/build-dotnet-module/default.nix48
-rw-r--r--pkgs/build-support/dotnet/build-dotnet-module/hooks/default.nix75
-rw-r--r--pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-build-hook.sh76
-rw-r--r--pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-check-hook.sh62
-rw-r--r--pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-configure-hook.sh98
-rw-r--r--pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-fixup-hook.sh84
-rw-r--r--pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-install-hook.sh80
-rw-r--r--pkgs/build-support/emacs/melpa.nix32
-rwxr-xr-xpkgs/build-support/fetchgit/nix-prefetch-git2
-rw-r--r--pkgs/build-support/fetchgit/tests.nix7
-rw-r--r--pkgs/build-support/fetchtorrent/default.nix4
-rw-r--r--pkgs/build-support/flutter/default.nix51
-rw-r--r--pkgs/build-support/go/module.nix20
-rw-r--r--pkgs/build-support/go/package.nix2
-rw-r--r--pkgs/build-support/kernel/compress-firmware-xz.nix29
-rw-r--r--pkgs/build-support/kernel/compress-firmware.nix43
-rw-r--r--pkgs/build-support/kernel/make-initrd-ng-tool.nix2
-rw-r--r--pkgs/build-support/kernel/modules-closure.sh2
-rw-r--r--pkgs/build-support/lib/meson.nix4
-rw-r--r--pkgs/build-support/libredirect/default.nix2
-rw-r--r--pkgs/build-support/make-pkgconfigitem/default.nix2
-rw-r--r--pkgs/build-support/node/fetch-yarn-deps/default.nix8
-rw-r--r--pkgs/build-support/ocaml/dune.nix3
-rw-r--r--pkgs/build-support/php/build-composer-project.nix85
-rw-r--r--pkgs/build-support/php/build-pecl.nix98
-rw-r--r--pkgs/build-support/php/builders/default.nix9
-rw-r--r--pkgs/build-support/php/builders/v1/build-composer-project.nix108
-rw-r--r--pkgs/build-support/php/builders/v1/build-composer-repository.nix (renamed from pkgs/build-support/php/build-composer-repository.nix)88
-rw-r--r--pkgs/build-support/php/builders/v1/build-composer-with-plugin.nix161
-rw-r--r--pkgs/build-support/php/builders/v1/hooks/composer-install-hook.sh (renamed from pkgs/build-support/php/hooks/composer-install-hook.sh)2
-rw-r--r--pkgs/build-support/php/builders/v1/hooks/composer-repository-hook.sh (renamed from pkgs/build-support/php/hooks/composer-repository-hook.sh)2
-rw-r--r--pkgs/build-support/php/builders/v1/hooks/composer-with-plugin-vendor-hook.sh93
-rw-r--r--pkgs/build-support/php/builders/v1/hooks/default.nix60
-rw-r--r--pkgs/build-support/php/builders/v1/hooks/php-script-utils.bash (renamed from pkgs/build-support/php/hooks/php-script-utils.bash)15
-rw-r--r--pkgs/build-support/php/hooks/default.nix39
-rw-r--r--pkgs/build-support/php/pkgs/composer-local-repo-plugin.nix113
-rw-r--r--pkgs/build-support/php/pkgs/composer-phar.nix38
-rw-r--r--pkgs/build-support/rust/build-rust-crate/build-crate.nix2
-rw-r--r--pkgs/build-support/rust/build-rust-crate/configure-crate.nix24
-rw-r--r--pkgs/build-support/rust/build-rust-crate/default.nix4
-rw-r--r--pkgs/build-support/rust/build-rust-crate/install-crate.nix2
-rw-r--r--pkgs/build-support/rust/build-rust-crate/test/default.nix49
-rw-r--r--pkgs/build-support/rust/build-rust-package/default.nix6
-rw-r--r--pkgs/build-support/rust/default-crate-overrides.nix12
-rw-r--r--pkgs/build-support/rust/hooks/cargo-build-hook.sh11
-rw-r--r--pkgs/build-support/rust/hooks/cargo-check-hook.sh2
-rw-r--r--pkgs/build-support/rust/hooks/cargo-nextest-hook.sh2
-rw-r--r--pkgs/build-support/rust/hooks/maturin-build-hook.sh2
-rw-r--r--pkgs/build-support/rust/replace-workspace-values.py42
-rw-r--r--pkgs/build-support/setup-hooks/strip.sh16
-rw-r--r--pkgs/build-support/setup-hooks/wrap-gapps-hook/default.nix16
-rw-r--r--pkgs/build-support/singularity-tools/default.nix2
-rw-r--r--pkgs/build-support/src-only/default.nix2
-rw-r--r--pkgs/build-support/testers/default.nix4
-rw-r--r--pkgs/build-support/testers/hasPkgConfigModules/tester.nix34
-rw-r--r--pkgs/build-support/testers/hasPkgConfigModules/tests.nix20
-rw-r--r--pkgs/build-support/testers/lychee.nix69
-rw-r--r--pkgs/build-support/testers/test/default.nix2
-rw-r--r--pkgs/build-support/trivial-builders/test/references/default.nix23
-rw-r--r--pkgs/build-support/vm/default.nix9
-rw-r--r--pkgs/build-support/writers/scripts.nix2
72 files changed, 1455 insertions, 786 deletions
diff --git a/pkgs/build-support/appimage/default.nix b/pkgs/build-support/appimage/default.nix
index 95dc7ffd1cd66..0d44a5ab23e96 100644
--- a/pkgs/build-support/appimage/default.nix
+++ b/pkgs/build-support/appimage/default.nix
@@ -40,7 +40,7 @@ rec {
 
   wrapAppImage = args@{
     src,
-    extraPkgs,
+    extraPkgs ? pkgs: [ ],
     meta ? {},
     ...
   }: buildFHSEnv
diff --git a/pkgs/build-support/bintools-wrapper/default.nix b/pkgs/build-support/bintools-wrapper/default.nix
index 5ca5bc3f5eb3b..2a1fe1344e205 100644
--- a/pkgs/build-support/bintools-wrapper/default.nix
+++ b/pkgs/build-support/bintools-wrapper/default.nix
@@ -60,6 +60,7 @@
 , postLinkSignHook ? null, signingUtils ? null
 }:
 
+assert propagateDoc -> bintools ? man;
 assert nativeTools -> !propagateDoc && nativePrefix != "";
 assert !nativeTools -> bintools != null && coreutils != null && gnugrep != null;
 assert !(nativeLibc && noLibc);
@@ -128,7 +129,7 @@ let
     else if targetPlatform.isRiscV                    then "${sharedLibraryLoader}/lib/ld-linux-riscv*.so.1"
     else if targetPlatform.isLoongArch64              then "${sharedLibraryLoader}/lib/ld-linux-loongarch*.so.1"
     else if targetPlatform.isDarwin                   then "/usr/lib/dyld"
-    else if targetPlatform.isFreeBSD                  then "/libexec/ld-elf.so.1"
+    else if targetPlatform.isFreeBSD                  then "${sharedLibraryLoader}/libexec/ld-elf.so.1"
     else if hasSuffix "pc-gnu" targetPlatform.config then "ld.so.1"
     else "";
 
diff --git a/pkgs/build-support/build-fhsenv-bubblewrap/buildFHSEnv.nix b/pkgs/build-support/build-fhsenv-bubblewrap/buildFHSEnv.nix
index 1e34ad1e5e448..ffd19cfd4a8a0 100644
--- a/pkgs/build-support/build-fhsenv-bubblewrap/buildFHSEnv.nix
+++ b/pkgs/build-support/build-fhsenv-bubblewrap/buildFHSEnv.nix
@@ -116,6 +116,10 @@ let
     export PKG_CONFIG_PATH=/usr/lib/pkgconfig
     export ACLOCAL_PATH=/usr/share/aclocal
 
+    # GStreamer searches for plugins relative to its real binary's location
+    # https://gitlab.freedesktop.org/gstreamer/gstreamer/-/commit/bd97973ce0f2c5495bcda5cccd4f7ef7dcb7febc
+    export GST_PLUGIN_SYSTEM_PATH_1_0=/usr/lib/gstreamer-1.0:/usr/lib32/gstreamer-1.0
+
     ${profile}
   '';
 
diff --git a/pkgs/build-support/build-fhsenv-bubblewrap/default.nix b/pkgs/build-support/build-fhsenv-bubblewrap/default.nix
index 12f3e7d585521..398a99e80e8cd 100644
--- a/pkgs/build-support/build-fhsenv-bubblewrap/default.nix
+++ b/pkgs/build-support/build-fhsenv-bubblewrap/default.nix
@@ -196,8 +196,10 @@ let
     x11_args+=(--tmpfs /tmp/.X11-unix)
 
     # Try to guess X socket path. This doesn't cover _everything_, but it covers some things.
-    if [[ "$DISPLAY" == :* ]]; then
-      display_nr=''${DISPLAY#?}
+    if [[ "$DISPLAY" == *:* ]]; then
+      # recover display number from $DISPLAY formatted [host]:num[.screen]
+      display_nr=''${DISPLAY/#*:} # strip host
+      display_nr=''${display_nr/%.*} # strip screen
       local_socket=/tmp/.X11-unix/X$display_nr
       x11_args+=(--ro-bind-try "$local_socket" "$local_socket")
     fi
diff --git a/pkgs/build-support/build-maven.nix b/pkgs/build-support/build-maven.nix
deleted file mode 100644
index 7ac8afdde225a..0000000000000
--- a/pkgs/build-support/build-maven.nix
+++ /dev/null
@@ -1,88 +0,0 @@
-{ stdenv, maven, runCommand, writeText, fetchurl, lib, requireFile, linkFarm }:
-# Takes an info file generated by mvn2nix
-# (https://github.com/NixOS/mvn2nix-maven-plugin) and builds the maven
-# project with it.
-#
-# repo: A local maven repository with the project's dependencies.
-#
-# settings: A settings.xml to pass to maven to use the repo.
-#
-# build: A simple build derivation that uses mvn compile and package to build
-#        the project.
-#
-# @example
-#     project = pkgs.buildMaven ./project-info.json
-infoFile:
-let
-  info = lib.importJSON infoFile;
-
-  dependencies = lib.flatten (map (dep:
-    let
-      inherit (dep) sha1 groupId artifactId version metadata repository-id;
-      versionDir = dep.unresolved-version or version;
-      authenticated = dep.authenticated or false;
-      url = dep.url or "";
-
-      fetch = if (url != "") then
-        ((if authenticated then requireFile else fetchurl) {
-          inherit url sha1;
-        })
-      else
-        "";
-
-      fetchMetadata = (if authenticated then requireFile else fetchurl) {
-        inherit (metadata) url sha1;
-      };
-
-      layout = "${
-          builtins.replaceStrings [ "." ] [ "/" ] groupId
-        }/${artifactId}/${versionDir}";
-    in lib.optional (url != "") {
-      layout = "${layout}/${fetch.name}";
-      drv = fetch;
-    } ++ lib.optionals (dep ? metadata) ([{
-      layout = "${layout}/maven-metadata-${repository-id}.xml";
-      drv = fetchMetadata;
-    }] ++ lib.optional (fetch != "") {
-      layout = "${layout}/${
-          builtins.replaceStrings [ version ] [ dep.unresolved-version ]
-          fetch.name
-        }";
-      drv = fetch;
-    })) info.dependencies);
-
-  repo = linkFarm "maven-repository" (lib.forEach dependencies (dependency: {
-    name = dependency.layout;
-    path = dependency.drv;
-  }));
-
-  settings = writeText "settings.xml" ''
-    <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
-      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-      xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
-                          http://maven.apache.org/xsd/settings-1.0.0.xsd">
-      <localRepository>${repo}</localRepository>
-    </settings>
-  '';
-
-  src = dirOf infoFile;
-in {
-  inherit repo settings info;
-
-  build = stdenv.mkDerivation {
-    name = "${info.project.artifactId}-${info.project.version}.jar";
-
-    src = builtins.filterSource (path: type:
-      (toString path) != (toString (src + "/target")) && (toString path)
-      != (toString (src + "/.git"))) src;
-
-    buildInputs = [ maven ];
-
-    buildPhase = "mvn --offline --settings ${settings} compile";
-
-    installPhase = ''
-      mvn --offline --settings ${settings} package
-      mv target/*.jar $out
-    '';
-  };
-}
diff --git a/pkgs/build-support/deterministic-uname/default.nix b/pkgs/build-support/deterministic-uname/default.nix
index 6d150557aa9d6..9efaa2558bfb6 100644
--- a/pkgs/build-support/deterministic-uname/default.nix
+++ b/pkgs/build-support/deterministic-uname/default.nix
@@ -5,6 +5,7 @@
 , coreutils
 , getopt
 , modDirVersion ? ""
+, forPlatform ? stdenv.buildPlatform
 }:
 
 substituteAll {
@@ -17,8 +18,8 @@ substituteAll {
 
   inherit coreutils getopt;
 
-  uSystem = if stdenv.buildPlatform.uname.system != null then stdenv.buildPlatform.uname.system else "unknown";
-  inherit (stdenv.buildPlatform.uname) processor;
+  uSystem = if forPlatform.uname.system != null then forPlatform.uname.system else "unknown";
+  inherit (forPlatform.uname) processor;
 
   # uname -o
   # maybe add to lib/systems/default.nix uname attrset
@@ -26,10 +27,12 @@ substituteAll {
   # https://stackoverflow.com/questions/61711186/where-does-host-operating-system-in-uname-c-comes-from
   # https://github.com/coreutils/gnulib/blob/master/m4/host-os.m4
   operatingSystem =
-    if stdenv.buildPlatform.isLinux
+    if forPlatform.isLinux
     then "GNU/Linux"
-    else if stdenv.buildPlatform.isDarwin
+    else if forPlatform.isDarwin
     then "Darwin" # darwin isn't in host-os.m4 so where does this come from?
+    else if stdenv.buildPlatform.isFreeBSD
+    then "FreeBSD"
     else "unknown";
 
   # in os-specific/linux module packages
@@ -42,11 +45,12 @@ substituteAll {
     mainProgram = "uname";
     longDescription = ''
       This package provides a replacement for `uname` whose output depends only
-      on `stdenv.buildPlatform`.  It is meant to be used from within derivations.
-      Many packages' build processes run `uname` at compile time and embed its
-      output into the result of the build.  Since `uname` calls into the kernel,
-      and the Nix sandbox currently does not intercept these calls, builds made
-      on different kernels will produce different results.
+      on `stdenv.buildPlatform`, or a configurable `forPlatform`.  It is meant
+      to be used from within derivations. Many packages' build processes run
+      `uname` at compile time and embed its output into the result of the build.
+      Since `uname` calls into the kernel, and the Nix sandbox currently does
+      not intercept these calls, builds made on different kernels will produce
+      different results.
     '';
     license = [ licenses.mit ];
     maintainers = with maintainers; [ artturin ];
diff --git a/pkgs/build-support/deterministic-uname/deterministic-uname.sh b/pkgs/build-support/deterministic-uname/deterministic-uname.sh
index 31772aeee3cc1..cb6f419b03311 100644
--- a/pkgs/build-support/deterministic-uname/deterministic-uname.sh
+++ b/pkgs/build-support/deterministic-uname/deterministic-uname.sh
@@ -131,6 +131,8 @@ fi
 #  Darwin *nodename* 22.1.0 Darwin Kernel Version 22.1.0: Sun Oct  9 20:14:30 PDT 2022; root:xnu-8792.41.9~2/RELEASE_ARM64_T8103 arm64 arm Darwin
 # NixOS:
 #  Linux *nodename* 6.0.13 #1-NixOS SMP PREEMPT_DYNAMIC Wed Dec 14 10:41:06 UTC 2022 x86_64 GNU/Linux
+# FreeBSD:
+#  FreeBSD *nodename* 14.0-RELEASE FreeBSD 14.0-RELEASE #0 releng/14.0-n265380-f9716eee8ab4: Fri Nov 10 05:57:23 UTC 2023     root@releng1.nyi.freebsd.org:/usr/obj/usr/src/amd64.amd64/sys/GENERIC amd64 amd64 AMD Ryzen 9 5950X 16-Core Processor             FreeBSD
 output=()
 if [[ "$all" = "1" ]]; then
     output+=("$KERNEL_NAME_VAL" "$NODENAME_VAL" "$KERNEL_RELEASE_VAL" "$KERNEL_VERSION_VAL" "$MACHINE_VAL")
diff --git a/pkgs/build-support/dlang/builddubpackage/default.nix b/pkgs/build-support/dlang/builddubpackage/default.nix
index 9295445b0f7c7..31454d5cd69bf 100644
--- a/pkgs/build-support/dlang/builddubpackage/default.nix
+++ b/pkgs/build-support/dlang/builddubpackage/default.nix
@@ -2,6 +2,7 @@
   lib,
   stdenv,
   fetchurl,
+  fetchgit,
   linkFarm,
   dub,
   ldc,
@@ -43,11 +44,27 @@ let
       };
     };
 
+  makeGitDep =
+    {
+      pname,
+      version,
+      repository,
+      sha256,
+    }:
+    {
+      inherit pname version;
+      src = fetchgit {
+        url = repository;
+        rev = version;
+        inherit sha256;
+      };
+    };
+
   lockJson = if lib.isPath dubLock then lib.importJSON dubLock else dubLock;
+  depsRaw = lib.mapAttrsToList (pname: args: { inherit pname; } // args) lockJson.dependencies;
 
-  lockedDeps = lib.mapAttrsToList (
-    pname: { version, sha256 }: makeDubDep { inherit pname version sha256; }
-  ) lockJson.dependencies;
+  dubDeps = map makeDubDep (lib.filter (args: !(args ? repository)) depsRaw);
+  gitDeps = map makeGitDep (lib.filter (args: args ? repository) depsRaw);
 
   # a directory with multiple single element registries
   # one big directory with all .zip files leads to version parsing errors
@@ -56,7 +73,7 @@ let
     map (dep: {
       name = "${dep.pname}/${dep.pname}-${dep.version}.zip";
       path = dep.src;
-    }) lockedDeps
+    }) dubDeps
   );
 
   combinedFlags = "--skip-registry=all --compiler=${lib.getExe compiler} ${toString dubFlags}";
@@ -79,12 +96,18 @@ stdenv.mkDerivation (
         runHook preConfigure
 
         export DUB_HOME="$NIX_BUILD_TOP/.dub"
-        mkdir -p $DUB_HOME
+        mkdir -p "$DUB_HOME"
 
-        # register dependencies
+        # register dub dependencies
         ${lib.concatMapStringsSep "\n" (dep: ''
           dub fetch ${dep.pname}@${dep.version} --cache=user --skip-registry=standard --registry=file://${dubRegistryBase}/${dep.pname}
-        '') lockedDeps}
+        '') dubDeps}
+
+        # register git dependencies
+        ${lib.concatMapStringsSep "\n" (dep: ''
+          mkdir -p "$DUB_HOME/packages/${dep.pname}/${dep.version}"
+          cp -r --no-preserve=all ${dep.src} "$DUB_HOME/packages/${dep.pname}/${dep.version}/${dep.pname}"
+        '') gitDeps}
 
         runHook postConfigure
       '';
diff --git a/pkgs/build-support/dlang/dub-to-nix/default.nix b/pkgs/build-support/dlang/dub-to-nix/default.nix
index 53a2e99c18df5..87db7eed09331 100644
--- a/pkgs/build-support/dlang/dub-to-nix/default.nix
+++ b/pkgs/build-support/dlang/dub-to-nix/default.nix
@@ -4,8 +4,15 @@
   makeWrapper,
   python3,
   nix,
+  nix-prefetch-git,
 }:
 
+let
+  binPath = lib.makeBinPath [
+    nix
+    nix-prefetch-git
+  ];
+in
 runCommand "dub-to-nix"
   {
     nativeBuildInputs = [ makeWrapper ];
@@ -15,5 +22,5 @@ runCommand "dub-to-nix"
     install -Dm755 ${./dub-to-nix.py} "$out/bin/dub-to-nix"
     patchShebangs "$out/bin/dub-to-nix"
     wrapProgram "$out/bin/dub-to-nix" \
-        --prefix PATH : ${lib.makeBinPath [ nix ]}
+        --prefix PATH : ${binPath}
   ''
diff --git a/pkgs/build-support/dlang/dub-to-nix/dub-to-nix.py b/pkgs/build-support/dlang/dub-to-nix/dub-to-nix.py
index 48a9f241348a4..fbb51960ad7b6 100644
--- a/pkgs/build-support/dlang/dub-to-nix/dub-to-nix.py
+++ b/pkgs/build-support/dlang/dub-to-nix/dub-to-nix.py
@@ -4,10 +4,13 @@ import sys
 import json
 import os
 import subprocess
+import string
+
 
 def eprint(text: str):
     print(text, file=sys.stderr)
 
+
 if not os.path.exists("dub.selections.json"):
     eprint("The file `dub.selections.json` does not exist in the current working directory")
     eprint("run `dub upgrade --annotate` to generate it")
@@ -16,24 +19,53 @@ if not os.path.exists("dub.selections.json"):
 with open("dub.selections.json") as f:
     selectionsJson = json.load(f)
 
-versionDict: dict[str, str] = selectionsJson["versions"]
+depsDict: dict = selectionsJson["versions"]
+
+# For each dependency expand non-expanded version into a dict with a "version" key
+depsDict = {pname: (versionOrDepDict if isinstance(versionOrDepDict, dict) else {"version": versionOrDepDict}) for (pname, versionOrDepDict) in depsDict.items()}
+
+# Don't process path-type selections
+depsDict = {pname: depDict for (pname, depDict) in depsDict.items() if "path" not in depDict}
 
-for pname in versionDict:
-    version = versionDict[pname]
+# Pre-validate selections before trying to fetch
+for pname in depsDict:
+    depDict = depsDict[pname]
+    version = depDict["version"]
     if version.startswith("~"):
-        eprint(f'Package "{pname}" has a branch-type version "{version}", which doesn\'t point to a fixed version')
-        eprint("You can resolve it by manually changing the required version to a fixed one inside `dub.selections.json`")
-        eprint("When packaging, you might need to create a patch for `dub.sdl` or `dub.json` to accept the changed version")
+        eprint(f'Expected version of "{pname}" to be non-branch type')
+        eprint(f'Found: "{version}"')
+        eprint("Please specify a non-branch version inside `dub.selections.json`")
+        eprint("When packaging, you might also need to patch the version value in the appropriate places (`dub.selections.json`, dub.sdl`, `dub.json`)")
         sys.exit(1)
+    if "repository" in depDict:
+        repository = depDict["repository"]
+        if not repository.startswith("git+"):
+            eprint(f'Expected repository field of "{pname}" to begin with "git+"')
+            eprint(f'Found: "{repository}"')
+            sys.exit(1)
+        if (len(version) < 7 or len(version) > 40 or not all(c in string.hexdigits for c in version)):
+            eprint(f'Expected version field of "{pname}" to begin be a valid git revision')
+            eprint(f'Found: "{version}"')
+            sys.exit(1)
 
-lockedDependenciesDict: dict[str, dict[str, str]] = {}
+lockedDepsDict: dict[str, dict[str, str]] = {}
 
-for pname in versionDict:
-    version = versionDict[pname]
-    eprint(f"Fetching {pname}@{version}")
-    url = f"https://code.dlang.org/packages/{pname}/{version}.zip"
-    command = ["nix-prefetch-url", "--type", "sha256", url]
-    sha256 = subprocess.run(command, check=True, text=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.rstrip()
-    lockedDependenciesDict[pname] = {"version": version, "sha256": sha256}
+for pname in depsDict:
+    depDict = depsDict[pname]
+    version = depDict["version"]
+    if "repository" in depDict:
+        repository = depDict["repository"]
+        strippedRepo = repository[4:]
+        eprint(f"Fetching {pname}@{version} ({strippedRepo})")
+        command = ["nix-prefetch-git", strippedRepo, version]
+        rawRes = subprocess.run(command, check=True, text=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout
+        sha256 = json.loads(rawRes)["sha256"]
+        lockedDepsDict[pname] = {"version": version, "repository": repository, "sha256": sha256}
+    else:
+        eprint(f"Fetching {pname}@{version}")
+        url = f"https://code.dlang.org/packages/{pname}/{version}.zip"
+        command = ["nix-prefetch-url", "--type", "sha256", url]
+        sha256 = subprocess.run(command, check=True, text=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.rstrip()
+        lockedDepsDict[pname] = {"version": version, "sha256": sha256}
 
-print(json.dumps({"dependencies": lockedDependenciesDict}, indent=2))
+print(json.dumps({"dependencies": lockedDepsDict}, indent=2))
diff --git a/pkgs/build-support/dotnet/build-dotnet-global-tool/default.nix b/pkgs/build-support/dotnet/build-dotnet-global-tool/default.nix
index 16cf029ca3451..7ae9cfc9f6618 100644
--- a/pkgs/build-support/dotnet/build-dotnet-global-tool/default.nix
+++ b/pkgs/build-support/dotnet/build-dotnet-global-tool/default.nix
@@ -28,7 +28,7 @@ buildDotnetModule (args // {
     ] ++ (nugetDeps fetchNuGet);
   };
 
-  projectFile = "";
+  dotnetGlobalTool = true;
 
   useDotnetFromEnv = true;
 
diff --git a/pkgs/build-support/dotnet/build-dotnet-module/default.nix b/pkgs/build-support/dotnet/build-dotnet-module/default.nix
index 15a753df07728..7b88b16064bca 100644
--- a/pkgs/build-support/dotnet/build-dotnet-module/default.nix
+++ b/pkgs/build-support/dotnet/build-dotnet-module/default.nix
@@ -69,7 +69,7 @@
 , disabledTests ? [ ]
   # The project file to run unit tests against. This is usually referenced in the regular project file, but sometimes it needs to be manually set.
   # It gets restored and build, but not installed. You may need to regenerate your nuget lockfile after setting this.
-, testProjectFile ? ""
+, testProjectFile ? null
 
   # The type of build to perform. This is passed to `dotnet` with the `--configuration` flag. Possible values are `Release`, `Debug`, etc.
 , buildType ? "Release"
@@ -84,23 +84,22 @@
 , dotnet-sdk ? dotnetCorePackages.sdk_6_0
   # The dotnet runtime to use.
 , dotnet-runtime ? dotnetCorePackages.runtime_6_0
-  # The dotnet SDK to run tests against. This can differentiate from the SDK compiled against.
-, dotnet-test-sdk ? dotnet-sdk
 , ...
 } @ args:
 
 let
+  projectFiles =
+    lib.optionals (projectFile != null) (lib.toList projectFile);
+  testProjectFiles =
+    lib.optionals (testProjectFile != null) (lib.toList testProjectFile);
+
   platforms =
     if args ? meta.platforms
     then lib.intersectLists args.meta.platforms dotnet-sdk.meta.platforms
     else dotnet-sdk.meta.platforms;
 
   inherit (callPackage ./hooks {
-    inherit dotnet-sdk dotnet-test-sdk disabledTests nuget-source dotnet-runtime runtimeDeps buildType;
-    runtimeId =
-      if runtimeId != null
-      then runtimeId
-      else dotnetCorePackages.systemToDotnetRid stdenvNoCC.targetPlatform.system;
+    inherit dotnet-sdk dotnet-runtime;
   }) dotnetConfigureHook dotnetBuildHook dotnetCheckHook dotnetInstallHook dotnetFixupHook;
 
   localDeps =
@@ -121,7 +120,7 @@ let
   # contains the actual package dependencies
   dependenciesSource = mkNugetSource {
     name = "${name}-dependencies-source";
-    description = "A Nuget source with the dependencies for ${name}";
+    description = "Nuget source with the dependencies for ${name}";
     deps = [ _nugetDeps ] ++ lib.optional (localDeps != null) localDeps;
   };
 
@@ -145,6 +144,19 @@ let
   nugetDepsFile = _nugetDeps.sourceFile;
 in
 stdenvNoCC.mkDerivation (args // {
+  dotnetInstallPath = installPath;
+  dotnetExecutables = executables;
+  dotnetBuildType = buildType;
+  dotnetProjectFiles = projectFiles;
+  dotnetTestProjectFiles = testProjectFiles;
+  dotnetDisabledTests = disabledTests;
+  dotnetRuntimeId = runtimeId;
+  nugetSource = nuget-source;
+  dotnetRuntimeDeps = map lib.getLib runtimeDeps;
+  dotnetSelfContainedBuild = selfContainedBuild;
+  dotnetUseAppHost = useAppHost;
+  inherit useDotnetFromEnv;
+
   nativeBuildInputs = args.nativeBuildInputs or [ ] ++ [
     dotnetConfigureHook
     dotnetBuildHook
@@ -174,7 +186,7 @@ stdenvNoCC.mkDerivation (args // {
        else [ ]));
 
   makeWrapperArgs = args.makeWrapperArgs or [ ] ++ [
-    "--prefix LD_LIBRARY_PATH : ${dotnet-sdk.icu}/lib"
+    "--prefix" "LD_LIBRARY_PATH" ":" "${dotnet-sdk.icu}/lib"
   ];
 
   # Stripping breaks the executable
@@ -183,7 +195,9 @@ stdenvNoCC.mkDerivation (args // {
   # gappsWrapperArgs gets included when wrapping for dotnet, as to avoid double wrapping
   dontWrapGApps = args.dontWrapGApps or true;
 
-  inherit selfContainedBuild useAppHost useDotnetFromEnv;
+  # propagate the runtime sandbox profile since the contents apply to published
+  # executables
+  propagatedSandboxProfile = toString dotnet-runtime.__propagatedSandboxProfile;
 
   passthru = {
     inherit nuget-source;
@@ -265,11 +279,11 @@ stdenvNoCC.mkDerivation (args // {
                 --no-cache \
                 --force \
                 ${lib.optionalString (!enableParallelBuilding) "--disable-parallel"} \
-                ${lib.optionalString (flags != []) (toString flags)}
+                ${lib.escapeShellArgs flags}
         }
 
-        declare -a projectFiles=( ${toString (lib.toList projectFile)} )
-        declare -a testProjectFiles=( ${toString (lib.toList testProjectFile)} )
+        declare -a projectFiles=( ${lib.escapeShellArgs projectFiles} )
+        declare -a testProjectFiles=( ${lib.escapeShellArgs testProjectFiles} )
 
         export DOTNET_NOLOGO=1
         export DOTNET_CLI_TELEMETRY_OPTOUT=1
@@ -316,8 +330,4 @@ stdenvNoCC.mkDerivation (args // {
   } // args.passthru or { };
 
   meta = (args.meta or { }) // { inherit platforms; };
-}
-  # ICU tries to unconditionally load files from /usr/share/icu on Darwin, which makes builds fail
-  # in the sandbox, so disable ICU on Darwin. This, as far as I know, shouldn't cause any built packages
-  # to behave differently, just the dotnet build tool.
-  // lib.optionalAttrs stdenvNoCC.isDarwin { DOTNET_SYSTEM_GLOBALIZATION_INVARIANT = 1; })
+})
diff --git a/pkgs/build-support/dotnet/build-dotnet-module/hooks/default.nix b/pkgs/build-support/dotnet/build-dotnet-module/hooks/default.nix
index 7012ff36a4a55..b9c51a743c6a6 100644
--- a/pkgs/build-support/dotnet/build-dotnet-module/hooks/default.nix
+++ b/pkgs/build-support/dotnet/build-dotnet-module/hooks/default.nix
@@ -4,30 +4,21 @@
 , coreutils
 , zlib
 , openssl
-, callPackage
 , makeSetupHook
-, makeWrapper
+, dotnetCorePackages
+  # Passed from ../default.nix
 , dotnet-sdk
-, dotnet-test-sdk
-, disabledTests
-, nuget-source
 , dotnet-runtime
-, runtimeDeps
-, buildType
-, runtimeId
 }:
-assert (builtins.isString runtimeId);
-
 let
-  libraryPath = lib.makeLibraryPath runtimeDeps;
+  runtimeId = dotnetCorePackages.systemToDotnetRid stdenv.hostPlatform.system;
 in
 {
-  dotnetConfigureHook = callPackage ({ }:
-    makeSetupHook {
+  dotnetConfigureHook = makeSetupHook
+    {
       name = "dotnet-configure-hook";
-      propagatedBuildInputs = [ dotnet-sdk nuget-source ];
       substitutions = {
-        nugetSource = nuget-source;
+        runtimeId = lib.escapeShellArg runtimeId;
         dynamicLinker = "${stdenv.cc}/nix-support/dynamic-linker";
         libPath = lib.makeLibraryPath [
           stdenv.cc.cc.lib
@@ -36,54 +27,44 @@ in
           zlib
           openssl
         ];
-        inherit runtimeId;
       };
-    } ./dotnet-configure-hook.sh) { };
+    }
+    ./dotnet-configure-hook.sh;
 
-  dotnetBuildHook = callPackage ({ }:
-    makeSetupHook {
+  dotnetBuildHook = makeSetupHook
+    {
       name = "dotnet-build-hook";
-      propagatedBuildInputs = [ dotnet-sdk ];
       substitutions = {
-        inherit buildType runtimeId;
+        runtimeId = lib.escapeShellArg runtimeId;
       };
-    } ./dotnet-build-hook.sh) { };
+    }
+    ./dotnet-build-hook.sh;
 
-  dotnetCheckHook = callPackage ({ }:
-    makeSetupHook {
+  dotnetCheckHook = makeSetupHook
+    {
       name = "dotnet-check-hook";
-      propagatedBuildInputs = [ dotnet-test-sdk ];
       substitutions = {
-        inherit buildType runtimeId libraryPath;
-        disabledTests = lib.optionalString (disabledTests != [])
-          (let
-            escapedNames = lib.lists.map (n: lib.replaceStrings [","] ["%2C"] n) disabledTests;
-            filters = lib.lists.map (n: "FullyQualifiedName!=${n}") escapedNames;
-          in
-          "${lib.concatStringsSep "&" filters}");
+        runtimeId = lib.escapeShellArg runtimeId;
       };
-    } ./dotnet-check-hook.sh) { };
+    }
+    ./dotnet-check-hook.sh;
 
-  dotnetInstallHook = callPackage ({ }:
-    makeSetupHook {
+  dotnetInstallHook = makeSetupHook
+    {
       name = "dotnet-install-hook";
-      propagatedBuildInputs = [ dotnet-sdk ];
       substitutions = {
-        inherit buildType runtimeId;
+        runtimeId = lib.escapeShellArg runtimeId;
       };
-    } ./dotnet-install-hook.sh) { };
+    }
+    ./dotnet-install-hook.sh;
 
-  dotnetFixupHook = callPackage ({ }:
-    makeSetupHook {
+  dotnetFixupHook = makeSetupHook
+    {
       name = "dotnet-fixup-hook";
-      propagatedBuildInputs = [ dotnet-runtime ];
       substitutions = {
         dotnetRuntime = dotnet-runtime;
-        runtimeDeps = libraryPath;
-        shell = stdenv.shell;
-        which = "${which}/bin/which";
-        dirname = "${coreutils}/bin/dirname";
-        realpath = "${coreutils}/bin/realpath";
+        wrapperPath = lib.makeBinPath [ which coreutils ];
       };
-    } ./dotnet-fixup-hook.sh) { };
+    }
+    ./dotnet-fixup-hook.sh;
 }
diff --git a/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-build-hook.sh b/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-build-hook.sh
index 0acfeced9b169..f209861f79b15 100644
--- a/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-build-hook.sh
+++ b/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-build-hook.sh
@@ -1,12 +1,25 @@
-# inherit arguments from derivation
-dotnetBuildFlags=( ${dotnetBuildFlags[@]-} )
-
 dotnetBuildHook() {
     echo "Executing dotnetBuildHook"
 
     runHook preBuild
 
-    if [ "${enableParallelBuilding-}" ]; then
+    local -r hostRuntimeId=@runtimeId@
+    local -r dotnetBuildType="${dotnetBuildType-Release}"
+    local -r dotnetRuntimeId="${dotnetRuntimeId-$hostRuntimeId}"
+
+    if [[ -n $__structuredAttrs ]]; then
+        local dotnetProjectFilesArray=( "${dotnetProjectFiles[@]}" )
+        local dotnetTestProjectFilesArray=( "${dotnetTestProjectFiles[@]}" )
+        local dotnetFlagsArray=( "${dotnetFlags[@]}" )
+        local dotnetBuildFlagsArray=( "${dotnetBuildFlags[@]}" )
+    else
+        local dotnetProjectFilesArray=($dotnetProjectFiles)
+        local dotnetTestProjectFilesArray=($dotnetTestProjectFiles)
+        local dotnetFlagsArray=($dotnetFlags)
+        local dotnetBuildFlagsArray=($dotnetBuildFlags)
+    fi
+
+    if [[ -n "${enableParallelBuilding-}" ]]; then
         local -r maxCpuFlag="$NIX_BUILD_CORES"
         local -r parallelBuildFlag="true"
     else
@@ -14,50 +27,53 @@ dotnetBuildHook() {
         local -r parallelBuildFlag="false"
     fi
 
-    if [ "${selfContainedBuild-}" ]; then
-        dotnetBuildFlags+=("-p:SelfContained=true")
+    if [[ -n ${dotnetSelfContainedBuild-} ]]; then
+        dotnetBuildFlagsArray+=("-p:SelfContained=true")
     else
-        dotnetBuildFlags+=("-p:SelfContained=false")
+        dotnetBuildFlagsArray+=("-p:SelfContained=false")
     fi
 
-    if [ "${useAppHost-}" ]; then
-        dotnetBuildFlags+=("-p:UseAppHost=true")
+    if [[ -n ${dotnetUseAppHost-} ]]; then
+        dotnetBuildFlagsArray+=("-p:UseAppHost=true")
     fi
 
-    local versionFlags=()
-    if [ "${version-}" ]; then
-        versionFlags+=("-p:InformationalVersion=${version-}")
+    local versionFlagsArray=()
+    if [[ -n ${version-} ]]; then
+        versionFlagsArray+=("-p:InformationalVersion=$version")
     fi
 
-    if [ "${versionForDotnet-}" ]; then
-        versionFlags+=("-p:Version=${versionForDotnet-}")
+    if [[ -n ${versionForDotnet-} ]]; then
+        versionFlagsArray+=("-p:Version=$versionForDotnet")
     fi
 
     dotnetBuild() {
-        local -r project="${1-}"
+        local -r projectFile="${1-}"
 
-        runtimeIdFlags=()
-        if [[ "$project" == *.csproj ]] || [ "${selfContainedBuild-}" ]; then
-            runtimeIdFlags+=("--runtime @runtimeId@")
+        local runtimeIdFlagsArray=()
+        if [[ $projectFile == *.csproj || -n ${dotnetSelfContainedBuild-} ]]; then
+            runtimeIdFlagsArray+=("--runtime" "$dotnetRuntimeId")
         fi
 
-        env dotnet build ${project-} \
-            -maxcpucount:$maxCpuFlag \
-            -p:BuildInParallel=$parallelBuildFlag \
+        dotnet build ${1+"$projectFile"} \
+            -maxcpucount:"$maxCpuFlag" \
+            -p:BuildInParallel="$parallelBuildFlag" \
             -p:ContinuousIntegrationBuild=true \
             -p:Deterministic=true \
-            --configuration "@buildType@" \
+            --configuration "$dotnetBuildType" \
             --no-restore \
-            ${versionFlags[@]} \
-            ${runtimeIdFlags[@]} \
-            ${dotnetBuildFlags[@]}  \
-            ${dotnetFlags[@]}
+            "${versionFlagsArray[@]}" \
+            "${runtimeIdFlagsArray[@]}" \
+            "${dotnetBuildFlagsArray[@]}" \
+            "${dotnetFlagsArray[@]}"
     }
 
-    (( "${#projectFile[@]}" == 0 )) && dotnetBuild
+    if (( ${#dotnetProjectFilesArray[@]} == 0 )); then
+        dotnetBuild
+    fi
 
-    for project in ${projectFile[@]} ${testProjectFile[@]-}; do
-        dotnetBuild "$project"
+    local projectFile
+    for projectFile in "${dotnetProjectFilesArray[@]}" "${dotnetTestProjectFilesArray[@]}"; do
+        dotnetBuild "$projectFile"
     done
 
     runHook postBuild
@@ -65,6 +81,6 @@ dotnetBuildHook() {
     echo "Finished dotnetBuildHook"
 }
 
-if [[ -z "${dontDotnetBuild-}" && -z "${buildPhase-}" ]]; then
+if [[ -z ${dontDotnetBuild-} && -z ${buildPhase-} ]]; then
     buildPhase=dotnetBuildHook
 fi
diff --git a/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-check-hook.sh b/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-check-hook.sh
index 507721ef98181..c91251f4f1807 100644
--- a/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-check-hook.sh
+++ b/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-check-hook.sh
@@ -1,39 +1,65 @@
-# inherit arguments from derivation
-dotnetTestFlags=( ${dotnetTestFlags[@]-} )
-
 dotnetCheckHook() {
     echo "Executing dotnetCheckHook"
 
     runHook preCheck
 
-    if [ "${disabledTests-}" ]; then
-        local -r disabledTestsFlag="--filter @disabledTests@"
+    local -r hostRuntimeId=@runtimeId@
+    local -r dotnetBuildType="${dotnetBuildType-Release}"
+    local -r dotnetRuntimeId="${dotnetRuntimeId-$hostRuntimeId}"
+
+    if [[ -n $__structuredAttrs ]]; then
+        local dotnetProjectFilesArray=( "${dotnetProjectFiles[@]}" )
+        local dotnetTestProjectFilesArray=( "${dotnetTestProjectFiles[@]}" )
+        local dotnetTestFlagsArray=( "${dotnetTestFlags[@]}" )
+        local dotnetDisabledTestsArray=( "${dotnetDisabledTests[@]}" )
+        local dotnetRuntimeDepsArray=( "${dotnetRuntimeDeps[@]}" )
+    else
+        local dotnetProjectFilesArray=($dotnetProjectFiles)
+        local dotnetTestProjectFilesArray=($dotnetTestProjectFiles)
+        local dotnetTestFlagsArray=($dotnetTestFlags)
+        local dotnetDisabledTestsArray=($dotnetDisabledTests)
+        local dotnetRuntimeDepsArray=($dotnetRuntimeDeps)
+    fi
+
+    if (( ${#dotnetDisabledTestsArray[@]} > 0 )); then
+        local disabledTestsFilters=("${dotnetDisabledTestsArray[@]/#/FullyQualifiedName!=}")
+        local OLDIFS="$IFS" IFS='&'
+        dotnetTestFlagsArray+=("--filter:${disabledTestsFilters[*]//,/%2C}")
+        IFS="$OLDIFS"
+    fi
+
+    local libraryPath="${LD_LIBRARY_PATH-}"
+    if (( ${#dotnetRuntimeDepsArray[@]} > 0 )); then
+        local libraryPathArray=("${dotnetRuntimeDepsArray[@]/%//lib}")
+        local OLDIFS="$IFS" IFS=':'
+        libraryPath="${libraryPathArray[*]}${libraryPath:+':'}$libraryPath"
+        IFS="$OLDIFS"
     fi
 
-    if [ "${enableParallelBuilding-}" ]; then
+    if [[ -n ${enableParallelBuilding-} ]]; then
         local -r maxCpuFlag="$NIX_BUILD_CORES"
     else
         local -r maxCpuFlag="1"
     fi
 
-    for project in ${testProjectFile[@]-${projectFile[@]}}; do
-        runtimeIdFlags=()
-        if [[ "$project" == *.csproj ]]; then
-            runtimeIdFlags=("--runtime @runtimeId@")
+    local projectFile
+    for projectFile in "${dotnetTestProjectFilesArray[@]-${dotnetProjectFilesArray[@]}}"; do
+        local runtimeIdFlagsArray=()
+        if [[ $projectFile == *.csproj ]]; then
+            runtimeIdFlagsArray=("--runtime" "$dotnetRuntimeId")
         fi
 
-        env "LD_LIBRARY_PATH=@libraryPath@" \
-            dotnet test "$project" \
-              -maxcpucount:$maxCpuFlag \
+        LD_LIBRARY_PATH=$libraryPath \
+            dotnet test "$projectFile" \
+              -maxcpucount:"$maxCpuFlag" \
               -p:ContinuousIntegrationBuild=true \
               -p:Deterministic=true \
-              --configuration "@buildType@" \
+              --configuration "$dotnetBuildType" \
               --no-build \
               --logger "console;verbosity=normal" \
-              ${disabledTestsFlag-} \
-              ${runtimeIdFlags[@]} \
-              "${dotnetTestFlags[@]}"  \
-              "${dotnetFlags[@]}"
+              "${runtimeIdFlagsArray[@]}" \
+              "${dotnetTestFlagsArray[@]}" \
+              "${dotnetFlagsArray[@]}"
     done
 
     runHook postCheck
diff --git a/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-configure-hook.sh b/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-configure-hook.sh
index c046fc3c306b1..12fa348699865 100644
--- a/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-configure-hook.sh
+++ b/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-configure-hook.sh
@@ -1,63 +1,103 @@
-declare -a projectFile testProjectFile
-
-# Inherit arguments from derivation
-dotnetFlags=( ${dotnetFlags[@]-} )
-dotnetRestoreFlags=( ${dotnetRestoreFlags[@]-} )
-
 dotnetConfigureHook() {
     echo "Executing dotnetConfigureHook"
 
     runHook preConfigure
 
-    if [ -z "${enableParallelBuilding-}" ]; then
+    if [[ -z ${nugetSource-} ]]; then
+        echo
+        echo "ERROR: no dependencies were specified"
+        echo 'Hint: set `nugetSource` if using these hooks individually. If this is happening with `buildDotnetModule`, please open an issue.'
+        echo
+
+        exit 1
+    fi
+
+    local nugetSourceSedQuoted="${nugetSource//[\/\\&$'\n']/\\&}"
+    local nugetSourceXMLQuoted="$nugetSource"
+    nugetSourceXMLQuoted="${nugetSource//&/\&amp;}"
+    nugetSourceXMLQuoted="${nugetSourceXMLQuoted//\"/\&quot;}"
+
+    local -r hostRuntimeId=@runtimeId@
+    local -r dynamicLinker=@dynamicLinker@
+    local -r libPath=@libPath@
+    local -r dotnetRuntimeId="${dotnetRuntimeId-$hostRuntimeId}"
+
+    if [[ -n $__structuredAttrs ]]; then
+        local dotnetProjectFilesArray=( "${dotnetProjectFiles[@]}" )
+        local dotnetTestProjectFilesArray=( "${dotnetTestProjectFiles[@]}" )
+        local dotnetFlagsArray=( "${dotnetFlags[@]}" )
+        local dotnetRestoreFlagsArray=( "${dotnetRestoreFlags[@]}" )
+    else
+        local dotnetProjectFilesArray=($dotnetProjectFiles)
+        local dotnetTestProjectFilesArray=($dotnetTestProjectFiles)
+        local dotnetFlagsArray=($dotnetFlags)
+        local dotnetRestoreFlagsArray=($dotnetRestoreFlags)
+    fi
+
+    if [[ -z ${enableParallelBuilding-} ]]; then
         local -r parallelFlag="--disable-parallel"
     fi
 
     dotnetRestore() {
-        local -r project="${1-}"
-        env dotnet restore ${project-} \
+        local -r projectFile="${1-}"
+        dotnet restore ${1+"$projectFile"} \
             -p:ContinuousIntegrationBuild=true \
             -p:Deterministic=true \
-            --runtime "@runtimeId@" \
-            --source "@nugetSource@/lib" \
+            --runtime "$dotnetRuntimeId" \
+            --source "$nugetSource/lib" \
             ${parallelFlag-} \
-            ${dotnetRestoreFlags[@]} \
-            ${dotnetFlags[@]}
+            "${dotnetRestoreFlagsArray[@]}" \
+            "${dotnetFlagsArray[@]}"
     }
 
     # Generate a NuGet.config file to make sure everything,
     # including things like <Sdk /> dependencies, is restored from the proper source
-cat <<EOF > "./NuGet.config"
+    cat >NuGet.config <<EOF
 <?xml version="1.0" encoding="utf-8"?>
 <configuration>
   <packageSources>
     <clear />
-    <add key="nugetSource" value="@nugetSource@/lib" />
+    <add key="nugetSource" value="$nugetSourceXMLQuoted/lib" />
   </packageSources>
 </configuration>
 EOF
 
-    # Patch paket.dependencies and paket.lock (if found) to use the proper source. This ensures
-    # paket restore works correctly
-    # We use + instead of / in sed to avoid problems with slashes
-    find -name paket.dependencies -exec sed -i 's+source .*+source @nugetSource@/lib+g' {} \;
-    find -name paket.lock -exec sed -i 's+remote:.*+remote: @nugetSource@/lib+g' {} \;
-
-    env dotnet tool restore --add-source "@nugetSource@/lib"
-
-    (( "${#projectFile[@]}" == 0 )) && dotnetRestore
+    # Patch paket.dependencies and paket.lock (if found) to use the proper
+    # source. This ensures paket restore works correctly. Note that the
+    # nugetSourceSedQuoted abomination below safely escapes nugetSource string
+    # for use as a sed replacement string to avoid issues with slashes and other
+    # special characters ('&', '\\' and '\n').
+    find -name paket.dependencies -exec sed -i "s/source .*/source $nugetSourceSedQuoted\/lib/g" {} \;
+    find -name paket.lock -exec sed -i "s/remote:.*/remote: $nugetSourceSedQuoted\/lib/g" {} \;
+
+    dotnet tool restore --add-source "$nugetSource/lib"
+
+    # dotnetGlobalTool is set in buildDotnetGlobalTool to patch dependencies but
+    # avoid other project-specific logic. This is a hack, but the old behavior
+    # is worse as it relied on a bug: setting projectFile to an empty string
+    # made the hooks actually skip all project-specific logic. It’s hard to keep
+    # backwards compatibility with this odd behavior now since we are using
+    # arrays, so instead we just pass a variable to indicate that we don’t have
+    # projects.
+    if [[ -z ${dotnetGlobalTool-} ]]; then
+        if (( ${#dotnetProjectFilesArray[@]} == 0 )); then
+            dotnetRestore
+        fi
 
-    for project in ${projectFile[@]} ${testProjectFile[@]-}; do
-        dotnetRestore "$project"
-    done
+        local projectFile
+        for projectFile in "${dotnetProjectFilesArray[@]}" "${dotnetTestProjectFilesArray[@]}"; do
+            dotnetRestore "$projectFile"
+        done
+    fi
 
     echo "Fixing up native binaries..."
     # Find all native binaries and nuget libraries, and fix them up,
     # by setting the proper interpreter and rpath to some commonly used libraries
+    local binary
     for binary in $(find "$HOME/.nuget/packages/" -type f -executable); do
         if patchelf --print-interpreter "$binary" >/dev/null 2>/dev/null; then
             echo "Found binary: $binary, fixing it up..."
-            patchelf --set-interpreter "$(cat "@dynamicLinker@")" "$binary"
+            patchelf --set-interpreter "$(cat "$dynamicLinker")" "$binary"
 
             # This makes sure that if the binary requires some specific runtime dependencies, it can find it.
             # This fixes dotnet-built binaries like crossgen2
@@ -68,7 +108,7 @@ EOF
                 --add-needed libssl.so \
                 "$binary"
 
-            patchelf --set-rpath "@libPath@" "$binary"
+            patchelf --set-rpath "$libPath" "$binary"
         fi
     done
 
diff --git a/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-fixup-hook.sh b/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-fixup-hook.sh
index e3671728af35e..a9701f2898b27 100644
--- a/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-fixup-hook.sh
+++ b/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-fixup-hook.sh
@@ -1,26 +1,60 @@
-# Inherit arguments from the derivation
-declare -a derivationMakeWrapperArgs="( ${makeWrapperArgs-} )"
-makeWrapperArgs=( "${derivationMakeWrapperArgs[@]}" )
+# For compatibility, convert makeWrapperArgs to an array unless we are using
+# structured attributes. That is, we ensure that makeWrapperArgs is always an
+# array.
+# See https://github.com/NixOS/nixpkgs/blob/858f4db3048c5be3527e183470e93c1a72c5727c/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-fixup-hook.sh#L1-L3
+# and https://github.com/NixOS/nixpkgs/pull/313005#issuecomment-2175482920
+if [[ -z $__structuredAttrs ]]; then
+    makeWrapperArgs=( ${makeWrapperArgs-} )
+fi
 
 # First argument is the executable you want to wrap,
 # the second is the destination for the wrapper.
 wrapDotnetProgram() {
-    local dotnetRootFlags=()
+    local -r dotnetRuntime=@dotnetRuntime@
+    local -r wrapperPath=@wrapperPath@
+
+    local -r dotnetFromEnvScript='dotnetFromEnv() {
+    local dotnetPath
+    if command -v dotnet 2>&1 >/dev/null; then
+        dotnetPath=$(which dotnet) && \
+            dotnetPath=$(realpath "$dotnetPath") && \
+            dotnetPath=$(dirname "$dotnetPath") && \
+            export DOTNET_ROOT="$dotnetPath"
+    fi
+}
+dotnetFromEnv'
+
+    if [[ -n $__structuredAttrs ]]; then
+        local -r dotnetRuntimeDepsArray=( "${dotnetRuntimeDeps[@]}" )
+    else
+        local -r dotnetRuntimeDepsArray=($dotnetRuntimeDeps)
+    fi
 
-    if [ ! "${selfContainedBuild-}" ]; then
-        if [ "${useDotnetFromEnv-}" ]; then
+    local dotnetRuntimeDepsFlags=()
+    if (( ${#dotnetRuntimeDepsArray[@]} > 0 )); then
+        local libraryPathArray=("${dotnetRuntimeDepsArray[@]/%//lib}")
+        local OLDIFS="$IFS" IFS=':'
+        dotnetRuntimeDepsFlags+=("--suffix" "LD_LIBRARY_PATH" ":" "${libraryPathArray[*]}")
+        IFS="$OLDIFS"
+    fi
+
+    local dotnetRootFlagsArray=()
+    if [[ -z ${dotnetSelfContainedBuild-} ]]; then
+        if [[ -n ${useDotnetFromEnv-} ]]; then
             # if dotnet CLI is available, set DOTNET_ROOT based on it. Otherwise set to default .NET runtime
-            dotnetRootFlags+=("--run" 'command -v dotnet &>/dev/null && export DOTNET_ROOT="$(@dirname@ "$(@realpath@ "$(@which@ dotnet)")")" || export DOTNET_ROOT="@dotnetRuntime@"')
-            dotnetRootFlags+=("--suffix" "PATH" ":" "@dotnetRuntime@/bin")
+            dotnetRootFlagsArray+=("--suffix" "PATH" ":" "$wrapperPath")
+            dotnetRootFlagsArray+=("--run" "$dotnetFromEnvScript")
+            dotnetRootFlagsArray+=("--set-default" "DOTNET_ROOT" "$dotnetRuntime")
+            dotnetRootFlagsArray+=("--suffix" "PATH" ":" "$dotnetRuntime/bin")
         else
-            dotnetRootFlags+=("--set" "DOTNET_ROOT" "@dotnetRuntime@")
-            dotnetRootFlags+=("--prefix" "PATH" ":" "@dotnetRuntime@/bin")
+            dotnetRootFlagsArray+=("--set" "DOTNET_ROOT" "$dotnetRuntime")
+            dotnetRootFlagsArray+=("--prefix" "PATH" ":" "$dotnetRuntime/bin")
         fi
     fi
 
     makeWrapper "$1" "$2" \
-        --suffix "LD_LIBRARY_PATH" : "@runtimeDeps@" \
-        "${dotnetRootFlags[@]}" \
+        "${dotnetRuntimeDepsFlags[@]}" \
+        "${dotnetRootFlagsArray[@]}" \
         "${gappsWrapperArgs[@]}" \
         "${makeWrapperArgs[@]}"
 
@@ -30,13 +64,24 @@ wrapDotnetProgram() {
 dotnetFixupHook() {
     echo "Executing dotnetFixupPhase"
 
-    # check if executables is declared (including empty values, in which case we generate no executables)
-    if declare -p executables &>/dev/null; then
-        for executable in ${executables[@]}; do
-            path="${installPath-$out/lib/$pname}/$executable"
+    local -r dotnetInstallPath="${dotnetInstallPath-$out/lib/$pname}"
+
+    local executable executableBasename
+
+    # check if dotnetExecutables is declared (including empty values, in which case we generate no executables)
+    if declare -p dotnetExecutables &>/dev/null; then
+        if [[ -n $__structuredAttrs ]]; then
+            local dotnetExecutablesArray=( "${dotnetExecutables[@]}" )
+        else
+            local dotnetExecutablesArray=($dotnetExecutables)
+        fi
+        for executable in "${dotnetExecutablesArray[@]}"; do
+            executableBasename=$(basename "$executable")
+
+            local path="$dotnetInstallPath/$executable"
 
             if test -x "$path"; then
-                wrapDotnetProgram "$path" "$out/bin/$(basename "$executable")"
+                wrapDotnetProgram "$path" "$out/bin/$executableBasename"
             else
                 echo "Specified binary \"$executable\" is either not an executable or does not exist!"
                 echo "Looked in $path"
@@ -45,8 +90,9 @@ dotnetFixupHook() {
         done
     else
         while IFS= read -d '' executable; do
-            wrapDotnetProgram "$executable" "$out/bin/$(basename "$executable")" \;
-        done < <(find "${installPath-$out/lib/$pname}" ! -name "*.dll" -executable -type f -print0)
+            executableBasename=$(basename "$executable")
+            wrapDotnetProgram "$executable" "$out/bin/$executableBasename" \;
+        done < <(find "$dotnetInstallPath" ! -name "*.dll" -executable -type f -print0)
     fi
 
     echo "Finished dotnetFixupPhase"
diff --git a/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-install-hook.sh b/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-install-hook.sh
index d832eac288096..4d9b3c502c354 100644
--- a/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-install-hook.sh
+++ b/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-install-hook.sh
@@ -1,70 +1,86 @@
-# inherit arguments from derivation
-dotnetInstallFlags=( ${dotnetInstallFlags[@]-} )
-
 dotnetInstallHook() {
     echo "Executing dotnetInstallHook"
 
     runHook preInstall
 
-    if [ "${selfContainedBuild-}" ]; then
-        dotnetInstallFlags+=("--self-contained")
+    local -r hostRuntimeId=@runtimeId@
+    local -r dotnetInstallPath="${dotnetInstallPath-$out/lib/$pname}"
+    local -r dotnetBuildType="${dotnetBuildType-Release}"
+    local -r dotnetRuntimeId="${dotnetRuntimeId-$hostRuntimeId}"
+
+    if [[ -n $__structuredAttrs ]]; then
+        local dotnetProjectFilesArray=( "${dotnetProjectFiles[@]}" )
+        local dotnetFlagsArray=( "${dotnetFlags[@]}" )
+        local dotnetInstallFlagsArray=( "${dotnetInstallFlags[@]}" )
+        local dotnetPackFlagsArray=( "${dotnetPackFlags[@]}" )
+    else
+        local dotnetProjectFilesArray=($dotnetProjectFiles)
+        local dotnetFlagsArray=($dotnetFlags)
+        local dotnetInstallFlagsArray=($dotnetInstallFlags)
+        local dotnetPackFlagsArray=($dotnetPackFlags)
+    fi
+
+    if [[ -n ${dotnetSelfContainedBuild-} ]]; then
+        dotnetInstallFlagsArray+=("--self-contained")
     else
-        dotnetInstallFlags+=("--no-self-contained")
+        dotnetInstallFlagsArray+=("--no-self-contained")
         # https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-self-contained
         # Trimming is only available for self-contained build, so force disable it here
-        dotnetInstallFlags+=("-p:PublishTrimmed=false")
+        dotnetInstallFlagsArray+=("-p:PublishTrimmed=false")
     fi
 
-    if [ "${useAppHost-}" ]; then
-        dotnetInstallFlags+=("-p:UseAppHost=true")
+    if [[ -n ${dotnetUseAppHost-} ]]; then
+        dotnetInstallFlagsArray+=("-p:UseAppHost=true")
     fi
 
     dotnetPublish() {
-        local -r project="${1-}"
+        local -r projectFile="${1-}"
 
-        runtimeIdFlags=()
-        if [[ "$project" == *.csproj ]] || [ "${selfContainedBuild-}" ]; then
-            runtimeIdFlags+=("--runtime @runtimeId@")
+        runtimeIdFlagsArray=()
+        if [[ $projectFile == *.csproj || -n ${dotnetSelfContainedBuild-} ]]; then
+            runtimeIdFlagsArray+=("--runtime" "$dotnetRuntimeId")
         fi
 
-        env dotnet publish ${project-} \
+        dotnet publish ${1+"$projectFile"} \
             -p:ContinuousIntegrationBuild=true \
             -p:Deterministic=true \
-            --output "${installPath-$out/lib/$pname}" \
-            --configuration "@buildType@" \
+            --output "$dotnetInstallPath" \
+            --configuration "$dotnetBuildType" \
             --no-build \
-            ${runtimeIdFlags[@]} \
-            ${dotnetInstallFlags[@]}  \
-            ${dotnetFlags[@]}
+            "${runtimeIdFlagsArray[@]}" \
+            "${dotnetInstallFlagsArray[@]}" \
+            "${dotnetFlagsArray[@]}"
     }
 
     dotnetPack() {
-        local -r project="${1-}"
-        env dotnet pack ${project-} \
+        local -r projectFile="${1-}"
+        dotnet pack ${1+"$projectFile"} \
             -p:ContinuousIntegrationBuild=true \
             -p:Deterministic=true \
             --output "$out/share" \
-            --configuration "@buildType@" \
+            --configuration "$dotnetBuildType" \
             --no-build \
-            --runtime "@runtimeId@" \
-            ${dotnetPackFlags[@]}  \
-            ${dotnetFlags[@]}
+            --runtime "$dotnetRuntimeId" \
+            "${dotnetPackFlagsArray[@]}" \
+            "${dotnetFlagsArray[@]}"
     }
 
-    if (( "${#projectFile[@]}" == 0 )); then
+    if (( ${#dotnetProjectFilesArray[@]} == 0 )); then
         dotnetPublish
     else
-        for project in ${projectFile[@]}; do
-            dotnetPublish "$project"
+        local projectFile
+        for projectFile in "${dotnetProjectFilesArray[@]}"; do
+            dotnetPublish "$projectFile"
         done
     fi
 
-    if [[ "${packNupkg-}" ]]; then
-        if (( "${#projectFile[@]}" == 0 )); then
+    if [[ -n ${packNupkg-} ]]; then
+        if (( ${#dotnetProjectFilesArray[@]} == 0 )); then
             dotnetPack
         else
-            for project in ${projectFile[@]}; do
-                dotnetPack "$project"
+            local projectFile
+            for projectFile in "${dotnetProjectFilesArray[@]}"; do
+                dotnetPack "$projectFile"
             done
         fi
     fi
diff --git a/pkgs/build-support/emacs/melpa.nix b/pkgs/build-support/emacs/melpa.nix
index 323c6e65d9d90..c8f6567049180 100644
--- a/pkgs/build-support/emacs/melpa.nix
+++ b/pkgs/build-support/emacs/melpa.nix
@@ -35,25 +35,41 @@ in
   pname
   /*
     ename: Original Emacs package name, possibly containing special symbols.
+    Default: pname
   */
-, ename ? null
+, ename ? pname
 , version
-, recipe
+  /*
+    commit: Optional package history commit.
+    Default: src.rev or "unknown"
+    This will be written into the generated package but it is not needed during
+    the build process.
+  */
+, commit ? (args.src.rev or "unknown")
+  /*
+    files: Optional recipe property specifying the files used to build the package.
+    If null, do not set it in recipe, keeping the default upstream behaviour.
+    Default: null
+  */
+, files ? null
+  /*
+    recipe: Optional MELPA recipe.
+    Default: a minimally functional recipe
+  */
+, recipe ? (writeText "${pname}-recipe" ''
+    (${ename} :fetcher git :url ""
+              ${lib.optionalString (files != null) ":files ${files}"})
+  '')
 , meta ? {}
 , ...
 }@args:
 
 genericBuild ({
 
-  ename =
-    if ename == null
-    then pname
-    else ename;
-
   elpa2nix = ./elpa2nix.el;
   melpa2nix = ./melpa2nix.el;
 
-  inherit packageBuild;
+  inherit packageBuild commit ename recipe;
 
   preUnpack = ''
     mkdir -p "$NIX_BUILD_TOP/recipes"
diff --git a/pkgs/build-support/fetchgit/nix-prefetch-git b/pkgs/build-support/fetchgit/nix-prefetch-git
index 7ac3dec91f7f4..0f41cbd6a265d 100755
--- a/pkgs/build-support/fetchgit/nix-prefetch-git
+++ b/pkgs/build-support/fetchgit/nix-prefetch-git
@@ -166,7 +166,7 @@ checkout_hash(){
     clean_git fetch -t ${builder:+--progress} origin || return 1
 
     local object_type=$(git cat-file -t "$hash")
-    if [[ "$object_type" == "commit" ]]; then
+    if [[ "$object_type" == "commit" || "$object_type" == "tag" ]]; then
         clean_git checkout -b "$branchName" "$hash" || return 1
     elif [[ "$object_type" == "tree" ]]; then
         clean_git config user.email "nix-prefetch-git@localhost"
diff --git a/pkgs/build-support/fetchgit/tests.nix b/pkgs/build-support/fetchgit/tests.nix
index 23e5fa299010d..f3fcd9a578de4 100644
--- a/pkgs/build-support/fetchgit/tests.nix
+++ b/pkgs/build-support/fetchgit/tests.nix
@@ -72,4 +72,11 @@
     leaveDotGit = true;
     fetchSubmodules = true;
   };
+
+  dumb-http-signed-tag = testers.invalidateFetcherByDrvHash fetchgit {
+    name = "dumb-http-signed-tag-source";
+    url = "https://git.scottworley.com/pub/git/pinch";
+    rev = "v3.0.14";
+    sha256 = "sha256-bd0Lx75Gd1pcBJtwz5WGki7XoYSpqhinCT3a77wpY2c=";
+  };
 }
diff --git a/pkgs/build-support/fetchtorrent/default.nix b/pkgs/build-support/fetchtorrent/default.nix
index 126748678bf25..041c1b9dcb394 100644
--- a/pkgs/build-support/fetchtorrent/default.nix
+++ b/pkgs/build-support/fetchtorrent/default.nix
@@ -1,4 +1,4 @@
-{ lib, runCommand, transmission_noSystemd, rqbit, writeShellScript, formats, cacert, rsync }:
+{ lib, runCommand, transmission_3_noSystemd, rqbit, writeShellScript, formats, cacert, rsync }:
 let
   urlRegexp = ''.*xt=urn:bt[im]h:([^&]{64}|[^&]{40}).*'';
 in
@@ -32,7 +32,7 @@ let
 in
 runCommand name {
   inherit meta;
-  nativeBuildInputs = [ cacert ] ++ (if (backend == "transmission" ) then [ transmission_noSystemd ] else if (backend == "rqbit") then [ rqbit ] else throw "rqbit or transmission are the only available backends for fetchtorrent");
+  nativeBuildInputs = [ cacert ] ++ (if (backend == "transmission" ) then [ transmission_3_noSystemd ] else if (backend == "rqbit") then [ rqbit ] else throw "rqbit or transmission are the only available backends for fetchtorrent");
   outputHashAlgo = if hash != "" then null else "sha256";
   outputHash = hash;
   outputHashMode = if recursiveHash then "recursive" else "flat";
diff --git a/pkgs/build-support/flutter/default.nix b/pkgs/build-support/flutter/default.nix
index 5d7cd7d984c17..ff6c6b31006f7 100644
--- a/pkgs/build-support/flutter/default.nix
+++ b/pkgs/build-support/flutter/default.nix
@@ -2,7 +2,7 @@
 , callPackage
 , runCommand
 , makeWrapper
-, wrapGAppsHook
+, wrapGAppsHook3
 , buildDartApplication
 , cacert
 , glib
@@ -10,21 +10,32 @@
 , pkg-config
 , jq
 , yq
-, moreutils
 }:
 
 # absolutely no mac support for now
 
-{ pubGetScript ? "flutter pub get"
+{ pubGetScript ? null
 , flutterBuildFlags ? [ ]
 , targetFlutterPlatform ? "linux"
 , extraWrapProgramArgs ? ""
+, flutterMode ? null
 , ...
 }@args:
 
 let
+  hasEngine = flutter ? engine && flutter.engine != null && flutter.engine.meta.available;
+  flutterMode = args.flutterMode or (if hasEngine then flutter.engine.runtimeMode else "release");
+
+  flutterFlags = lib.optional hasEngine "--local-engine host_${flutterMode}${lib.optionalString (!flutter.engine.isOptimized) "_unopt"}";
+
+  flutterBuildFlags = [
+    "--${flutterMode}"
+  ] ++ (args.flutterBuildFlags or []) ++ flutterFlags;
+
   builderArgs = rec {
     universal = args // {
+      inherit flutterMode flutterFlags flutterBuildFlags;
+
       sdkSetupScript = ''
         # Pub needs SSL certificates. Dart normally looks in a hardcoded path.
         # https://github.com/dart-lang/sdk/blob/3.1.0/runtime/bin/security_context_linux.cc#L48
@@ -47,11 +58,11 @@ let
         ''}/bin/dart"
 
         export HOME="$NIX_BUILD_TOP"
-        flutter config --no-analytics &>/dev/null # mute first-run
-        flutter config --enable-linux-desktop >/dev/null
+        flutter config $flutterFlags --no-analytics &>/dev/null # mute first-run
+        flutter config $flutterFlags --enable-linux-desktop >/dev/null
       '';
 
-      inherit pubGetScript;
+      pubGetScript = args.pubGetScript or "flutter${lib.optionalString hasEngine " --local-engine $flutterMode"} pub get";
 
       sdkSourceBuilders = {
         # https://github.com/dart-lang/pub/blob/68dc2f547d0a264955c1fa551fa0a0e158046494/lib/src/sdk/flutter.dart#L81
@@ -68,16 +79,34 @@ let
             exit 1
           fi
         '';
+        # https://github.com/dart-lang/pub/blob/e1fbda73d1ac597474b82882ee0bf6ecea5df108/lib/src/sdk/dart.dart#L80
+        "dart" = name: runCommand "dart-sdk-${name}" { passthru.packageRoot = "."; } ''
+          for path in '${flutter.dart}/pkg/${name}'; do
+            if [ -d "$path" ]; then
+              ln -s "$path" "$out"
+              break
+            fi
+          done
+
+          if [ ! -e "$out" ]; then
+            echo 1>&2 'The Dart SDK does not contain the requested package: ${name}!'
+            exit 1
+          fi
+        '';
       };
 
       extraPackageConfigSetup = ''
         # https://github.com/flutter/flutter/blob/3.13.8/packages/flutter_tools/lib/src/dart/pub.dart#L755
         if [ "$('${yq}/bin/yq' '.flutter.generate // false' pubspec.yaml)" = "true" ]; then
+          export TEMP_PACKAGES=$(mktemp)
           '${jq}/bin/jq' '.packages |= . + [{
             name: "flutter_gen",
             rootUri: "flutter_gen",
             languageVersion: "2.12",
-          }]' "$out" | '${moreutils}/bin/sponge' "$out"
+          }]' "$out" > "$TEMP_PACKAGES"
+          cp "$TEMP_PACKAGES" "$out"
+          rm "$TEMP_PACKAGES"
+          unset TEMP_PACKAGES
         fi
       '';
     };
@@ -86,7 +115,7 @@ let
       outputs = universal.outputs or [ ] ++ [ "debug" ];
 
       nativeBuildInputs = (universal.nativeBuildInputs or [ ]) ++ [
-        wrapGAppsHook
+        wrapGAppsHook3
 
         # Flutter requires pkg-config for Linux desktop support, and many plugins
         # attempt to use it.
@@ -105,7 +134,7 @@ let
 
         mkdir -p build/flutter_assets/fonts
 
-        flutter build linux -v --release --split-debug-info="$debug" ${builtins.concatStringsSep " " (map (flag: "\"${flag}\"") flutterBuildFlags)}
+        flutter build linux -v --split-debug-info="$debug" $flutterBuildFlags
 
         runHook postBuild
       '';
@@ -114,7 +143,7 @@ let
       installPhase = universal.installPhase or ''
         runHook preInstall
 
-        built=build/linux/*/release/bundle
+        built=build/linux/*/$flutterMode/bundle
 
         mkdir -p $out/bin
         mv $built $out/app
@@ -156,7 +185,7 @@ let
 
         mkdir -p build/flutter_assets/fonts
 
-        flutter build web -v --release ${builtins.concatStringsSep " " (map (flag: "\"${flag}\"") flutterBuildFlags)}
+        flutter build web -v $flutterBuildFlags
 
         runHook postBuild
       '';
diff --git a/pkgs/build-support/go/module.nix b/pkgs/build-support/go/module.nix
index 6f568c0eb4f95..bc28fbf6fc420 100644
--- a/pkgs/build-support/go/module.nix
+++ b/pkgs/build-support/go/module.nix
@@ -163,8 +163,10 @@ let
     inherit (go) GOOS GOARCH;
 
     GOFLAGS = GOFLAGS
-      ++ lib.optional (!proxyVendor) "-mod=vendor"
-      ++ lib.optional (!allowGoReference) "-trimpath";
+      ++ lib.warnIf (lib.any (lib.hasPrefix "-mod=") GOFLAGS) "use `proxyVendor` to control Go module/vendor behavior instead of setting `-mod=` in GOFLAGS"
+        (lib.optional (!proxyVendor) "-mod=vendor")
+      ++ lib.warnIf (builtins.elem "-trimpath" GOFLAGS) "`-trimpath` is added by default to GOFLAGS by buildGoModule when allowGoReference isn't set to true"
+        (lib.optional (!allowGoReference) "-trimpath");
     inherit CGO_ENABLED enableParallelBuilding GO111MODULE GOTOOLCHAIN;
 
     # If not set to an explicit value, set the buildid empty for reproducibility.
@@ -196,7 +198,12 @@ let
       runHook postConfigure
     '');
 
-    buildPhase = args.buildPhase or (''
+    buildPhase = args.buildPhase or (
+      lib.warnIf (buildFlags != "" || buildFlagsArray != "")
+        "`buildFlags`/`buildFlagsArray` are deprecated and will be removed in the 24.11 release. Use the `ldflags` and/or `tags` attributes instead of `buildFlags`/`buildFlagsArray`"
+      lib.warnIf (builtins.elem "-buildid=" ldflags)
+        "`-buildid=` is set by default as ldflag by buildGoModule"
+    ''
       runHook preBuild
 
       exclude='\(/_\|examples\|Godeps\|testdata'
@@ -313,9 +320,4 @@ let
     } // meta;
   });
 in
-lib.warnIf (buildFlags != "" || buildFlagsArray != "")
-  "Use the `ldflags` and/or `tags` attributes instead of `buildFlags`/`buildFlagsArray`"
-lib.warnIf (builtins.elem "-buildid=" ldflags) "`-buildid=` is set by default as ldflag by buildGoModule"
-lib.warnIf (builtins.elem "-trimpath" GOFLAGS) "`-trimpath` is added by default to GOFLAGS by buildGoModule when allowGoReference isn't set to true"
-lib.warnIf (lib.any (lib.hasPrefix "-mod=") GOFLAGS) "use `proxyVendor` to control Go module/vendor behavior instead of setting `-mod=` in GOFLAGS"
-  package
+package
diff --git a/pkgs/build-support/go/package.nix b/pkgs/build-support/go/package.nix
index 94a459c267f36..58a242a2535c2 100644
--- a/pkgs/build-support/go/package.nix
+++ b/pkgs/build-support/go/package.nix
@@ -286,7 +286,7 @@ let
   });
 in
 lib.warnIf (buildFlags != "" || buildFlagsArray != "")
-  "Use the `ldflags` and/or `tags` attributes instead of `buildFlags`/`buildFlagsArray`"
+  "`buildFlags`/`buildFlagsArray` are deprecated and will be removed in the 24.11 release. Use the `ldflags` and/or `tags` attributes instead"
 lib.warnIf (builtins.elem "-buildid=" ldflags) "`-buildid=` is set by default as ldflag by buildGoModule"
 lib.warnIf (builtins.elem "-trimpath" GOFLAGS) "`-trimpath` is added by default to GOFLAGS by buildGoModule when allowGoReference isn't set to true"
   package
diff --git a/pkgs/build-support/kernel/compress-firmware-xz.nix b/pkgs/build-support/kernel/compress-firmware-xz.nix
deleted file mode 100644
index cb9ce7a713389..0000000000000
--- a/pkgs/build-support/kernel/compress-firmware-xz.nix
+++ /dev/null
@@ -1,29 +0,0 @@
-{ runCommand, lib }:
-
-firmware:
-
-let
-  args = {
-    allowedRequisites = [];
-  } // lib.optionalAttrs (firmware ? meta) { inherit (firmware) meta; };
-in
-
-runCommand "${firmware.name}-xz" args ''
-  mkdir -p $out/lib
-  (cd ${firmware} && find lib/firmware -type d -print0) |
-      (cd $out && xargs -0 mkdir -v --)
-  (cd ${firmware} && find lib/firmware -type f -print0) |
-      (cd $out && xargs -0rtP "$NIX_BUILD_CORES" -n1 \
-          sh -c 'xz -9c -T1 -C crc32 --lzma2=dict=2MiB "${firmware}/$1" > "$1.xz"' --)
-  (cd ${firmware} && find lib/firmware -type l) | while read link; do
-      target="$(readlink "${firmware}/$link")"
-      if [ -f "${firmware}/$link" ]; then
-        ln -vs -- "''${target/^${firmware}/$out}.xz" "$out/$link.xz"
-      else
-        ln -vs -- "''${target/^${firmware}/$out}" "$out/$link"
-      fi
-  done
-
-  echo "Checking for broken symlinks:"
-  find -L $out -type l -print -execdir false -- '{}' '+'
-''
diff --git a/pkgs/build-support/kernel/compress-firmware.nix b/pkgs/build-support/kernel/compress-firmware.nix
new file mode 100644
index 0000000000000..0949036d5127f
--- /dev/null
+++ b/pkgs/build-support/kernel/compress-firmware.nix
@@ -0,0 +1,43 @@
+{ runCommand, lib, type ? "zstd", zstd }:
+
+firmware:
+
+let
+  compressor = {
+    xz = {
+      ext = "xz";
+      nativeBuildInputs = [ ];
+      cmd = file: target: ''xz -9c -T1 -C crc32 --lzma2=dict=2MiB "${file}" > "${target}"'';
+    };
+    zstd = {
+      ext = "zst";
+      nativeBuildInputs = [ zstd ];
+      cmd = file: target: ''zstd -T1 -19 --long --check -f "${file}" -o "${target}"'';
+    };
+  }.${type} or (throw "Unsupported compressor type for firmware.");
+
+  args = {
+    allowedRequisites = [];
+    inherit (compressor) nativeBuildInputs;
+  } // lib.optionalAttrs (firmware ? meta) { inherit (firmware) meta; };
+in
+
+runCommand "${firmware.name}-${type}" args ''
+  mkdir -p $out/lib
+  (cd ${firmware} && find lib/firmware -type d -print0) |
+      (cd $out && xargs -0 mkdir -v --)
+  (cd ${firmware} && find lib/firmware -type f -print0) |
+      (cd $out && xargs -0rtP "$NIX_BUILD_CORES" -n1 \
+          sh -c '${compressor.cmd "${firmware}/$1" "$1.${compressor.ext}"}' --)
+  (cd ${firmware} && find lib/firmware -type l) | while read link; do
+      target="$(readlink "${firmware}/$link")"
+      if [ -f "${firmware}/$link" ]; then
+        ln -vs -- "''${target/^${firmware}/$out}.${compressor.ext}" "$out/$link.${compressor.ext}"
+      else
+        ln -vs -- "''${target/^${firmware}/$out}" "$out/$link"
+      fi
+  done
+
+  echo "Checking for broken symlinks:"
+  find -L $out -type l -print -execdir false -- '{}' '+'
+''
diff --git a/pkgs/build-support/kernel/make-initrd-ng-tool.nix b/pkgs/build-support/kernel/make-initrd-ng-tool.nix
index 5e08c091c0549..7ffa0bf6813a5 100644
--- a/pkgs/build-support/kernel/make-initrd-ng-tool.nix
+++ b/pkgs/build-support/kernel/make-initrd-ng-tool.nix
@@ -12,7 +12,7 @@ rustPlatform.buildRustPackage {
   meta = {
     description = "Tool for copying binaries and their dependencies";
     mainProgram = "make-initrd-ng";
-    maintainers = with lib.maintainers; [ das_j elvishjerricco k900 lheckemann ];
+    maintainers = with lib.maintainers; [ das_j elvishjerricco k900 ];
     license = lib.licenses.mit;
   };
 }
diff --git a/pkgs/build-support/kernel/modules-closure.sh b/pkgs/build-support/kernel/modules-closure.sh
index 5f61bac751af2..06eb5b0d0de12 100644
--- a/pkgs/build-support/kernel/modules-closure.sh
+++ b/pkgs/build-support/kernel/modules-closure.sh
@@ -80,7 +80,7 @@ for module in $(< ~-/closure); do
     # of its output.
     modinfo -b $kernel --set-version "$version" -F firmware $module | grep -v '^name:' | while read -r i; do
         echo "firmware for $module: $i"
-        for name in "$i" "$i.xz" ""; do
+        for name in "$i" "$i.xz" "$i.zst" ""; do
             [ -z "$name" ] && echo "WARNING: missing firmware $i for module $module"
             if cp -v --parents --no-preserve=mode lib/firmware/$name "$out" 2>/dev/null; then
                 break
diff --git a/pkgs/build-support/lib/meson.nix b/pkgs/build-support/lib/meson.nix
index 456c10fcb8eeb..9ffc5b8c1710f 100644
--- a/pkgs/build-support/lib/meson.nix
+++ b/pkgs/build-support/lib/meson.nix
@@ -23,6 +23,10 @@ let
     [binaries]
     llvm-config = 'llvm-config-native'
     rust = ['rustc', '--target', '${stdenv.targetPlatform.rust.rustcTargetSpec}']
+    # Meson refuses to consider any CMake binary during cross compilation if it's
+    # not explicitly specified here, in the cross file.
+    # https://github.com/mesonbuild/meson/blob/0ed78cf6fa6d87c0738f67ae43525e661b50a8a2/mesonbuild/cmake/executor.py#L72
+    cmake = 'cmake'
   '';
 
   crossFlags = optionals (stdenv.hostPlatform != stdenv.buildPlatform) [ "--cross-file=${crossFile}" ];
diff --git a/pkgs/build-support/libredirect/default.nix b/pkgs/build-support/libredirect/default.nix
index 1ab4a0db827aa..c4399189b7a21 100644
--- a/pkgs/build-support/libredirect/default.nix
+++ b/pkgs/build-support/libredirect/default.nix
@@ -112,7 +112,7 @@ else stdenv.mkDerivation rec {
 
   meta = with lib; {
     platforms = platforms.unix;
-    description = "An LD_PRELOAD library to intercept and rewrite the paths in glibc calls";
+    description = "LD_PRELOAD library to intercept and rewrite the paths in glibc calls";
     longDescription = ''
       libredirect is an LD_PRELOAD library to intercept and rewrite the paths in
       glibc calls based on the value of $NIX_REDIRECTS, a colon-separated list
diff --git a/pkgs/build-support/make-pkgconfigitem/default.nix b/pkgs/build-support/make-pkgconfigitem/default.nix
index d3bcabbb940f9..e459184b8d7eb 100644
--- a/pkgs/build-support/make-pkgconfigitem/default.nix
+++ b/pkgs/build-support/make-pkgconfigitem/default.nix
@@ -4,7 +4,7 @@
 { name # The name of the pc file
   # keywords
   # provide a default description for convenience. it's not important but still required by pkg-config.
-, description ? "A pkg-config file for ${name}"
+, description ? "Pkg-config file for ${name}"
 , url ? ""
 , version ? ""
 , requires ? [ ]
diff --git a/pkgs/build-support/node/fetch-yarn-deps/default.nix b/pkgs/build-support/node/fetch-yarn-deps/default.nix
index 7f0e0692f81f6..4ef74c0cab884 100644
--- a/pkgs/build-support/node/fetch-yarn-deps/default.nix
+++ b/pkgs/build-support/node/fetch-yarn-deps/default.nix
@@ -25,7 +25,8 @@ in {
 
       tar --strip-components=1 -xf ${yarnpkg-lockfile-tar} package/index.js
       mv index.js $out/libexec/yarnpkg-lockfile.js
-      cp ${./.}/common.js ${./.}/index.js $out/libexec/
+      cp ${./common.js} $out/libexec/common.js
+      cp ${./index.js} $out/libexec/index.js
 
       patchShebangs $out/libexec
       makeWrapper $out/libexec/index.js $out/bin/prefetch-yarn-deps \
@@ -53,7 +54,8 @@ in {
 
       tar --strip-components=1 -xf ${yarnpkg-lockfile-tar} package/index.js
       mv index.js $out/libexec/yarnpkg-lockfile.js
-      cp ${./.}/common.js ${./.}/fixup.js $out/libexec/
+      cp ${./common.js} $out/libexec/common.js
+      cp ${./fixup.js} $out/libexec/fixup.js
 
       patchShebangs $out/libexec
       makeWrapper $out/libexec/fixup.js $out/bin/fixup-yarn-lock
@@ -97,7 +99,7 @@ in {
       '';
 
       outputHashMode = "recursive";
-    } // hash_ // (removeAttrs args ["src" "name" "hash" "sha256"]));
+    } // hash_ // (removeAttrs args (["name" "hash" "sha256"] ++ (lib.optional (src == null) "src"))));
 
   in lib.setFunctionArgs f (lib.functionArgs f) // {
     inherit tests;
diff --git a/pkgs/build-support/ocaml/dune.nix b/pkgs/build-support/ocaml/dune.nix
index 972244f80b0a4..e293605cb31d0 100644
--- a/pkgs/build-support/ocaml/dune.nix
+++ b/pkgs/build-support/ocaml/dune.nix
@@ -7,8 +7,7 @@ let Dune =
   { "1" = dune_1; "2" = dune_2; "3" = dune_3; }."${dune-version}"
 ; in
 
-if (args ? minimumOCamlVersion && lib.versionOlder ocaml.version args.minimumOCamlVersion) ||
-   (args ? minimalOCamlVersion && lib.versionOlder ocaml.version args.minimalOCamlVersion)
+if args ? minimalOCamlVersion && lib.versionOlder ocaml.version args.minimalOCamlVersion
 then throw "${pname}-${version} is not available for OCaml ${ocaml.version}"
 else
 
diff --git a/pkgs/build-support/php/build-composer-project.nix b/pkgs/build-support/php/build-composer-project.nix
deleted file mode 100644
index 80c63bcde71b9..0000000000000
--- a/pkgs/build-support/php/build-composer-project.nix
+++ /dev/null
@@ -1,85 +0,0 @@
-{ callPackage, stdenvNoCC, lib, writeTextDir, php, makeBinaryWrapper, fetchFromGitHub, fetchurl }:
-
-let
-  buildComposerProjectOverride = finalAttrs: previousAttrs:
-
-    let
-      phpDrv = finalAttrs.php or php;
-      composer = finalAttrs.composer or phpDrv.packages.composer;
-      composer-local-repo-plugin = callPackage ./pkgs/composer-local-repo-plugin.nix { };
-    in
-    {
-      composerLock = previousAttrs.composerLock or null;
-      composerNoDev = previousAttrs.composerNoDev or true;
-      composerNoPlugins = previousAttrs.composerNoPlugins or true;
-      composerNoScripts = previousAttrs.composerNoScripts or true;
-      composerStrictValidation = previousAttrs.composerStrictValidation or true;
-
-      nativeBuildInputs = (previousAttrs.nativeBuildInputs or [ ]) ++ [
-        composer
-        composer-local-repo-plugin
-        phpDrv
-        phpDrv.composerHooks.composerInstallHook
-      ];
-
-      buildInputs = (previousAttrs.buildInputs or [ ]) ++ [
-        phpDrv
-      ];
-
-      patches = previousAttrs.patches or [ ];
-      strictDeps = previousAttrs.strictDeps or true;
-
-      # Should we keep these empty phases?
-      configurePhase = previousAttrs.configurePhase or ''
-        runHook preConfigure
-
-        runHook postConfigure
-      '';
-
-      buildPhase = previousAttrs.buildPhase or ''
-        runHook preBuild
-
-        runHook postBuild
-      '';
-
-      doCheck = previousAttrs.doCheck or true;
-      checkPhase = previousAttrs.checkPhase or ''
-        runHook preCheck
-
-        runHook postCheck
-      '';
-
-      installPhase = previousAttrs.installPhase or ''
-        runHook preInstall
-
-        runHook postInstall
-      '';
-
-      doInstallCheck = previousAttrs.doInstallCheck or false;
-      installCheckPhase = previousAttrs.installCheckPhase or ''
-        runHook preInstallCheck
-
-        runHook postInstallCheck
-      '';
-
-      composerRepository = phpDrv.mkComposerRepository {
-        inherit composer composer-local-repo-plugin;
-        inherit (finalAttrs) patches pname src vendorHash version;
-
-        composerLock = previousAttrs.composerLock or null;
-        composerNoDev = previousAttrs.composerNoDev or true;
-        composerNoPlugins = previousAttrs.composerNoPlugins or true;
-        composerNoScripts = previousAttrs.composerNoScripts or true;
-        composerStrictValidation = previousAttrs.composerStrictValidation or true;
-      };
-
-      COMPOSER_CACHE_DIR="/dev/null";
-      COMPOSER_DISABLE_NETWORK="1";
-      COMPOSER_MIRROR_PATH_REPOS="1";
-
-      meta = previousAttrs.meta or { } // {
-        platforms = lib.platforms.all;
-      };
-    };
-in
-args: (stdenvNoCC.mkDerivation args).overrideAttrs buildComposerProjectOverride
diff --git a/pkgs/build-support/php/build-pecl.nix b/pkgs/build-support/php/build-pecl.nix
index 6f38a668f3a34..16913a85c63de 100644
--- a/pkgs/build-support/php/build-pecl.nix
+++ b/pkgs/build-support/php/build-pecl.nix
@@ -1,45 +1,69 @@
-{ stdenv, lib, php, autoreconfHook, fetchurl, re2c, nix-update-script }:
+{
+  stdenv,
+  lib,
+  php,
+  autoreconfHook,
+  fetchurl,
+  re2c,
+  nix-update-script,
+}:
 
-{ pname
-, version
-, internalDeps ? [ ]
-, peclDeps ? [ ]
-, buildInputs ? [ ]
-, nativeBuildInputs ? [ ]
-, postPhpize ? ""
-, makeFlags ? [ ]
-, src ? fetchurl ({
-    url = "https://pecl.php.net/get/${pname}-${version}.tgz";
-  } // lib.filterAttrs (attrName: _: lib.elem attrName [ "sha256" "hash" ]) args)
-, passthru ? { }
-, ...
+{
+  pname,
+  version,
+  internalDeps ? [ ],
+  peclDeps ? [ ],
+  buildInputs ? [ ],
+  nativeBuildInputs ? [ ],
+  postPhpize ? "",
+  makeFlags ? [ ],
+  src ? fetchurl (
+    {
+      url = "https://pecl.php.net/get/${pname}-${version}.tgz";
+    }
+    // lib.filterAttrs (
+      attrName: _:
+      lib.elem attrName [
+        "sha256"
+        "hash"
+      ]
+    ) args
+  ),
+  passthru ? { },
+  ...
 }@args:
 
-stdenv.mkDerivation (args // {
-  name = "php-${pname}-${version}";
-  extensionName = pname;
+stdenv.mkDerivation (
+  args
+  // {
+    name = "php-${pname}-${version}";
+    extensionName = pname;
 
-  inherit src;
+    inherit src;
 
-  nativeBuildInputs = [ autoreconfHook re2c ] ++ nativeBuildInputs;
-  buildInputs = [ php ] ++ peclDeps ++ buildInputs;
+    nativeBuildInputs = [
+      autoreconfHook
+      re2c
+    ] ++ nativeBuildInputs;
+    buildInputs = [ php ] ++ peclDeps ++ buildInputs;
 
-  makeFlags = [ "EXTENSION_DIR=$(out)/lib/php/extensions" ] ++ makeFlags;
+    makeFlags = [ "EXTENSION_DIR=$(out)/lib/php/extensions" ] ++ makeFlags;
 
-  autoreconfPhase = ''
-    phpize
-    ${postPhpize}
-    ${lib.concatMapStringsSep "\n"
-      (dep: "mkdir -p ext; ln -s ${dep.dev}/include ext/${dep.extensionName}")
-      internalDeps}
-  '';
-  checkPhase = "NO_INTERACTON=yes make test";
+    autoreconfPhase = ''
+      phpize
+      ${postPhpize}
+      ${lib.concatMapStringsSep "\n" (
+        dep: "mkdir -p ext; ln -s ${dep.dev}/include ext/${dep.extensionName}"
+      ) internalDeps}
+    '';
+    checkPhase = "NO_INTERACTON=yes make test";
 
-  passthru = passthru // {
-    # Thes flags were introduced for `nix-update` so that it can update
-    # PHP extensions correctly.
-    # See the corresponding PR: https://github.com/Mic92/nix-update/pull/123
-    isPhpExtension = true;
-    updateScript = nix-update-script {};
-  };
-})
+    passthru = passthru // {
+      # Thes flags were introduced for `nix-update` so that it can update
+      # PHP extensions correctly.
+      # See the corresponding PR: https://github.com/Mic92/nix-update/pull/123
+      isPhpExtension = true;
+      updateScript = nix-update-script { };
+    };
+  }
+)
diff --git a/pkgs/build-support/php/builders/default.nix b/pkgs/build-support/php/builders/default.nix
new file mode 100644
index 0000000000000..ea9bb33504356
--- /dev/null
+++ b/pkgs/build-support/php/builders/default.nix
@@ -0,0 +1,9 @@
+{ callPackage, callPackages, ... }:
+{
+  v1 = {
+    buildComposerProject = callPackage ./v1/build-composer-project.nix { };
+    buildComposerWithPlugin = callPackage ./v1/build-composer-with-plugin.nix { };
+    mkComposerRepository = callPackage ./v1/build-composer-repository.nix { };
+    composerHooks = callPackages ./v1/hooks { };
+  };
+}
diff --git a/pkgs/build-support/php/builders/v1/build-composer-project.nix b/pkgs/build-support/php/builders/v1/build-composer-project.nix
new file mode 100644
index 0000000000000..698391ad1603c
--- /dev/null
+++ b/pkgs/build-support/php/builders/v1/build-composer-project.nix
@@ -0,0 +1,108 @@
+{
+  nix-update-script,
+  stdenvNoCC,
+  lib,
+  php,
+}:
+
+let
+  buildComposerProjectOverride =
+    finalAttrs: previousAttrs:
+
+    let
+      phpDrv = finalAttrs.php or php;
+      composer = finalAttrs.composer or phpDrv.packages.composer-local-repo-plugin;
+    in
+    {
+      composerLock = previousAttrs.composerLock or null;
+      composerNoDev = previousAttrs.composerNoDev or true;
+      composerNoPlugins = previousAttrs.composerNoPlugins or true;
+      composerNoScripts = previousAttrs.composerNoScripts or true;
+      composerStrictValidation = previousAttrs.composerStrictValidation or true;
+
+      nativeBuildInputs = (previousAttrs.nativeBuildInputs or [ ]) ++ [
+        composer
+        phpDrv
+        phpDrv.composerHooks.composerInstallHook
+      ];
+
+      buildInputs = (previousAttrs.buildInputs or [ ]) ++ [ phpDrv ];
+
+      patches = previousAttrs.patches or [ ];
+      strictDeps = previousAttrs.strictDeps or true;
+
+      # Should we keep these empty phases?
+      configurePhase =
+        previousAttrs.configurePhase or ''
+          runHook preConfigure
+
+          runHook postConfigure
+        '';
+
+      buildPhase =
+        previousAttrs.buildPhase or ''
+          runHook preBuild
+
+          runHook postBuild
+        '';
+
+      doCheck = previousAttrs.doCheck or true;
+      checkPhase =
+        previousAttrs.checkPhase or ''
+          runHook preCheck
+
+          runHook postCheck
+        '';
+
+      installPhase =
+        previousAttrs.installPhase or ''
+          runHook preInstall
+
+          runHook postInstall
+        '';
+
+      doInstallCheck = previousAttrs.doInstallCheck or false;
+      installCheckPhase =
+        previousAttrs.installCheckPhase or ''
+          runHook preInstallCheck
+
+          runHook postInstallCheck
+        '';
+
+      composerRepository =
+        previousAttrs.composerRepository or (phpDrv.mkComposerRepository {
+          inherit composer;
+          inherit (finalAttrs)
+            patches
+            pname
+            src
+            vendorHash
+            version
+            ;
+
+          composerLock = previousAttrs.composerLock or null;
+          composerNoDev = previousAttrs.composerNoDev or true;
+          composerNoPlugins = previousAttrs.composerNoPlugins or true;
+          composerNoScripts = previousAttrs.composerNoScripts or true;
+          composerStrictValidation = previousAttrs.composerStrictValidation or true;
+        });
+
+      # Projects providing a lockfile from upstream can be automatically updated.
+      passthru = previousAttrs.passthru or { } // {
+        updateScript =
+          previousAttrs.passthru.updateScript
+            or (if finalAttrs.composerRepository.composerLock == null then nix-update-script { } else null);
+      };
+
+      env = {
+        COMPOSER_CACHE_DIR = "/dev/null";
+        COMPOSER_DISABLE_NETWORK = "1";
+        COMPOSER_MIRROR_PATH_REPOS = "1";
+      };
+
+      meta = previousAttrs.meta or { } // {
+        platforms = lib.platforms.all;
+      };
+    };
+in
+args: (stdenvNoCC.mkDerivation args).overrideAttrs buildComposerProjectOverride
diff --git a/pkgs/build-support/php/build-composer-repository.nix b/pkgs/build-support/php/builders/v1/build-composer-repository.nix
index e359c0829aaf7..037d8bdeb3eb1 100644
--- a/pkgs/build-support/php/build-composer-repository.nix
+++ b/pkgs/build-support/php/builders/v1/build-composer-repository.nix
@@ -1,4 +1,8 @@
-{ callPackage, stdenvNoCC, lib, writeTextDir, fetchFromGitHub, php }:
+{
+  stdenvNoCC,
+  lib,
+  php,
+}:
 
 let
   mkComposerRepositoryOverride =
@@ -18,16 +22,27 @@ let
 
     let
       phpDrv = finalAttrs.php or php;
-      composer = finalAttrs.composer or phpDrv.packages.composer;
-      composer-local-repo-plugin = callPackage ./pkgs/composer-local-repo-plugin.nix { };
+      composer = finalAttrs.composer or phpDrv.packages.composer-local-repo-plugin;
     in
     assert (lib.assertMsg (previousAttrs ? src) "mkComposerRepository expects src argument.");
-    assert (lib.assertMsg (previousAttrs ? vendorHash) "mkComposerRepository expects vendorHash argument.");
+    assert (
+      lib.assertMsg (previousAttrs ? vendorHash) "mkComposerRepository expects vendorHash argument."
+    );
     assert (lib.assertMsg (previousAttrs ? version) "mkComposerRepository expects version argument.");
     assert (lib.assertMsg (previousAttrs ? pname) "mkComposerRepository expects pname argument.");
-    assert (lib.assertMsg (previousAttrs ? composerNoDev) "mkComposerRepository expects composerNoDev argument.");
-    assert (lib.assertMsg (previousAttrs ? composerNoPlugins) "mkComposerRepository expects composerNoPlugins argument.");
-    assert (lib.assertMsg (previousAttrs ? composerNoScripts) "mkComposerRepository expects composerNoScripts argument.");
+    assert (
+      lib.assertMsg (previousAttrs ? composerNoDev) "mkComposerRepository expects composerNoDev argument."
+    );
+    assert (
+      lib.assertMsg (
+        previousAttrs ? composerNoPlugins
+      ) "mkComposerRepository expects composerNoPlugins argument."
+    );
+    assert (
+      lib.assertMsg (
+        previousAttrs ? composerNoScripts
+      ) "mkComposerRepository expects composerNoScripts argument."
+    );
     {
       composerNoDev = previousAttrs.composerNoDev or true;
       composerNoPlugins = previousAttrs.composerNoPlugins or true;
@@ -41,7 +56,6 @@ let
 
       nativeBuildInputs = (previousAttrs.nativeBuildInputs or [ ]) ++ [
         composer
-        composer-local-repo-plugin
         phpDrv
         phpDrv.composerHooks.composerRepositoryHook
       ];
@@ -51,45 +65,53 @@ let
       strictDeps = previousAttrs.strictDeps or true;
 
       # Should we keep these empty phases?
-      configurePhase = previousAttrs.configurePhase or ''
-        runHook preConfigure
+      configurePhase =
+        previousAttrs.configurePhase or ''
+          runHook preConfigure
 
-        runHook postConfigure
-      '';
+          runHook postConfigure
+        '';
 
-      buildPhase = previousAttrs.buildPhase or ''
-        runHook preBuild
+      buildPhase =
+        previousAttrs.buildPhase or ''
+          runHook preBuild
 
-        runHook postBuild
-      '';
+          runHook postBuild
+        '';
 
       doCheck = previousAttrs.doCheck or true;
-      checkPhase = previousAttrs.checkPhase or ''
-        runHook preCheck
+      checkPhase =
+        previousAttrs.checkPhase or ''
+          runHook preCheck
 
-        runHook postCheck
-      '';
+          runHook postCheck
+        '';
 
-      installPhase = previousAttrs.installPhase or ''
-        runHook preInstall
+      installPhase =
+        previousAttrs.installPhase or ''
+          runHook preInstall
 
-        runHook postInstall
-      '';
+          runHook postInstall
+        '';
 
       doInstallCheck = previousAttrs.doInstallCheck or false;
-      installCheckPhase = previousAttrs.installCheckPhase or ''
-        runHook preInstallCheck
+      installCheckPhase =
+        previousAttrs.installCheckPhase or ''
+          runHook preInstallCheck
 
-        runHook postInstallCheck
-      '';
+          runHook postInstallCheck
+        '';
 
-      COMPOSER_CACHE_DIR = "/dev/null";
-      COMPOSER_MIRROR_PATH_REPOS = "1";
-      COMPOSER_HTACCESS_PROTECT = "0";
-      COMPOSER_DISABLE_NETWORK = "0";
+      env = {
+        COMPOSER_CACHE_DIR = "/dev/null";
+        COMPOSER_MIRROR_PATH_REPOS = "1";
+        COMPOSER_HTACCESS_PROTECT = "0";
+        COMPOSER_DISABLE_NETWORK = "0";
+      };
 
       outputHashMode = "recursive";
-      outputHashAlgo = if (finalAttrs ? vendorHash && finalAttrs.vendorHash != "") then null else "sha256";
+      outputHashAlgo =
+        if (finalAttrs ? vendorHash && finalAttrs.vendorHash != "") then null else "sha256";
       outputHash = finalAttrs.vendorHash or "";
     };
 in
diff --git a/pkgs/build-support/php/builders/v1/build-composer-with-plugin.nix b/pkgs/build-support/php/builders/v1/build-composer-with-plugin.nix
new file mode 100644
index 0000000000000..060b51241e6c8
--- /dev/null
+++ b/pkgs/build-support/php/builders/v1/build-composer-with-plugin.nix
@@ -0,0 +1,161 @@
+{
+  stdenvNoCC,
+  writeText,
+  lib,
+  makeBinaryWrapper,
+  php,
+  cacert,
+  nix-update-script,
+}:
+
+let
+  composerJsonBuilder =
+    pluginName: pluginVersion:
+    writeText "composer.json" (
+      builtins.toJSON {
+        name = "nix/plugin";
+        description = "Nix Composer plugin";
+        license = "MIT";
+        require = {
+          "${pluginName}" = "${pluginVersion}";
+        };
+        config = {
+          "allow-plugins" = {
+            "${pluginName}" = true;
+          };
+        };
+        repositories = [
+          {
+            type = "path";
+            url = "./src";
+            options = {
+              versions = {
+                "${pluginName}" = "${pluginVersion}";
+              };
+            };
+          }
+        ];
+      }
+    );
+
+  buildComposerWithPluginOverride =
+    finalAttrs: previousAttrs:
+
+    let
+      phpDrv = finalAttrs.php or php;
+      composer = finalAttrs.composer or phpDrv.packages.composer;
+    in
+    {
+      composerLock = previousAttrs.composerLock or null;
+      composerNoDev = previousAttrs.composerNoDev or true;
+      composerNoPlugins = previousAttrs.composerNoPlugins or true;
+      composerNoScripts = previousAttrs.composerNoScripts or true;
+      composerStrictValidation = previousAttrs.composerStrictValidation or true;
+      composerGlobal = true;
+
+      nativeBuildInputs = (previousAttrs.nativeBuildInputs or [ ]) ++ [
+        composer
+        phpDrv
+        makeBinaryWrapper
+      ];
+
+      buildInputs = (previousAttrs.buildInputs or [ ]) ++ [ phpDrv ];
+
+      patches = previousAttrs.patches or [ ];
+      strictDeps = previousAttrs.strictDeps or true;
+
+      # Should we keep these empty phases?
+      configurePhase =
+        previousAttrs.configurePhase or ''
+          runHook preConfigure
+
+          runHook postConfigure
+        '';
+
+      buildPhase =
+        previousAttrs.buildPhase or ''
+          runHook preBuild
+
+          runHook postBuild
+        '';
+
+      doCheck = previousAttrs.doCheck or true;
+
+      checkPhase =
+        previousAttrs.checkPhase or ''
+          runHook preCheck
+
+          runHook postCheck
+        '';
+
+      installPhase =
+        previousAttrs.installPhase or ''
+            runHook preInstall
+
+          makeWrapper ${lib.getExe composer} $out/bin/composer \
+            --prefix COMPOSER_HOME : ${finalAttrs.vendor}
+
+            runHook postInstall
+        '';
+
+      doInstallCheck = previousAttrs.doInstallCheck or false;
+      installCheckPhase =
+        previousAttrs.installCheckPhase or ''
+          runHook preInstallCheck
+
+          composer global show ${finalAttrs.pname}
+
+          runHook postInstallCheck
+        '';
+
+      vendor = previousAttrs.vendor or stdenvNoCC.mkDerivation {
+        pname = "${finalAttrs.pname}-vendor";
+        pluginName = finalAttrs.pname;
+
+        inherit (finalAttrs) version src;
+
+        composerLock = previousAttrs.composerLock or null;
+        composerNoDev = previousAttrs.composerNoDev or true;
+        composerNoPlugins = previousAttrs.composerNoPlugins or true;
+        composerNoScripts = previousAttrs.composerNoScripts or true;
+        composerStrictValidation = previousAttrs.composerStrictValidation or true;
+        composerGlobal = true;
+        composerJson = composerJsonBuilder finalAttrs.pname finalAttrs.version;
+
+        nativeBuildInputs = [
+          cacert
+          composer
+          phpDrv.composerHooks.composerWithPluginVendorHook
+        ];
+
+        dontPatchShebangs = true;
+        doCheck = true;
+        doInstallCheck = true;
+
+        env = {
+          COMPOSER_CACHE_DIR = "/dev/null";
+          COMPOSER_HTACCESS_PROTECT = "0";
+        };
+
+        outputHashMode = "recursive";
+        outputHashAlgo = "sha256";
+        outputHash = finalAttrs.vendorHash;
+      };
+
+      # Projects providing a lockfile from upstream can be automatically updated.
+      passthru = previousAttrs.passthru or { } // {
+        updateScript =
+          previousAttrs.passthru.updateScript
+            or (if finalAttrs.vendor.composerLock == null then nix-update-script { } else null);
+      };
+
+      env = {
+        COMPOSER_CACHE_DIR = "/dev/null";
+        COMPOSER_DISABLE_NETWORK = "1";
+        COMPOSER_MIRROR_PATH_REPOS = "1";
+      };
+
+      meta = previousAttrs.meta or composer.meta;
+    };
+in
+args: (stdenvNoCC.mkDerivation args).overrideAttrs buildComposerWithPluginOverride
diff --git a/pkgs/build-support/php/hooks/composer-install-hook.sh b/pkgs/build-support/php/builders/v1/hooks/composer-install-hook.sh
index a91263422bc84..44e87d06d3a53 100644
--- a/pkgs/build-support/php/hooks/composer-install-hook.sh
+++ b/pkgs/build-support/php/builders/v1/hooks/composer-install-hook.sh
@@ -83,7 +83,7 @@ composerInstallBuildHook() {
 
     # Since this file cannot be generated in the composer-repository-hook.sh
     # because the file contains hardcoded nix store paths, we generate it here.
-    composer-local-repo-plugin --no-ansi build-local-repo-lock -m "${composerRepository}" .
+    composer build-local-repo-lock -m "${composerRepository}" .
 
     echo "Finished composerInstallBuildHook"
 }
diff --git a/pkgs/build-support/php/hooks/composer-repository-hook.sh b/pkgs/build-support/php/builders/v1/hooks/composer-repository-hook.sh
index c4fa0d52126c1..ec9777541fc0f 100644
--- a/pkgs/build-support/php/hooks/composer-repository-hook.sh
+++ b/pkgs/build-support/php/builders/v1/hooks/composer-repository-hook.sh
@@ -63,7 +63,7 @@ composerRepositoryBuildHook() {
     # Build the local composer repository
     # The command 'build-local-repo' is provided by the Composer plugin
     # nix-community/composer-local-repo-plugin.
-    composer-local-repo-plugin --no-ansi build-local-repo-lock ${composerNoDev:+--no-dev} -r repository
+    composer build-local-repo-lock ${composerNoDev:+--no-dev} -r repository
 
     echo "Finished composerRepositoryBuildHook"
 }
diff --git a/pkgs/build-support/php/builders/v1/hooks/composer-with-plugin-vendor-hook.sh b/pkgs/build-support/php/builders/v1/hooks/composer-with-plugin-vendor-hook.sh
new file mode 100644
index 0000000000000..0d88d14094ad4
--- /dev/null
+++ b/pkgs/build-support/php/builders/v1/hooks/composer-with-plugin-vendor-hook.sh
@@ -0,0 +1,93 @@
+declare composerLock
+declare version
+declare composerNoDev
+declare composerNoPlugins
+declare composerNoScripts
+declare composerStrictValidation
+
+preConfigureHooks+=(composerWithPluginConfigureHook)
+preBuildHooks+=(composerWithPluginBuildHook)
+preCheckHooks+=(composerWithPluginCheckHook)
+preInstallHooks+=(composerWithPluginInstallHook)
+preInstallCheckHooks+=(composerWithPluginInstallCheckHook)
+
+source @phpScriptUtils@
+
+composerWithPluginConfigureHook() {
+    echo "Executing composerWithPluginConfigureHook"
+
+    mkdir -p $out
+
+    export COMPOSER_HOME=$out
+
+    if [[ -e "$composerLock" ]]; then
+        cp $composerLock $out/composer.lock
+    fi
+
+    cp $composerJson $out/composer.json
+    cp -ar $src $out/src
+
+    if [[ ! -f "$out/composer.lock" ]]; then
+        setComposeRootVersion
+
+        composer \
+            global \
+            --no-install \
+            --no-interaction \
+            --no-progress \
+            ${composerNoDev:+--no-dev} \
+            ${composerNoPlugins:+--no-plugins} \
+            ${composerNoScripts:+--no-scripts} \
+            update
+
+        echo
+        echo -e "\e[31mERROR: No composer.lock found\e[0m"
+        echo
+        echo -e '\e[31mNo composer.lock file found, consider adding one to your repository to ensure reproducible builds.\e[0m'
+        echo -e "\e[31mIn the meantime, a composer.lock file has been generated for you in $out/composer.lock\e[0m"
+        echo
+        echo -e '\e[31mTo fix the issue:\e[0m'
+        echo -e "\e[31m1. Copy the composer.lock file from $out/composer.lock to the project's source:\e[0m"
+        echo -e "\e[31m  cp $out/composer.lock <path>\e[0m"
+        echo -e '\e[31m2. Add the composerLock attribute, pointing to the copied composer.lock file:\e[0m'
+        echo -e '\e[31m  composerLock = ./composer.lock;\e[0m'
+        echo
+
+        exit 1
+    fi
+
+    echo "Finished composerWithPluginConfigureHook"
+}
+
+composerWithPluginBuildHook() {
+    echo "Executing composerWithPluginBuildHook"
+
+    echo "Finished composerWithPluginBuildHook"
+}
+
+composerWithPluginCheckHook() {
+    echo "Executing composerWithPluginCheckHook"
+
+    checkComposerValidate
+
+    echo "Finished composerWithPluginCheckHook"
+}
+
+composerWithPluginInstallHook() {
+    echo "Executing composerWithPluginInstallHook"
+
+    composer \
+        global \
+        --no-interaction \
+        --no-progress \
+        ${composerNoDev:+--no-dev} \
+        ${composerNoPlugins:+--no-plugins} \
+        ${composerNoScripts:+--no-scripts} \
+        install
+
+    echo "Finished composerWithPluginInstallHook"
+}
+
+composerWithPluginInstallCheckHook() {
+    composer global show $pluginName
+}
diff --git a/pkgs/build-support/php/builders/v1/hooks/default.nix b/pkgs/build-support/php/builders/v1/hooks/default.nix
new file mode 100644
index 0000000000000..d10ff78067278
--- /dev/null
+++ b/pkgs/build-support/php/builders/v1/hooks/default.nix
@@ -0,0 +1,60 @@
+{
+  lib,
+  makeSetupHook,
+  jq,
+  writeShellApplication,
+  moreutils,
+  cacert,
+  buildPackages,
+}:
+
+let
+  php-script-utils = writeShellApplication {
+    name = "php-script-utils";
+    runtimeInputs = [ jq ];
+    text = builtins.readFile ./php-script-utils.bash;
+  };
+in
+{
+  composerRepositoryHook = makeSetupHook {
+    name = "composer-repository-hook.sh";
+    propagatedBuildInputs = [
+      jq
+      moreutils
+      cacert
+    ];
+    substitutions = {
+      phpScriptUtils = lib.getExe php-script-utils;
+    };
+  } ./composer-repository-hook.sh;
+
+  composerInstallHook = makeSetupHook {
+    name = "composer-install-hook.sh";
+    propagatedBuildInputs = [
+      jq
+      moreutils
+      cacert
+    ];
+    substitutions = {
+      # Specify the stdenv's `diff` by abspath to ensure that the user's build
+      # inputs do not cause us to find the wrong `diff`.
+      cmp = "${lib.getBin buildPackages.diffutils}/bin/cmp";
+      phpScriptUtils = lib.getExe php-script-utils;
+    };
+  } ./composer-install-hook.sh;
+
+  composerWithPluginVendorHook = makeSetupHook {
+    name = "composer-with-plugin-vendor-hook.sh";
+    propagatedBuildInputs = [
+      jq
+      moreutils
+      cacert
+    ];
+    substitutions = {
+      # Specify the stdenv's `diff` by abspath to ensure that the user's build
+      # inputs do not cause us to find the wrong `diff`.
+      cmp = "${lib.getBin buildPackages.diffutils}/bin/cmp";
+      phpScriptUtils = lib.getExe php-script-utils;
+    };
+  } ./composer-with-plugin-vendor-hook.sh;
+}
diff --git a/pkgs/build-support/php/hooks/php-script-utils.bash b/pkgs/build-support/php/builders/v1/hooks/php-script-utils.bash
index bba0242e65d1e..65c0a3b410f69 100644
--- a/pkgs/build-support/php/hooks/php-script-utils.bash
+++ b/pkgs/build-support/php/builders/v1/hooks/php-script-utils.bash
@@ -1,5 +1,6 @@
 declare version
 declare composerStrictValidation
+declare composerGlobal
 
 setComposeRootVersion() {
     set +e # Disable exit on error
@@ -13,7 +14,16 @@ setComposeRootVersion() {
 }
 
 checkComposerValidate() {
-    if ! composer validate --strict --no-ansi --no-interaction --quiet --no-check-all --no-check-lock; then
+    setComposeRootVersion
+
+    if [ "1" == "${composerGlobal-}" ]; then
+      global="global";
+    else
+      global="";
+    fi
+
+    command="composer ${global} validate --strict --quiet --no-interaction --no-check-all --no-check-lock"
+    if ! $command; then
         if [ "1" == "${composerStrictValidation-}" ]; then
             echo
             echo -e "\e[31mERROR: composer files validation failed\e[0m"
@@ -42,7 +52,8 @@ checkComposerValidate() {
         fi
     fi
 
-    if ! composer validate --strict --no-ansi --no-interaction --quiet --no-check-all --check-lock; then
+    command="composer ${global} validate --strict --no-ansi --no-interaction --quiet --no-check-all --check-lock"
+    if ! $command; then
         if [ "1" == "${composerStrictValidation-}" ]; then
             echo
             echo -e "\e[31mERROR: composer files validation failed\e[0m"
diff --git a/pkgs/build-support/php/hooks/default.nix b/pkgs/build-support/php/hooks/default.nix
deleted file mode 100644
index ca96b1056db9d..0000000000000
--- a/pkgs/build-support/php/hooks/default.nix
+++ /dev/null
@@ -1,39 +0,0 @@
-{ lib
-, makeSetupHook
-, diffutils
-, jq
-, writeShellApplication
-, moreutils
-, cacert
-, buildPackages
-}:
-
-let
-  php-script-utils = writeShellApplication {
-    name = "php-script-utils";
-    runtimeInputs = [ jq ];
-    text = builtins.readFile ./php-script-utils.bash;
-  };
-in
-{
-  composerRepositoryHook = makeSetupHook
-    {
-      name = "composer-repository-hook.sh";
-      propagatedBuildInputs = [ jq moreutils cacert ];
-      substitutions = {
-        phpScriptUtils = lib.getExe php-script-utils;
-      };
-    } ./composer-repository-hook.sh;
-
-  composerInstallHook = makeSetupHook
-    {
-      name = "composer-install-hook.sh";
-      propagatedBuildInputs = [ jq moreutils cacert ];
-      substitutions = {
-        # Specify the stdenv's `diff` by abspath to ensure that the user's build
-        # inputs do not cause us to find the wrong `diff`.
-        cmp = "${lib.getBin buildPackages.diffutils}/bin/cmp";
-        phpScriptUtils = lib.getExe php-script-utils;
-      };
-    } ./composer-install-hook.sh;
-}
diff --git a/pkgs/build-support/php/pkgs/composer-local-repo-plugin.nix b/pkgs/build-support/php/pkgs/composer-local-repo-plugin.nix
deleted file mode 100644
index bfdc3d4f98d1b..0000000000000
--- a/pkgs/build-support/php/pkgs/composer-local-repo-plugin.nix
+++ /dev/null
@@ -1,113 +0,0 @@
-{ php, callPackage, stdenvNoCC, lib, fetchFromGitHub, makeBinaryWrapper }:
-
-let
-  composer = callPackage ./composer-phar.nix {
-    inherit (php.packages.composer) version pharHash;
-  };
-
-  composerKeys = stdenvNoCC.mkDerivation (finalComposerKeysAttrs: {
-    pname = "composer-keys";
-    version = "fa5a62092f33e094073fbda23bbfc7188df3cbc5";
-
-    src = fetchFromGitHub {
-      owner = "composer";
-      repo = "composer.github.io";
-      rev = "${finalComposerKeysAttrs.version}";
-      hash = "sha256-3Sfn71LDG1jHwuEIU8iEnV3k6D6QTX7KVIKVaNSuCVE=";
-    };
-
-    installPhase = ''
-      runHook preInstall
-
-      mkdir -p $out
-      install releases.pub $out/keys.tags.pub
-      install snapshots.pub $out/keys.dev.pub
-
-      runHook postInstall
-    '';
-  });
-in
-stdenvNoCC.mkDerivation (finalAttrs: {
-  pname = "composer-local-repo-plugin";
-  version = "1.1.0";
-
-  src = fetchFromGitHub {
-    owner = "nix-community";
-    repo = "composer-local-repo-plugin";
-    rev = finalAttrs.version;
-    hash = "sha256-edbn07r/Uc1g0qOuVBZBs6N1bMN5kIfA1b4FCufdw5M=";
-  };
-
-  COMPOSER_CACHE_DIR = "/dev/null";
-  COMPOSER_MIRROR_PATH_REPOS = "1";
-  COMPOSER_HTACCESS_PROTECT = "0";
-  COMPOSER_DISABLE_NETWORK = "1";
-
-  nativeBuildInputs = [
-    makeBinaryWrapper
-  ];
-
-  buildInputs = [
-    composer
-  ];
-
-  configurePhase = ''
-    runHook preConfigure
-
-    export COMPOSER_HOME=${placeholder "out"}
-
-    runHook postConfigure
-  '';
-
-  buildPhase = ''
-    runHook preBuild
-
-    # Configure composer globally
-    composer global init --quiet --no-interaction --no-ansi \
-      --name="nixos/composer" \
-      --homepage "https://nixos.org/" \
-      --description "Composer with nix-community/composer-local-repo-plugin" \
-      --license "MIT"
-
-    composer global config --quiet minimum-stability dev
-    composer global config --quiet prefer-stable true
-    composer global config --quiet apcu-autoloader false
-    composer global config --quiet allow-plugins.nix-community/composer-local-repo-plugin true
-    composer global config --quiet repo.packagist false
-    composer global config --quiet repo.plugin path $src
-
-    # Install the local repository plugin
-    composer global require --quiet --no-ansi --no-interaction nix-community/composer-local-repo-plugin
-
-    runHook postBuild
-  '';
-
-  checkPhase = ''
-    runHook preCheck
-
-    composer global validate --no-ansi
-    composer global show --no-ansi nix-community/composer-local-repo-plugin
-
-    runHook postCheck
-  '';
-
-  installPhase = ''
-    runHook preInstall
-
-    mkdir -p $out
-    cp -ar ${composerKeys}/* $out/
-
-    makeWrapper ${composer}/bin/composer $out/bin/composer-local-repo-plugin \
-      --prefix COMPOSER_HOME : $out
-
-    runHook postInstall
-  '';
-
-  meta = {
-    description = "Composer local repo plugin for Composer";
-    homepage = "https://github.com/nix-community/composer-local-repo-plugin";
-    license = lib.licenses.mit;
-    maintainers = with lib.maintainers; [ drupol ];
-    platforms = lib.platforms.all;
-  };
-})
diff --git a/pkgs/build-support/php/pkgs/composer-phar.nix b/pkgs/build-support/php/pkgs/composer-phar.nix
index f281334ab2d9f..b07c25beec55f 100644
--- a/pkgs/build-support/php/pkgs/composer-phar.nix
+++ b/pkgs/build-support/php/pkgs/composer-phar.nix
@@ -1,17 +1,16 @@
 {
-  _7zz
-  , cacert
-  , curl
-  , fetchurl
-  , git
-  , lib
-  , makeBinaryWrapper
-  , php
-  , stdenvNoCC
-  , unzip
-  , xz
-  , version
-  , pharHash
+  _7zz,
+  curl,
+  fetchurl,
+  git,
+  lib,
+  makeBinaryWrapper,
+  php,
+  stdenvNoCC,
+  unzip,
+  xz,
+  version,
+  pharHash,
 }:
 
 stdenvNoCC.mkDerivation (finalAttrs: {
@@ -32,9 +31,17 @@ stdenvNoCC.mkDerivation (finalAttrs: {
 
     mkdir -p $out/bin
     install -D $src $out/libexec/composer/composer.phar
-    makeWrapper ${php}/bin/php $out/bin/composer \
+    makeWrapper ${lib.getExe php} $out/bin/composer \
       --add-flags "$out/libexec/composer/composer.phar" \
-      --prefix PATH : ${lib.makeBinPath [ _7zz cacert curl git unzip xz ]}
+      --prefix PATH : ${
+        lib.makeBinPath [
+          _7zz
+          curl
+          git
+          unzip
+          xz
+        ]
+      }
 
     runHook postInstall
   '';
@@ -44,6 +51,7 @@ stdenvNoCC.mkDerivation (finalAttrs: {
     description = "Dependency Manager for PHP, shipped from the PHAR file";
     homepage = "https://getcomposer.org/";
     license = lib.licenses.mit;
+    mainProgram = "composer";
     maintainers = with lib.maintainers; [ drupol ];
     platforms = lib.platforms.all;
   };
diff --git a/pkgs/build-support/rust/build-rust-crate/build-crate.nix b/pkgs/build-support/rust/build-rust-crate/build-crate.nix
index bbb26606a6a4d..7484b3ad0290e 100644
--- a/pkgs/build-support/rust/build-rust-crate/build-crate.nix
+++ b/pkgs/build-support/rust/build-rust-crate/build-crate.nix
@@ -51,7 +51,7 @@
     # configure & source common build functions
     LIB_RUSTC_OPTS="${libRustcOpts}"
     BIN_RUSTC_OPTS="${binRustcOpts}"
-    LIB_EXT="${stdenv.hostPlatform.extensions.sharedLibrary or ""}"
+    LIB_EXT="${stdenv.hostPlatform.extensions.library}"
     LIB_PATH="${libPath}"
     LIB_NAME="${libName}"
 
diff --git a/pkgs/build-support/rust/build-rust-crate/configure-crate.nix b/pkgs/build-support/rust/build-rust-crate/configure-crate.nix
index 6b88271602623..ab872bac854f8 100644
--- a/pkgs/build-support/rust/build-rust-crate/configure-crate.nix
+++ b/pkgs/build-support/rust/build-rust-crate/configure-crate.nix
@@ -198,13 +198,16 @@ in ''
      )
 
      set +e
-     EXTRA_BUILD=$(sed -n "s/^cargo:rustc-flags=\(.*\)/\1/p" target/build/${crateName}.opt | tr '\n' ' ' | sort -u)
-     EXTRA_FEATURES=$(sed -n "s/^cargo:rustc-cfg=\(.*\)/--cfg \1/p" target/build/${crateName}.opt | tr '\n' ' ')
-     EXTRA_LINK_ARGS=$(sed -n "s/^cargo:rustc-link-arg=\(.*\)/-C link-arg=\1/p" target/build/${crateName}.opt | tr '\n' ' ')
-     EXTRA_LINK_ARGS_BINS=$(sed -n "s/^cargo:rustc-link-arg-bins=\(.*\)/-C link-arg=\1/p" target/build/${crateName}.opt | tr '\n' ' ')
-     EXTRA_LINK_ARGS_LIB=$(sed -n "s/^cargo:rustc-link-arg-lib=\(.*\)/-C link-arg=\1/p" target/build/${crateName}.opt | tr '\n' ' ')
-     EXTRA_LINK_LIBS=$(sed -n "s/^cargo:rustc-link-lib=\(.*\)/\1/p" target/build/${crateName}.opt | tr '\n' ' ')
-     EXTRA_LINK_SEARCH=$(sed -n "s/^cargo:rustc-link-search=\(.*\)/\1/p" target/build/${crateName}.opt | tr '\n' ' ' | sort -u)
+     # We want to support the new prefix invocation syntax which uses two colons
+     # See https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script
+
+     EXTRA_BUILD=$(sed -n "s/^cargo::\{0,1\}rustc-flags=\(.*\)/\1/p" target/build/${crateName}.opt | tr '\n' ' ' | sort -u)
+     EXTRA_FEATURES=$(sed -n "s/^cargo::\{0,1\}rustc-cfg=\(.*\)/--cfg \1/p" target/build/${crateName}.opt | tr '\n' ' ')
+     EXTRA_LINK_ARGS=$(sed -n "s/^cargo::\{0,1\}rustc-link-arg=\(.*\)/-C link-arg=\1/p" target/build/${crateName}.opt | tr '\n' ' ')
+     EXTRA_LINK_ARGS_BINS=$(sed -n "s/^cargo::\{0,1\}rustc-link-arg-bins=\(.*\)/-C link-arg=\1/p" target/build/${crateName}.opt | tr '\n' ' ')
+     EXTRA_LINK_ARGS_LIB=$(sed -n "s/^cargo::\{0,1\}rustc-link-arg-lib=\(.*\)/-C link-arg=\1/p" target/build/${crateName}.opt | tr '\n' ' ')
+     EXTRA_LINK_LIBS=$(sed -n "s/^cargo::\{0,1\}rustc-link-lib=\(.*\)/\1/p" target/build/${crateName}.opt | tr '\n' ' ')
+     EXTRA_LINK_SEARCH=$(sed -n "s/^cargo::\{0,1\}rustc-link-search=\(.*\)/\1/p" target/build/${crateName}.opt | tr '\n' ' ' | sort -u)
 
      # We want to read part of every line that has cargo:rustc-env= prefix and
      # export it as environment variables. This turns out tricky if the lines
@@ -217,14 +220,15 @@ in ''
      #
      _OLDIFS="$IFS"
      IFS=$'\n'
-     for env in $(sed -n "s/^cargo:rustc-env=\(.*\)/\1/p" target/build/${crateName}.opt); do
+     for env in $(sed -n "s/^cargo::\{0,1\}rustc-env=\(.*\)/\1/p" target/build/${crateName}.opt); do
        export "$env"
      done
      IFS="$_OLDIFS"
 
      CRATENAME=$(echo ${crateName} | sed -e "s/\(.*\)-sys$/\U\1/" -e "s/-/_/g")
-     grep -P "^cargo:(?!(rustc-|warning=|rerun-if-changed=|rerun-if-env-changed))" target/build/${crateName}.opt \
-       | awk -F= "/^cargo:/ { sub(/^cargo:/, \"\", \$1); gsub(/-/, \"_\", \$1); print \"export \" toupper(\"DEP_$(echo $CRATENAME)_\" \$1) \"=\" \$2 }" > target/env
+     grep -P "^cargo:(?!:?(rustc-|warning=|rerun-if-changed=|rerun-if-env-changed))" target/build/${crateName}.opt \
+       | awk -F= "/^cargo::metadata=/ {  gsub(/-/, \"_\", \$2); print \"export \" toupper(\"DEP_$(echo $CRATENAME)_\" \$2) \"=\" \"\\\"\"\$3\"\\\"\"; next }
+                  /^cargo:/ { sub(/^cargo::?/, \"\", \$1); gsub(/-/, \"_\", \$1); print \"export \" toupper(\"DEP_$(echo $CRATENAME)_\" \$1) \"=\" \"\\\"\"\$2\"\\\"\"; next }" > target/env
      set -e
   fi
   runHook postConfigure
diff --git a/pkgs/build-support/rust/build-rust-crate/default.nix b/pkgs/build-support/rust/build-rust-crate/default.nix
index 4a7fd114829ad..dfe28cc334b5e 100644
--- a/pkgs/build-support/rust/build-rust-crate/default.nix
+++ b/pkgs/build-support/rust/build-rust-crate/default.nix
@@ -49,7 +49,9 @@ let
           filename =
             if lib.any (x: x == "lib" || x == "rlib") dep.crateType
             then "${dep.metadata}.rlib"
-            else "${dep.metadata}${stdenv.hostPlatform.extensions.sharedLibrary}";
+            # Adjust lib filename for crates of type proc-macro. Proc macros are compiled/run on the build platform architecture.
+            else if (lib.attrByPath [ "procMacro" ] false dep) then "${dep.metadata}${stdenv.buildPlatform.extensions.library}"
+            else "${dep.metadata}${stdenv.hostPlatform.extensions.library}";
         in
         " --extern ${opts}${name}=${dep.lib}/lib/lib${extern}-${filename}"
       )
diff --git a/pkgs/build-support/rust/build-rust-crate/install-crate.nix b/pkgs/build-support/rust/build-rust-crate/install-crate.nix
index f4a4dcdb0d945..7c7c102833d85 100644
--- a/pkgs/build-support/rust/build-rust-crate/install-crate.nix
+++ b/pkgs/build-support/rust/build-rust-crate/install-crate.nix
@@ -41,7 +41,7 @@ if !buildTests then ''
   fi
   if [ -e target/lib ]; then
     find target/lib/ -type f \! -name '*.rlib' \
-      -a \! -name '*${stdenv.hostPlatform.extensions.sharedLibrary}' \
+      -a \! -name '*${stdenv.hostPlatform.extensions.library}' \
       -a \! -name '*.d' \
       -executable \
       -print0 | xargs --no-run-if-empty --null install --target $out/tests;
diff --git a/pkgs/build-support/rust/build-rust-crate/test/default.nix b/pkgs/build-support/rust/build-rust-crate/test/default.nix
index 1ecef4c8e3270..d020031a92f93 100644
--- a/pkgs/build-support/rust/build-rust-crate/test/default.nix
+++ b/pkgs/build-support/rust/build-rust-crate/test/default.nix
@@ -421,6 +421,53 @@ let
         buildDependencies = [ depCrate ];
         dependencies = [ depCrate ];
       };
+      # Support new invocation prefix for build scripts `cargo::`
+      # https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script
+      buildScriptInvocationPrefix = let
+        depCrate = buildRustCrate: mkCrate buildRustCrate {
+          crateName = "bar";
+          src = mkFile "build.rs" ''
+              fn main() {
+                // Old invocation prefix
+                // We likely won't see be mixing these syntaxes in the same build script in the wild.
+                println!("cargo:key_old=value_old");
+
+                // New invocation prefix
+                println!("cargo::metadata=key=value");
+                println!("cargo::metadata=key_complex=complex(value)");
+              }
+          '';
+        };
+      in {
+        crateName = "foo";
+        src = symlinkJoin {
+          name = "build-script-and-main-invocation-prefix";
+          paths = [
+            (mkFile  "src/main.rs" ''
+              const BUILDFOO: &'static str = env!("BUILDFOO");
+
+              #[test]
+              fn build_foo_check() { assert!(BUILDFOO == "yes(check)"); }
+
+              fn main() { }
+            '')
+            (mkFile  "build.rs" ''
+              use std::env;
+              fn main() {
+                assert!(env::var_os("DEP_BAR_KEY_OLD").expect("metadata key 'key_old' not set in dependency") == "value_old");
+                assert!(env::var_os("DEP_BAR_KEY").expect("metadata key 'key' not set in dependency") == "value");
+                assert!(env::var_os("DEP_BAR_KEY_COMPLEX").expect("metadata key 'key_complex' not set in dependency") == "complex(value)");
+
+                println!("cargo::rustc-env=BUILDFOO=yes(check)");
+              }
+            '')
+          ];
+        };
+        buildDependencies = [ (depCrate buildPackages.buildRustCrate) ];
+        dependencies = [ (depCrate buildRustCrate) ];
+        buildTests = true;
+        expectedTestOutputs = [ "test build_foo_check ... ok" ];
+      };
       # Regression test for https://github.com/NixOS/nixpkgs/issues/74071
       # Whenevever a build.rs file is generating files those should not be overlayed onto the actual source dir
       buildRsOutDirOverlay = {
@@ -479,7 +526,7 @@ let
             # `-undefined dynamic_lookup` as otherwise the compilation fails.
             $CC -shared \
               ${lib.optionalString stdenv.isDarwin "-undefined dynamic_lookup"} \
-              -o $out/lib/${name}${stdenv.hostPlatform.extensions.sharedLibrary} ${src}
+              -o $out/lib/${name}${stdenv.hostPlatform.extensions.library} ${src}
           '';
           b = compile "libb" ''
             #include <stdio.h>
diff --git a/pkgs/build-support/rust/build-rust-package/default.nix b/pkgs/build-support/rust/build-rust-package/default.nix
index cd90c68c79303..36e0408cc198a 100644
--- a/pkgs/build-support/rust/build-rust-package/default.nix
+++ b/pkgs/build-support/rust/build-rust-package/default.nix
@@ -156,9 +156,9 @@ stdenv.mkDerivation ((removeAttrs args [ "depsExtraArgs" "cargoUpdateHook" "carg
       # Platforms without host tools from
       # https://doc.rust-lang.org/nightly/rustc/platform-support.html
       "armv7a-darwin"
-      "armv5tel-linux" "armv7a-linux" "m68k-linux" "mipsel-linux"
-      "mips64el-linux" "riscv32-linux"
-      "armv6l-netbsd"
+      "armv5tel-linux" "armv7a-linux" "m68k-linux" "mips-linux"
+      "mips64-linux" "mipsel-linux" "mips64el-linux" "riscv32-linux"
+      "armv6l-netbsd" "mipsel-netbsd" "riscv64-netbsd"
       "x86_64-redox"
       "wasm32-wasi"
     ];
diff --git a/pkgs/build-support/rust/default-crate-overrides.nix b/pkgs/build-support/rust/default-crate-overrides.nix
index 92c71dfc059ce..d8f1bfaf4447b 100644
--- a/pkgs/build-support/rust/default-crate-overrides.nix
+++ b/pkgs/build-support/rust/default-crate-overrides.nix
@@ -218,6 +218,10 @@ in
     buildInputs = [ openssl ];
   };
 
+  opentelemetry-proto = attrs: {
+    nativeBuildInputs = [ protobuf ];
+  };
+
   pam-sys = attr: {
     buildInputs = [ linux-pam ];
   };
@@ -236,6 +240,10 @@ in
     nativeBuildInputs = [ protobuf ];
   };
 
+  prost-wkt-types = attr: {
+    nativeBuildInputs = [ protobuf ];
+  };
+
   rdkafka-sys = attr: {
     nativeBuildInputs = [ pkg-config ];
     buildInputs = [ rdkafka ];
@@ -299,6 +307,10 @@ in
     buildInputs = [ libsodium ];
   };
 
+  tonic-reflection = attrs: {
+    nativeBuildInputs = [ protobuf ];
+  };
+
   xcb = attrs: {
     buildInputs = [ python3 ];
   };
diff --git a/pkgs/build-support/rust/hooks/cargo-build-hook.sh b/pkgs/build-support/rust/hooks/cargo-build-hook.sh
index ed982c7ff30a3..26dde914f22aa 100644
--- a/pkgs/build-support/rust/hooks/cargo-build-hook.sh
+++ b/pkgs/build-support/rust/hooks/cargo-build-hook.sh
@@ -25,14 +25,21 @@ cargoBuildHook() {
     fi
 
     if [ -n "${cargoBuildFeatures-}" ]; then
-        cargoBuildFeaturesFlag="--features=${cargoBuildFeatures// /,}"
+        if [ -n "$__structuredAttrs" ]; then
+            OLDIFS="$IFS"
+            IFS=','; cargoBuildFeaturesFlag="--features=${cargoBuildFeatures[*]}"
+            IFS="$OLDIFS"
+            unset OLDIFS
+        else
+            cargoBuildFeaturesFlag="--features=${cargoBuildFeatures// /,}"
+        fi
     fi
 
     (
     set -x
     @setEnv@ cargo build -j $NIX_BUILD_CORES \
         --target @rustHostPlatformSpec@ \
-        --frozen \
+        --offline \
         ${cargoBuildProfileFlag} \
         ${cargoBuildNoDefaultFeaturesFlag} \
         ${cargoBuildFeaturesFlag} \
diff --git a/pkgs/build-support/rust/hooks/cargo-check-hook.sh b/pkgs/build-support/rust/hooks/cargo-check-hook.sh
index 971a140ec178f..96b87dbf15b45 100644
--- a/pkgs/build-support/rust/hooks/cargo-check-hook.sh
+++ b/pkgs/build-support/rust/hooks/cargo-check-hook.sh
@@ -29,7 +29,7 @@ cargoCheckHook() {
     fi
 
     argstr="${cargoCheckProfileFlag} ${cargoCheckNoDefaultFeaturesFlag} ${cargoCheckFeaturesFlag}
-        --target @rustHostPlatformSpec@ --frozen ${cargoTestFlags}"
+        --target @rustHostPlatformSpec@ --offline ${cargoTestFlags}"
 
     (
         set -x
diff --git a/pkgs/build-support/rust/hooks/cargo-nextest-hook.sh b/pkgs/build-support/rust/hooks/cargo-nextest-hook.sh
index 29ba18a6a1e3f..16d32513a0d01 100644
--- a/pkgs/build-support/rust/hooks/cargo-nextest-hook.sh
+++ b/pkgs/build-support/rust/hooks/cargo-nextest-hook.sh
@@ -29,7 +29,7 @@ cargoNextestHook() {
     fi
 
     argstr="${cargoCheckProfileFlag} ${cargoCheckNoDefaultFeaturesFlag} ${cargoCheckFeaturesFlag}
-        --target @rustHostPlatformSpec@ --frozen ${cargoTestFlags}"
+        --target @rustHostPlatformSpec@ --offline ${cargoTestFlags}"
 
     (
         set -x
diff --git a/pkgs/build-support/rust/hooks/maturin-build-hook.sh b/pkgs/build-support/rust/hooks/maturin-build-hook.sh
index 028441d18160e..b3cc1ced79647 100644
--- a/pkgs/build-support/rust/hooks/maturin-build-hook.sh
+++ b/pkgs/build-support/rust/hooks/maturin-build-hook.sh
@@ -11,7 +11,7 @@ maturinBuildHook() {
     set -x
     @setEnv@ maturin build \
         --jobs=$NIX_BUILD_CORES \
-        --frozen \
+        --offline \
         --target @rustTargetPlatformSpec@ \
         --manylinux off \
         --strip \
diff --git a/pkgs/build-support/rust/replace-workspace-values.py b/pkgs/build-support/rust/replace-workspace-values.py
index 2b88f1fa79bbe..003023ff2560a 100644
--- a/pkgs/build-support/rust/replace-workspace-values.py
+++ b/pkgs/build-support/rust/replace-workspace-values.py
@@ -15,6 +15,9 @@ def load_file(path: str) -> dict[str, Any]:
         return tomli.load(f)
 
 
+# This replicates the dependency merging logic from Cargo.
+# See `inner_dependency_inherit_with`:
+# https://github.com/rust-lang/cargo/blob/4de0094ac78743d2c8ff682489e35c8a7cafe8e4/src/cargo/util/toml/mod.rs#L982
 def replace_key(
     workspace_manifest: dict[str, Any], table: dict[str, Any], section: str, key: str
 ) -> bool:
@@ -25,28 +28,37 @@ def replace_key(
     ):
         print("replacing " + key)
 
-        replaced = table[key]
-        del replaced["workspace"]
+        local_dep = table[key]
+        del local_dep["workspace"]
 
-        workspace_copy = workspace_manifest[section][key]
+        workspace_dep = workspace_manifest[section][key]
 
         if section == "dependencies":
-            crate_features = replaced.get("features")
+            if isinstance(workspace_dep, str):
+                workspace_dep = {"version": workspace_dep}
 
-            if type(workspace_copy) is str:
-                replaced["version"] = workspace_copy
-            else:
-                replaced.update(workspace_copy)
+            final: dict[str, Any] = workspace_dep.copy()
 
-                merged_features = (crate_features or []) + (
-                    workspace_copy.get("features") or []
-                )
+            merged_features = local_dep.pop("features", []) + workspace_dep.get("features", [])
+            if merged_features:
+                final["features"] = merged_features
 
-                if len(merged_features) > 0:
-                    # Dictionaries are guaranteed to be ordered (https://stackoverflow.com/a/7961425)
-                    replaced["features"] = list(dict.fromkeys(merged_features))
+            local_default_features = local_dep.pop("default-features", None)
+            workspace_default_features = workspace_dep.get("default-features")
+
+            if not workspace_default_features and local_default_features:
+                final["default-features"] = True
+
+            optional = local_dep.pop("optional", False)
+            if optional:
+                final["optional"] = True
+
+            if local_dep:
+                raise Exception(f"Unhandled keys in inherited dependency {key}: {local_dep}")
+
+            table[key] = final
         elif section == "package":
-            table[key] = replaced = workspace_copy
+            table[key] = workspace_dep
 
         return True
 
diff --git a/pkgs/build-support/setup-hooks/strip.sh b/pkgs/build-support/setup-hooks/strip.sh
index ce41e6ea0562a..49a350af1fa5c 100644
--- a/pkgs/build-support/setup-hooks/strip.sh
+++ b/pkgs/build-support/setup-hooks/strip.sh
@@ -74,13 +74,17 @@ stripDirs() {
         echo "stripping (with command $cmd and flags $stripFlags) in $paths"
         local striperr
         striperr="$(mktemp --tmpdir="$TMPDIR" 'striperr.XXXXXX')"
-        # Do not strip lib/debug. This is a directory used by setup-hooks/separate-debug-info.sh.
-        find $paths -type f "${excludeFlags[@]}" -a '!' -path "$prefix/lib/debug/*" -print0 |
-            # Make sure we process files under symlinks only once. Otherwise
-            # 'strip` can corrupt files when writes to them in parallel:
-            #   https://github.com/NixOS/nixpkgs/issues/246147#issuecomment-1657072039
-            xargs -r -0 -n1 -- realpath -z | sort -u -z |
+        # Make sure we process files only once. `strip`ping the same file through different
+        # links in parallel can corrupt it:
+        #   https://github.com/NixOS/nixpkgs/issues/246147#issuecomment-1657072039
 
+        # Do not strip lib/debug. This is a directory used by setup-hooks/separate-debug-info.sh.
+        # Print out each file's device and inode (which will be the same if two files are hardlinked
+        # or are the same file found through different symlinks), followed by its path...
+        find $paths -type f "${excludeFlags[@]}" -a '!' -path "$prefix/lib/debug/*" -printf '%D-%i,%p\0' |
+            # ... sort/uniq by device/inode, then cut them out and keep the path, ...
+            sort -t, -k1,1 -u -z | cut -d, -f2- -z |
+            # and finally strip each unique path in parallel.
             xargs -r -0 -n1 -P "$NIX_BUILD_CORES" -- $cmd $stripFlags 2>"$striperr" || exit_code=$?
         # xargs exits with status code 123 if some but not all of the
         # processes fail. We don't care if some of the files couldn't
diff --git a/pkgs/build-support/setup-hooks/wrap-gapps-hook/default.nix b/pkgs/build-support/setup-hooks/wrap-gapps-hook/default.nix
index 3c5199be31322..69f9f3b145d7c 100644
--- a/pkgs/build-support/setup-hooks/wrap-gapps-hook/default.nix
+++ b/pkgs/build-support/setup-hooks/wrap-gapps-hook/default.nix
@@ -3,12 +3,12 @@
 , makeSetupHook
 , makeWrapper
 , gobject-introspection
-, isGraphical ? true
+, isGraphical ? false
 , gtk3
 , librsvg
 , dconf
 , callPackage
-, wrapGAppsHook
+, wrapGAppsHook3
 , targetPackages
 }:
 
@@ -24,9 +24,9 @@ makeSetupHook {
     librsvg
   ];
 
-  # depsTargetTargetPropagated will essentially be buildInputs when wrapGAppsHook is placed into nativeBuildInputs
+  # depsTargetTargetPropagated will essentially be buildInputs when wrapGAppsHook3 is placed into nativeBuildInputs
   # the librsvg and gtk3 above should be removed but kept to not break anything that implicitly depended on its binaries
-  depsTargetTargetPropagated = assert (lib.assertMsg (!targetPackages ? raw) "wrapGAppsHook must be in nativeBuildInputs"); lib.optionals isGraphical [
+  depsTargetTargetPropagated = assert (lib.assertMsg (!targetPackages ? raw) "wrapGAppsHook3 must be in nativeBuildInputs"); lib.optionals isGraphical [
     # librsvg provides a module for gdk-pixbuf to allow rendering
     # SVG icons. Most icon themes are SVG-based and so are some
     # graphics in GTK (e.g. cross for closing window in window title bar)
@@ -59,7 +59,7 @@ makeSetupHook {
         src = sample-project;
 
         strictDeps = true;
-        nativeBuildInputs = [ wrapGAppsHook ];
+        nativeBuildInputs = [ wrapGAppsHook3 ];
 
         installFlags = [ "bin-foo" "libexec-bar" ];
       };
@@ -103,7 +103,7 @@ makeSetupHook {
         strictDeps = true;
         nativeBuildInputs = [
           gobject-introspection
-          wrapGAppsHook
+          wrapGAppsHook3
         ];
 
         buildInputs = [
@@ -150,7 +150,7 @@ makeSetupHook {
         strictDeps = true;
         nativeBuildInputs = [
           gobject-introspection
-          wrapGAppsHook
+          wrapGAppsHook3
         ];
 
         buildInputs = [
@@ -181,7 +181,7 @@ makeSetupHook {
         strictDeps = true;
         nativeBuildInputs = [
           gobject-introspection
-          wrapGAppsHook
+          wrapGAppsHook3
         ];
 
         installFlags = [ "typelib-Cow" "bin-foo" "libexec-bar" ];
diff --git a/pkgs/build-support/singularity-tools/default.nix b/pkgs/build-support/singularity-tools/default.nix
index c9e53a4cb706f..cd10a9960421b 100644
--- a/pkgs/build-support/singularity-tools/default.nix
+++ b/pkgs/build-support/singularity-tools/default.nix
@@ -45,7 +45,7 @@ rec {
     , diskSize ? 1024
     , runScript ? "#!${stdenv.shell}\nexec /bin/sh"
     , runAsRoot ? null
-    , memSize ? 512
+    , memSize ? 1024
     , singularity ? defaultSingularity
     }:
     let
diff --git a/pkgs/build-support/src-only/default.nix b/pkgs/build-support/src-only/default.nix
index 2b0db0e267aa7..cd8572629cad8 100644
--- a/pkgs/build-support/src-only/default.nix
+++ b/pkgs/build-support/src-only/default.nix
@@ -13,7 +13,7 @@ let
 in
 stdenv.mkDerivation (args // {
   name = "${name}-source";
-  installPhase = "cp -r . $out";
+  installPhase = "cp -pr --reflink=auto -- . $out";
   outputs = [ "out" ];
   separateDebugInfo = false;
   dontUnpack = false;
diff --git a/pkgs/build-support/testers/default.nix b/pkgs/build-support/testers/default.nix
index 362622d9b7ee8..dbf9a6d6cb05b 100644
--- a/pkgs/build-support/testers/default.nix
+++ b/pkgs/build-support/testers/default.nix
@@ -1,6 +1,10 @@
 { pkgs, pkgsLinux, buildPackages, lib, callPackage, runCommand, stdenv, substituteAll, testers }:
 # Documentation is in doc/builders/testers.chapter.md
 {
+  # See https://nixos.org/manual/nixpkgs/unstable/#tester-lycheeLinkCheck
+  # or doc/builders/testers.chapter.md
+  inherit (callPackage ./lychee.nix {}) lycheeLinkCheck;
+
   # See https://nixos.org/manual/nixpkgs/unstable/#tester-testBuildFailure
   # or doc/builders/testers.chapter.md
   testBuildFailure = drv: drv.overrideAttrs (orig: {
diff --git a/pkgs/build-support/testers/hasPkgConfigModules/tester.nix b/pkgs/build-support/testers/hasPkgConfigModules/tester.nix
index bbcc4f0c0f710..3d4ded812f75e 100644
--- a/pkgs/build-support/testers/hasPkgConfigModules/tester.nix
+++ b/pkgs/build-support/testers/hasPkgConfigModules/tester.nix
@@ -5,14 +5,16 @@
 { package,
   moduleNames ? package.meta.pkgConfigModules,
   testName ? "check-pkg-config-${lib.concatStringsSep "-" moduleNames}",
+  version ? package.version or null,
+  versionCheck ? false,
 }:
 
 runCommand testName {
     nativeBuildInputs = [ pkg-config ];
     buildInputs = [ package ];
-    inherit moduleNames;
+    inherit moduleNames version versionCheck;
     meta = {
-      description = "Test whether ${package.name} exposes pkg-config modules ${lib.concatStringsSep ", " moduleNames}.";
+      description = "Test whether ${package.name} exposes pkg-config modules ${lib.concatStringsSep ", " moduleNames}";
     }
     # Make sure licensing info etc is preserved, as this is a concern for e.g. cache.nixos.org,
     # as hydra can't check this meta info in dependencies.
@@ -31,20 +33,38 @@ runCommand testName {
         package.meta;
   } ''
     touch "$out"
+    notFound=0
+    versionMismatch=0
     for moduleName in $moduleNames; do
       echo "checking pkg-config module $moduleName in $buildInputs"
       set +e
-      version="$($PKG_CONFIG --modversion $moduleName)"
+      moduleVersion="$($PKG_CONFIG --modversion $moduleName)"
       r=$?
       set -e
       if [[ $r = 0 ]]; then
-        echo "✅ pkg-config module $moduleName exists and has version $version"
+        if [[ "$moduleVersion" == "$version" ]]; then
+          echo "✅ pkg-config module $moduleName exists and has version $moduleVersion"
+        else
+          echo "${if versionCheck then "❌" else "ℹ️"} pkg-config module $moduleName exists at version $moduleVersion != $version (drv version)"
+          ((versionMismatch+=1))
+        fi
         printf '%s\t%s\n' "$moduleName" "$version" >> "$out"
       else
-        echo "These modules were available in the input propagation closure:"
-        $PKG_CONFIG --list-all
         echo "❌ pkg-config module $moduleName was not found"
-        false
+        ((notFound+=1))
       fi
     done
+
+    if [[ $notFound -eq 0 ]] && ([[ $versionMismatch -eq 0 ]] || [[ -z "$versionCheck" ]]); then
+      exit 0
+    fi
+    if [[ $notFound -ne 0 ]]; then
+      echo "$notFound modules not found"
+      echo "These modules were available in the input propagation closure:"
+      $PKG_CONFIG --list-all
+    fi
+    if [[ $versionMismatch -ne 0 ]]; then
+      echo "$versionMismatch version mismatches"
+    fi
+    exit 1
   ''
diff --git a/pkgs/build-support/testers/hasPkgConfigModules/tests.nix b/pkgs/build-support/testers/hasPkgConfigModules/tests.nix
index 96569498fb152..adad935079849 100644
--- a/pkgs/build-support/testers/hasPkgConfigModules/tests.nix
+++ b/pkgs/build-support/testers/hasPkgConfigModules/tests.nix
@@ -1,9 +1,25 @@
 # cd nixpkgs
-# nix-build -A tests.testers.hasPkgConfigModule
-{ lib, testers, zlib, openssl, runCommand }:
+# nix-build -A tests.testers.hasPkgConfigModules
+{ lib, testers, miniz, zlib, openssl, runCommand }:
 
 lib.recurseIntoAttrs {
 
+  miniz-versions-match = testers.hasPkgConfigModules {
+    package = miniz;
+    versionCheck = true;
+  };
+
+  miniz-versions-mismatch = testers.testBuildFailure (testers.hasPkgConfigModules {
+    package = miniz;
+    version = "1.2.3";  # Deliberately-incorrect version number
+    versionCheck = true;
+  });
+
+  miniz-no-versionCheck = testers.hasPkgConfigModules {
+    package = miniz;
+    version = "1.2.3";  # Deliberately-incorrect version number
+  };
+
   zlib-has-zlib = testers.hasPkgConfigModules {
     package = zlib;
     moduleNames = [ "zlib" ];
diff --git a/pkgs/build-support/testers/lychee.nix b/pkgs/build-support/testers/lychee.nix
new file mode 100644
index 0000000000000..80088dc4f9638
--- /dev/null
+++ b/pkgs/build-support/testers/lychee.nix
@@ -0,0 +1,69 @@
+deps@{ formats, lib, lychee, stdenv, writeShellApplication }:
+let
+  inherit (lib) mapAttrsToList throwIf;
+  inherit (lib.strings) hasInfix hasPrefix escapeNixString;
+
+  toURL = v:
+    let s = "${v}";
+    in if hasPrefix builtins.storeDir s
+    then # lychee requires that paths on the file system are prefixed with file://
+      "file://${s}"
+    else s;
+
+  withCheckedName = name:
+    throwIf (hasInfix " " name) ''
+      lycheeLinkCheck: remap patterns must not contain spaces.
+      A space marks the end of the regex in lychee.toml.
+
+      Please change attribute name 'remap.${escapeNixString name}'
+    '';
+
+  # See https://nixos.org/manual/nixpkgs/unstable/#tester-lycheeLinkCheck
+  # or doc/builders/testers.chapter.md
+  lycheeLinkCheck = {
+    site,
+    remap ? { },
+    lychee ? deps.lychee,
+    extraConfig ? { },
+  }:
+    stdenv.mkDerivation (finalAttrs: {
+      name = "lychee-link-check";
+      inherit site;
+      nativeBuildInputs = [ finalAttrs.passthru.lychee ];
+      configFile = (formats.toml {}).generate "lychee.toml" finalAttrs.passthru.config;
+
+      # These can be overriden with overrideAttrs if needed.
+      passthru = {
+        inherit lychee remap;
+        config = {
+          include_fragments = true;
+        } // lib.optionalAttrs (finalAttrs.passthru.remap != { }) {
+          remap =
+            mapAttrsToList
+              (name: value: withCheckedName name "${name} ${toURL value}")
+              finalAttrs.passthru.remap;
+        } // extraConfig;
+        online = writeShellApplication {
+          name = "run-lychee-online";
+          runtimeInputs = [ finalAttrs.passthru.lychee ];
+          # Comment out to run shellcheck:
+          checkPhase = "";
+          text = ''
+            site=${finalAttrs.site}
+            configFile=${finalAttrs.configFile}
+            echo Checking links on $site
+            exec lychee --config $configFile $site "$@"
+          '';
+        };
+      };
+      buildCommand = ''
+        echo Checking internal links on $site
+        lychee --offline --config $configFile $site
+        touch $out
+      '';
+    });
+
+in
+{
+  inherit lycheeLinkCheck;
+}
diff --git a/pkgs/build-support/testers/test/default.nix b/pkgs/build-support/testers/test/default.nix
index da67711156bed..a815fe63e416e 100644
--- a/pkgs/build-support/testers/test/default.nix
+++ b/pkgs/build-support/testers/test/default.nix
@@ -12,6 +12,8 @@ let
 
 in
 lib.recurseIntoAttrs {
+  lycheeLinkCheck = lib.recurseIntoAttrs pkgs.lychee.tests;
+
   hasPkgConfigModules = pkgs.callPackage ../hasPkgConfigModules/tests.nix { };
 
   runNixOSTest-example = pkgs-with-overlay.testers.runNixOSTest ({ lib, ... }: {
diff --git a/pkgs/build-support/trivial-builders/test/references/default.nix b/pkgs/build-support/trivial-builders/test/references/default.nix
index 928cc1d9461f0..85df57ea210c2 100644
--- a/pkgs/build-support/trivial-builders/test/references/default.nix
+++ b/pkgs/build-support/trivial-builders/test/references/default.nix
@@ -93,21 +93,27 @@ let
     };
   });
 in
-testers.nixosTest {
-  name = "nixpkgs-trivial-builders";
-  nodes.machine = { ... }: {
+testers.runNixOSTest ({ config, lib, ... }:
+let
+  # Use the testScriptBin from guest pkgs.
+  # The attribute path to access the guest version of testScriptBin is
+  # tests.trivial-builders.references.config.node.pkgs.tests.trivial-builders.references.testScriptBin
+  # which is why passthru.guestTestScriptBin is provided.
+  guestTestScriptBin = config.node.pkgs.tests.trivial-builders.references.testScriptBin;
+in
+{
+  name = "nixpkgs-trivial-builders-references";
+  nodes.machine = { config, lib, pkgs, ... }: {
     virtualisation.writableStore = true;
 
     # Test runs without network, so we don't substitute and prepare our deps
     nix.settings.substituters = lib.mkForce [ ];
-    environment.etc."pre-built-paths".source = writeText "pre-built-paths" (
-      builtins.toJSON [ testScriptBin ]
-    );
+    system.extraDependencies = [ guestTestScriptBin ];
   };
   testScript =
     ''
       machine.succeed("""
-        ${lib.getExe testScriptBin} 2>/dev/console
+        ${lib.getExe guestTestScriptBin} 2>/dev/console
       """)
     '';
   passthru = {
@@ -118,6 +124,7 @@ testers.nixosTest {
       samples
       testScriptBin
       ;
+    inherit guestTestScriptBin;
   };
   meta = {
     maintainers = with lib.maintainers; [
@@ -125,4 +132,4 @@ testers.nixosTest {
       ShamrockLee
     ];
   };
-}
+})
diff --git a/pkgs/build-support/vm/default.nix b/pkgs/build-support/vm/default.nix
index 56375851783fb..871f81bb5d69f 100644
--- a/pkgs/build-support/vm/default.nix
+++ b/pkgs/build-support/vm/default.nix
@@ -40,10 +40,14 @@ rec {
         ${pkgs.stdenv.cc.libc}/lib/libc.so.* \
         ${pkgs.stdenv.cc.libc}/lib/libm.so.* \
         ${pkgs.stdenv.cc.libc}/lib/libresolv.so.* \
+        ${pkgs.stdenv.cc.libc}/lib/libpthread.so.* \
+        ${pkgs.zstd.out}/lib/libzstd.so.* \
+        ${pkgs.xz.out}/lib/liblzma.so.* \
         $out/lib
 
       # Copy BusyBox.
       cp -pd ${pkgs.busybox}/bin/* $out/bin
+      cp -pd ${pkgs.kmod}/bin/* $out/bin
 
       # Run patchelf to make the programs refer to the copied libraries.
       for i in $out/bin/* $out/lib/*; do if ! test -L $i; then nuke-refs $i; fi; done
@@ -54,6 +58,11 @@ rec {
               patchelf --set-interpreter $out/lib/ld-*.so.? --set-rpath $out/lib $i || true
           fi
       done
+
+      find $out/lib -type f \! -name 'ld*.so.?' | while read i; do
+        echo "patching $i..."
+        patchelf --set-rpath $out/lib $i
+      done
     ''; # */
 
 
diff --git a/pkgs/build-support/writers/scripts.nix b/pkgs/build-support/writers/scripts.nix
index 06d763ca9d6af..93fac09c07ba5 100644
--- a/pkgs/build-support/writers/scripts.nix
+++ b/pkgs/build-support/writers/scripts.nix
@@ -635,7 +635,7 @@ rec {
 
     nuget-source = mkNugetSource {
       name = "${fname}-nuget-source";
-      description = "A Nuget source with the dependencies for ${fname}";
+      description = "Nuget source with the dependencies for ${fname}";
       deps = [ _nugetDeps ];
     };