{
  lib,
  buildPackages,
  config,
}:

let
  # rudimentary support for cross-compiling
  # see: https://github.com/NixOS/nixpkgs/pull/279487#discussion_r1444449726
  inherit (buildPackages)
    mktemp
    rsync
    ;

  /*
    Build a derivation based on the checkpoint output generated by
    * the `prepareCheckpointBuild` function.
    *
    * Usage:
    * let
    *   checkpointArtifacts = prepareCheckpointBuild drv;
    * in mkCheckpointBuild drv checkpointArtifacts
  */
  mkCheckpointBuild =
    drv: checkpointArtifacts:
    drv.overrideAttrs (old: {
      # The actual checkpoint build phase.
      # We compare the changed sources from a previous build with the current and create a patch.
      # Afterwards we clean the build directory and copy the previous output files (including the sources).
      # The source difference patch is then applied to get the latest changes again to allow short build times.
      preBuild = (old.preBuild or "") + ''
        set +e
        sourceDifferencePatchFile=$(${mktemp}/bin/mktemp)
        diff -ur ${checkpointArtifacts}/sources ./ > "$sourceDifferencePatchFile"
        set -e
        shopt -s dotglob
        rm -r *
        ${rsync}/bin/rsync \
          --checksum --times --atimes --chown=$USER:$USER --chmod=+w \
          -r ${checkpointArtifacts}/outputs/ .
        patch -p 1 -i "$sourceDifferencePatchFile"
        rm "$sourceDifferencePatchFile"
      '';
    });
in

{
  inherit mkCheckpointBuild;
  /*
    Prepare a derivation for local builds.
    *
    * This function prepares checkpoint builds by storing
    * the build output and the sources for cross checking.
    * The build output can be used later to allow checkpoint builds
    * by passing the derivation output to the `mkCheckpointBuild` function.
    *
    * To build a project with checkpoints, follow these steps:
    * - run `prepareCheckpointBuild` on the desired derivation, e.g.
    *     checkpointArtifacts = prepareCheckpointBuild virtualbox;
    * - change something you want in the sources of the package,
    *   e.g. using source override:
    *     changedVBox = pkgs.virtuabox.overrideAttrs (old: {
    *       src = path/to/vbox/sources;
    *     };
    * - use `mkCheckpointBuild changedVBox checkpointArtifacts`
    * - enjoy shorter build times
  */
  prepareCheckpointBuild =
    drv:
    drv.overrideAttrs (old: {
      outputs = [ "out" ];
      name = drv.name + "-checkpointArtifacts";
      # To determine differences between the state of the build directory
      # from an earlier build and a later one we store the state of the build
      # directory before build, but after patch phases.
      # This way, the same derivation can be used multiple times and only changes are detected.
      # Additionally, removed files are handled correctly in later builds.
      preBuild = (old.preBuild or "") + ''
        mkdir -p $out/sources
        cp -r ./* $out/sources/
      '';

      # After the build, the build directory is copied again
      # to get the output files.
      # We copy the complete build folder, to take care of
      # build tools that build in the source directory, instead of
      # having a separate build directory such as the Linux kernel.
      installPhase = ''
        runHook preCheckpointInstall
        mkdir -p $out/outputs
        cp -r ./* $out/outputs/
        runHook postCheckpointInstall
        unset postPhases
      '';

      dontFixup = true;
      doInstallCheck = false;
      doDist = false;
    });
}
// lib.optionalAttrs config.allowAliases {
  mkCheckpointedBuild = lib.warn "`mkCheckpointedBuild` is deprecated, use `mkCheckpointBuild` instead!" mkCheckpointBuild;
}
