{
  lib,
  stdenv,
  fetchFromGitHub,
  buildPackages,
  ld64,
  llvm,
  memstreamHook,
  meson,
  ninja,
  openssl,
  xar,
  gitUpdater,
}:

let
  # The targetPrefix is prepended to binary names to allow multiple binuntils on the PATH to both be usable.
  targetPrefix = lib.optionalString (
    stdenv.targetPlatform != stdenv.hostPlatform
  ) "${stdenv.targetPlatform.config}-";

  # First version with all the required files
  xnu = fetchFromGitHub {
    name = "xnu-src";
    owner = "apple-oss-distributions";
    repo = "xnu";
    rev = "xnu-7195.50.7.100.1";
    hash = "sha256-uHmAOm6k9ZXWfyqHiDSpm+tZqUbERlr6rXSJ4xNACkM=";
  };
in
stdenv.mkDerivation (finalAttrs: {
  pname = "${targetPrefix}cctools";
  version = "1010.6";

  outputs = [
    "out"
    "dev"
    "man"
    "gas"
    "libtool"
  ];

  src = fetchFromGitHub {
    owner = "apple-oss-distributions";
    repo = "cctools";
    rev = "cctools-${finalAttrs.version}";
    hash = "sha256-JiKCP6U+xxR4mk4TXWv/mEo9Idg+QQqUYmB/EeRksCE=";
  };

  xcodeHash = "sha256-5RBbGrz1UKV0wt2Uk7RIHdfgWH8sgw/jy7hfTVrtVuM=";

  postUnpack = ''
    unpackFile '${xnu}'

    # Verify that the Xcode project has not changed unexpectedly.
    hashType=$(echo $xcodeHash | cut -d- -f1)
    expectedHash=$(echo $xcodeHash | cut -d- -f2)
    hash=$(openssl "$hashType" -binary "$sourceRoot/cctools.xcodeproj/project.pbxproj" | base64)

    if [ "$hash" != "$expectedHash" ]; then
      echo 'error: hash mismatch in cctools.xcodeproj/project.pbxproj'
      echo "        specified: $xcodeHash"
      echo "           got:    $hashType-$hash"
      echo
      echo 'Upstream Xcode project has changed. Update `meson.build` with any changes, then update `xcodeHash`.'
      echo 'Use `nix-hash --flat --sri --type sha256 cctools.xcodeproj/project.pbxproj` to regenerate it.'
      exit 1
    fi
  '';

  patches = [
    # Fix compile errors in redo_prebinding.c
    ./0001-Fix-build-issues-with-misc-redo_prebinding.c.patch
    # Use libcd_is_blob_a_linker_signature as defined in the libcodedirectory.h header
    ./0002-Rely-on-libcd_is_blob_a_linker_signature.patch
    # cctools uses availability checks for `utimensat`, but it checks the wrong version.
    # Also, provide a definition to avoid implicit function definition errors.
    ./0003-Fix-utimensat-compatability-with-the-10.12-SDK.patch
    # Use the nixpkgs clang’s path as the prefix.
    ./0004-Use-nixpkgs-clang-with-the-assembler-driver.patch
    # Make sure cctools can find ld64 in the store
    ./0005-Find-ld64-in-the-store.patch
    # `ranlib` is a symlink to `libtool`. Make sure its detection works when it is used in cross-compilation.
    ./0006-Support-target-prefixes-in-ranlib-detection.patch
  ];

  postPatch = ''
    substitute ${./meson.build} meson.build \
      --subst-var version
    cp ${./meson.options} meson.options

    # Make sure as’s clang driver uses clang from nixpkgs and finds the drivers in the store.
    substituteInPlace as/driver.c \
      --subst-var-by clang-unwrapped '${lib.getBin buildPackages.clang.cc}' \
      --subst-var-by gas '${placeholder "gas"}'

    # Need to set the path to make sure cctools can find ld64 in the store.
    substituteInPlace libstuff/execute.c \
      --subst-var-by ld64_path '${lib.getBin ld64}/bin/ld'

    # Set the target prefix for `ranlib`
    substituteInPlace misc/libtool.c \
      --subst-var-by targetPrefix '${targetPrefix}'

    # The version of this file distributed with cctools defines several CPU types missing from the 10.12 SDK.
    ln -s machine-cctools.h include/mach/machine.h

    # Use libxar from nixpkgs
    for cctool_src in misc/nm.c otool/print_bitcode.c; do
      substituteInPlace $cctool_src \
        --replace-fail 'makestr(prefix, "../lib/libxar.dylib", NULL)' '"${lib.getLib xar}/lib/libxar.dylib"' \
        --replace-fail '/usr/lib/libxar.dylib' '${lib.getLib xar}/lib/libxar.dylib'
    done

    # Use libLTO.dylib from nixpkgs LLVM
    substituteInPlace libstuff/llvm.c \
      --replace-fail 'getenv("LIBLTO_PATH")' '"${lib.getLib llvm}/lib/libLTO.dylib"'

    cp ../xnu-src/EXTERNAL_HEADERS/mach-o/fixup-chains.h include/mach-o/fixup-chains.h
  '';

  strictDeps = true;

  nativeBuildInputs = [
    meson
    ninja
    openssl
  ];

  buildInputs = [
    ld64
    llvm
  ] ++ lib.optionals (stdenv.hostPlatform.isDarwin && stdenv.hostPlatform.isx86_64) [ memstreamHook ];

  mesonBuildType = "release";

  mesonFlags = [
    (lib.mesonOption "b_ndebug" "if-release")
  ] ++ lib.optionals (targetPrefix != "") [ (lib.mesonOption "target_prefix" targetPrefix) ];

  postInstall = ''
    ln -s ${targetPrefix}libtool "$out/bin/${targetPrefix}ranlib"
    ln -s nm-classic.1 "''${!outputMan}/share/man/man1/nm.1"
    ln -s otool-classic.1 "''${!outputMan}/share/man/man1/otool.1"
    ln -s size-classic.1 "''${!outputMan}/share/man/man1/size.1"

    # Move GNU as to its own output to prevent it from being used accidentally.
    moveToOutput bin/gas "$gas"
    moveToOutput libexec "$gas"
    for arch in arm i386 x86_64; do
      mv "$gas/libexec/as/$arch/as-$arch" "$gas/libexec/as/$arch/as"
    done

    # Move libtool to its own output to allow packages to add it without pulling in all of cctools
    moveToOutput bin/${targetPrefix}libtool "$libtool"
    ln -s "$libtool/bin/${targetPrefix}libtool" "$out/bin/${targetPrefix}libtool"
  '';

  __structuredAttrs = true;

  passthru = {
    inherit targetPrefix;
    updateScript = gitUpdater { rev-prefix = "cctools-"; };
  };

  meta = {
    description = "The classic linker for Darwin";
    homepage = "https://opensource.apple.com/releases/";
    license = with lib.licenses; [
      apple-psl20
      gpl2 # GNU as
    ];
    maintainers = lib.teams.darwin.members;
    platforms = lib.platforms.darwin;
  };
})
