{
  lib,
  stdenvNoCC,
  cctools,
  clang-unwrapped,
  ld64,
  llvm,
  llvm-manpages,
  makeWrapper,
  enableManpages ? stdenvNoCC.targetPlatform == stdenvNoCC.hostPlatform,
}:

let
  inherit (stdenvNoCC) targetPlatform hostPlatform;
  targetPrefix = lib.optionalString (targetPlatform != hostPlatform) "${targetPlatform.config}-";

  llvm_cmds = [
    "addr2line"
    "ar"
    "c++filt"
    "dwarfdump"
    "dsymutil"
    "nm"
    "objcopy"
    "objdump"
    "otool"
    "size"
    "strings"
    "strip"
  ];

  cctools_cmds = [
    "codesign_allocate"
    "gprof"
    "ranlib"
    # Use the cctools versions because the LLVM ones can crash or fail when the cctools ones don’t.
    # Revisit when LLVM is updated to LLVM 18 on Darwin.
    "lipo"
    "install_name_tool"
  ];

  linkManPages =
    pkg: source: target:
    lib.optionalString enableManpages ''
      sourcePath=${pkg}/share/man/man1/${source}.1.gz
      targetPath=''${!outputMan}/share/man/man1/${target}.1.gz

      if [ -f "$sourcePath" ]; then
        mkdir -p "$(dirname "$targetPath")"
        ln -s "$sourcePath" "$targetPath"
      fi
    '';
in
stdenvNoCC.mkDerivation {
  pname = "${targetPrefix}cctools-binutils-darwin";
  inherit (cctools) version;

  outputs = [ "out" ] ++ lib.optional enableManpages "man";

  strictDeps = true;

  nativeBuildInputs = [ makeWrapper ];

  buildCommand = ''
    mkdir -p $out/bin $out/include

    for tool in ${toString llvm_cmds}; do
      # Translate between LLVM and traditional tool names (e.g., `c++filt` versus `cxxfilt`).
      cctoolsTool=''${tool//-/_}
      llvmTool=''${tool//++/xx}

      # Some tools aren’t prefixed (like `dsymutil`).
      llvmPath="${lib.getBin llvm}/bin"
      if [ -e "$llvmPath/llvm-$llvmTool" ]; then
        llvmTool=llvm-$llvmTool
      elif [ -e "$llvmPath/${targetPrefix}$llvmTool" ]; then
        llvmTool=${targetPrefix}$llvmTool
      fi

      # Not all tools are included in the bootstrap tools. Don’t link them if they don’t exist.
      if [ -e "$llvmPath/$llvmTool" ]; then
        ln -s "$llvmPath/$llvmTool" "$out/bin/${targetPrefix}$cctoolsTool"
      fi
      ${linkManPages llvm-manpages "$llvmTool" "$cctoolsTool"}
    done

    for tool in ${toString cctools_cmds}; do
      toolsrc="${lib.getBin cctools}/bin/${targetPrefix}$tool"
      if [ -e "$toolsrc" ]; then
        ln -s "${lib.getBin cctools}/bin/${targetPrefix}$tool" "$out/bin/${targetPrefix}$tool"
      fi
      ${linkManPages (lib.getMan cctools) "$tool" "$tool"}
    done
    ${
      # These are unprefixed because some tools expect to invoke them without it when cross-compiling to Darwin:
      # - clang needs `dsymutil` when building with debug information;
      # - meson needs `lipo` when cross-compiling to Darwin; and
      # - meson also needs `install_name_tool` and `otool` when performing rpath cleanup on installation.
      lib.optionalString (targetPrefix != "") ''
        for bintool in dsymutil install_name_tool lipo otool; do
          ln -s "$out/bin/${targetPrefix}$bintool" "$out/bin/$bintool"
        done
      ''
    }
    # Use the clang-integrated assembler. `as` in cctools is deprecated upstream and no longer built in nixpkgs.
    makeWrapper "${lib.getBin clang-unwrapped}/bin/clang" "$out/bin/${targetPrefix}as" \
      --add-flags "-x assembler -integrated-as -c"

    ln -s '${lib.getBin ld64}/bin/${targetPrefix}ld' "$out/bin/${targetPrefix}ld"
    ${linkManPages (lib.getMan ld64) "ld" "ld"}
    ${linkManPages (lib.getMan ld64) "ld-classic" "ld-classic"}
    ${linkManPages (lib.getMan ld64) "ld64" "ld64"}
  '';

  __structuredAttrs = true;

  passthru = {
    inherit cctools_cmds llvm_cmds targetPrefix;
    isCCTools = true; # The fact ld64 is used instead of lld is why this isn’t `isLLVM`.
  };

  meta = {
    maintainers = with lib.maintainers; [ reckenrode ];
    priority = 10;
  };
}
