{ lib, stdenv, makeWrapper, writeText, writeTextFile, runCommand, callPackage
, CoreServices, ImageIO, CoreGraphics
, xcodePlatform ? stdenv.targetPlatform.xcodePlatform or "MacOSX"
, xcodeVer ? stdenv.targetPlatform.xcodeVer or "9.4.1"
, sdkVer ? stdenv.targetPlatform.darwinSdkVersion or "10.12"
, productBuildVer ? null
}:

let

  toolchainName = "com.apple.dt.toolchain.XcodeDefault";
  sdkName = "${xcodePlatform}${sdkVer}";
  xcrunSdkName = lib.toLower xcodePlatform;

  # TODO: expose MACOSX_DEPLOYMENT_TARGET in nix so we can use it here.
  sdkBuildVersion = "17E189";
  xcodeSelectVersion = "2349";

  xcbuild = callPackage ./default.nix {
    inherit CoreServices ImageIO CoreGraphics stdenv;
  };

  toolchains = callPackage ./toolchains.nix {
    inherit toolchainName stdenv;
  };

  sdks = callPackage ./sdks.nix {
    inherit toolchainName sdkName xcodePlatform sdkVer productBuildVer;
  };

  platforms = callPackage ./platforms.nix {
    inherit sdks xcodePlatform stdenv;
  };

  xcconfig = writeText "nix.xcconfig" ''
    SDKROOT=${sdkName}
  '';

  xcode-select = writeText "xcode-select" ''
#!${stdenv.shell}
while [ $# -gt 0 ]; do
   case "$1" in
         -h | --help) ;; # noop
         -s | --switch) shift;; # noop
         -r | --reset) ;; # noop
         -v | --version) echo xcode-select version ${xcodeSelectVersion} ;;
         -p | -print-path | --print-path) echo @DEVELOPER_DIR@ ;;
         --install) ;; # noop
    esac
    shift
done
  '';

  xcrun = writeTextFile {
    name = "xcrun";
    executable = true;
    destination = "/bin/xcrun";
    text = ''
#!${stdenv.shell}
args=( "$@" )

# If an SDK was requested, check that it matches.
for ((i = 0; i < ''${#args[@]}; i++)); do
  case "''${args[i]}" in
    --sdk | -sdk)
      i=$((i + 1))
      if [[ "''${args[i]}" != '${xcrunSdkName}' ]]; then
        echo >&2 "xcodebuild: error: SDK \"''${args[i]}\" cannot be located."
        exit 1
      fi
      ;;
  esac
done

while [ $# -gt 0 ]; do
   case "$1" in
         --sdk | -sdk) shift ;;
         --toolchain | -toolchain) shift ;;
         --find | -find | -f)
           shift
           command -v $1 || exit 1 ;;
         --log | -log) ;; # noop
         --verbose | -verbose) ;; # noop
         --no-cache | -no-cache) ;; # noop
         --kill-cache | -kill-cache) ;; # noop
         --show-sdk-path | -show-sdk-path)
           echo ${sdks}/${sdkName}.sdk ;;
         --show-sdk-platform-path | -show-sdk-platform-path)
           echo ${platforms}/${xcodePlatform}.platform ;;
         --show-sdk-version | -show-sdk-version)
           echo ${sdkVer} ;;
         --show-sdk-build-version | -show-sdk-build-version)
           echo ${sdkBuildVersion} ;;
         *) break ;;
    esac
    shift
done

if ! [[ -z "$@" ]]; then
   exec "$@"
fi
    '';
    checkPhase = ''
      ${stdenv.shellDryRun} "$target"
    '';
  };

in

runCommand "xcodebuild-${xcbuild.version}" {
  nativeBuildInputs = [ makeWrapper ];
  inherit (xcbuild) meta;

  # ensure that the toolchain goes in PATH
  propagatedBuildInputs = [ "${toolchains}/XcodeDefault.xctoolchain" ];

  passthru = {
    inherit xcbuild xcrun;
    toolchain = "${toolchains}/XcodeDefault.xctoolchain";
    sdk = "${sdks}/${sdkName}";
    platform = "${platforms}/${xcodePlatform}.platform";
  };

  preferLocalBuild = true;
} ''
  mkdir -p $out/bin

  ln -s $out $out/usr

  mkdir -p $out/Library/Xcode
  ln -s ${xcbuild}/Library/Xcode/Specifications $out/Library/Xcode/Specifications

  ln -s ${platforms} $out/Platforms
  ln -s ${toolchains} $out/Toolchains

  mkdir -p $out/Applications/Xcode.app/Contents
  ln -s $out $out/Applications/Xcode.app/Contents/Developer

  # The native xcodebuild command supports an invocation like "xcodebuild -version -sdk" without specifying the specific SDK, so we simulate this by
  # detecting this case and simulating the output; printing the header and appending the normal output via appending the sdk version to the positional
  # arguments we pass through to the wrapped xcodebuild.
  makeWrapper ${xcbuild}/bin/xcodebuild $out/bin/xcodebuild \
    --add-flags "-xcconfig ${xcconfig}" \
    --add-flags "DERIVED_DATA_DIR=." \
    --set DEVELOPER_DIR "$out" \
    --set SDKROOT ${sdkName} \
    --run '[ "$#" -eq 2 ] && [ "$1" = "-version" ] && [ "$2" = "-sdk" ] && echo ${sdkName}.sdk - macOS ${sdkVer} \(macosx${sdkVer}\) && set -- "$@" "${sdkName}"' \
    --run '[ "$1" = "-version" ] && [ "$#" -eq 1 ] && (echo Xcode ${xcodeVer}; echo Build version ${sdkBuildVersion}) && exit 0' \
    --run '[ "$1" = "-license" ] && exit 0'

  substitute ${xcode-select} $out/bin/xcode-select \
    --subst-var-by DEVELOPER_DIR $out/Applications/Xcode.app/Contents/Developer
  chmod +x $out/bin/xcode-select

  cp ${xcrun}/bin/xcrun $out/bin/xcrun

  for bin in PlistBuddy actool builtin-copy builtin-copyPlist \
             builtin-copyStrings builtin-copyTiff \
             builtin-embeddedBinaryValidationUtility \
             builtin-infoPlistUtility builtin-lsRegisterURL \
             builtin-productPackagingUtility builtin-validationUtility \
             lsbom plutil; do
    ln -s ${xcbuild}/bin/$bin $out/bin/$bin
  done

  fixupPhase
''
