Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions doc/build-helpers/special/checkpoint-build.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,38 @@

`pkgs.checkpointBuildTools` provides a way to build derivations incrementally. It consists of two functions to make checkpoint builds using Nix possible.

For hermeticity, Nix derivations do not allow any state to carry over between builds, making a transparent incremental build within a derivation impossible.
For hermeticity, Nix derivations do not allow any state to be carried over between builds, making a transparent incremental build within a derivation impossible.

However, we can tell Nix explicitly what the previous build state was, by representing that previous state as a derivation output. This allows the passed build state to be used for an incremental build.

To change a normal derivation to a checkpoint based build, these steps must be taken:
- apply `prepareCheckpointBuild` on the desired derivation
e.g.:
- apply `prepareCheckpointBuild` on the desired derivation, e.g.
```nix
checkpointArtifacts = (pkgs.checkpointBuildTools.prepareCheckpointBuild pkgs.virtualbox);
```
- change something you want in the sources of the package. (e.g. using a source override)
- change something you want in the sources of the package, e.g. use a source override:
```nix
changedVBox = pkgs.virtualbox.overrideAttrs (old: {
src = path/to/vbox/sources;
}
});
```
- use `mkCheckpointedBuild changedVBox buildOutput`
- use `mkCheckpointBuild changedVBox checkpointArtifacts`
- enjoy shorter build times

## Example {#sec-checkpoint-build-example}
```nix
{ pkgs ? import <nixpkgs> {} }: with (pkgs) checkpointBuildTools;
{ pkgs ? import <nixpkgs> {} }:
let
helloCheckpoint = checkpointBuildTools.prepareCheckpointBuild pkgs.hello;
inherit (pkgs.checkpointBuildTools)
prepareCheckpointBuild
mkCheckpointBuild
;
helloCheckpoint = prepareCheckpointBuild pkgs.hello;
changedHello = pkgs.hello.overrideAttrs (_: {
doCheck = false;
patchPhase = ''
sed -i 's/Hello, world!/Hello, Nix!/g' src/hello.c
'';
});
in checkpointBuildTools.mkCheckpointBuild changedHello helloCheckpoint
in mkCheckpointBuild changedHello helloCheckpoint
```
79 changes: 50 additions & 29 deletions pkgs/build-support/checkpoint-build.nix
Original file line number Diff line number Diff line change
@@ -1,40 +1,53 @@
{ pkgs }:
{ lib
, buildPackages
}:

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

rec {
/* Prepare a derivation for local builds.
*
* This function prepares checkpoint builds by provinding,
* containing the build output and the sources for cross checking.
* 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 prepareIncrementalBuild on the desired derivation
* e.G `incrementalBuildArtifacts = (pkgs.checkpointBuildTools.prepareCheckpointBuild pkgs.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 `mkCheckpointedBuild changedVBox buildOutput`
* 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
# 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.
# 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
# After the build, the build directory is copied again
# to get the output files.
# We copy the complete build folder, to take care for
# Build tools, building in the source directory, instead of
# having a build root directory, e.G the Linux kernel.
# 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
Expand All @@ -44,26 +57,34 @@ rec {
});

/* Build a derivation based on the checkpoint output generated by
* the `prepareCheckpointBuild function.
* the `prepareCheckpointBuild` function.
*
* Usage:
* let
* checkpointArtifacts = prepareCheckpointBuild drv
* in mkCheckpointedBuild drv checkpointArtifacts
* checkpointArtifacts = prepareCheckpointBuild drv;
* in mkCheckpointBuild drv checkpointArtifacts
*/
mkCheckpointedBuild = drv: previousBuildArtifacts: drv.overrideAttrs (old: {
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 to copy the previous output files (Including the sources)
# The source difference patch is applied to get the latest changes again to allow short build times.
# 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
diff -ur ${previousBuildArtifacts}/sources ./ > sourceDifference.patch
sourceDifferencePatchFile=$(${mktemp}/bin/mktemp)
diff -ur ${checkpointArtifacts}/sources ./ > "$sourceDifferencePatchFile"
set -e
shopt -s extglob dotglob
rm -r !("sourceDifference.patch")
${pkgs.rsync}/bin/rsync -cutU --chown=$USER:$USER --chmod=+w -r ${previousBuildArtifacts}/outputs/* .
patch -p 1 -i sourceDifference.patch
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"
'';
});

mkCheckpointedBuild = lib.warn
"`mkCheckpointedBuild` is deprecated, use `mkCheckpointBuild` instead!"
Copy link
Member Author

@bryango bryango Jan 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[grammar] Remember to port this:

From a3b90e768b8d43e0efb9cc227153d55609c65999 Mon Sep 17 00:00:00 2001
From: bryango <bryango@users.noreply.github.com>
Date: Thu, 11 Jan 2024 22:52:52 +0800
Subject: [PATCH] checkpointBuildTools.mkCheckpointedBuild: update deprecation
 warning

Co-authored-by: Robert Hensing <roberth@users.noreply.github.com>
---
 pkgs/build-support/checkpoint-build.nix | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pkgs/build-support/checkpoint-build.nix b/pkgs/build-support/checkpoint-build.nix
index c9bee45005a13a..cc70eddd7ba303 100644
--- a/pkgs/build-support/checkpoint-build.nix
+++ b/pkgs/build-support/checkpoint-build.nix
@@ -85,6 +85,6 @@ rec {
   });
 
   mkCheckpointedBuild = lib.warn
-    "`mkCheckpointedBuild` is deprecated, use `mkCheckpointBuild` instead!"
+    "`mkCheckpointedBuild` is deprecated; use `mkCheckpointBuild` instead."
     mkCheckpointBuild;
 }

... in some future revision.

mkCheckpointBuild;
}
4 changes: 2 additions & 2 deletions pkgs/test/checkpointBuild/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ let
patch -p1 < ${./hello.patch}
'';
});
checkpointBuiltHello = checkpointBuildTools.mkCheckpointedBuild patchedHello baseHelloArtifacts;
checkpointBuiltHello = checkpointBuildTools.mkCheckpointBuild patchedHello baseHelloArtifacts;

checkpointBuiltHelloWithCheck = checkpointBuiltHello.overrideAttrs (old: {
doCheck = true;
Expand Down Expand Up @@ -41,7 +41,7 @@ let
'';
});

checkpointBuiltHelloWithRemovedFile = checkpointBuildTools.mkCheckpointedBuild patchedHelloRemoveFile baseHelloRemoveFileArtifacts;
checkpointBuiltHelloWithRemovedFile = checkpointBuildTools.mkCheckpointBuild patchedHelloRemoveFile baseHelloRemoveFileArtifacts;
in
stdenv.mkDerivation {
name = "patched-hello-returns-correct-output";
Expand Down
2 changes: 1 addition & 1 deletion pkgs/test/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ with pkgs;

install-shell-files = callPackage ./install-shell-files {};

checkpoint-build = callPackage ./checkpointBuild {};
checkpointBuildTools = callPackage ./checkpointBuild {};

kernel-config = callPackage ./kernel.nix {};

Expand Down