-
-
Notifications
You must be signed in to change notification settings - Fork 18.1k
incremental builds: add derivation override functions #167670
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3f4e3a8
8beb562
1cd6b7f
17e88c2
cd6c65f
c8afee8
fc2e3fa
c85d18f
ddfddf4
6db9612
0ab2262
f61980d
0d6d654
a3e0a52
15c2c68
5ebb78d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # pkgs.checkpointBuildTools {#sec-checkpoint-build} | ||
|
|
||
| `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. | ||
|
|
||
| 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.: | ||
| ```nix | ||
| checkpointArtifacts = (pkgs.checkpointBuildTools.prepareCheckpointBuild pkgs.virtualbox); | ||
| ``` | ||
| - change something you want in the sources of the package. (e.g. using a source override) | ||
| ```nix | ||
| changedVBox = pkgs.virtualbox.overrideAttrs (old: { | ||
| src = path/to/vbox/sources; | ||
| } | ||
| ``` | ||
| - use `mkCheckpointedBuild changedVBox buildOutput` | ||
| - enjoy shorter build times | ||
|
|
||
| ## Example {#sec-checkpoint-build-example} | ||
| ```nix | ||
| { pkgs ? import <nixpkgs> {} }: with (pkgs) checkpointBuildTools; | ||
| let | ||
| helloCheckpoint = checkpointBuildTools.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 | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,69 @@ | ||||||||||
| { pkgs }: | ||||||||||
| rec { | ||||||||||
| /* Prepare a derivation for local builds. | ||||||||||
| * | ||||||||||
| * This function prepares checkpoint builds by provinding, | ||||||||||
| * containing 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` | ||||||||||
| * - 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 | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit:
Suggested change
|
||||||||||
| # 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 | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| # to get the output files. | ||||||||||
| # We copy the complete build folder, to take care for | ||||||||||
| # Build tools, building in the source directory, instead of | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| # having a build root directory, e.G the Linux kernel. | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
or
Suggested change
|
||||||||||
| installPhase = '' | ||||||||||
messemar marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
| runHook preCheckpointInstall | ||||||||||
| mkdir -p $out/outputs | ||||||||||
| cp -r ./* $out/outputs/ | ||||||||||
| runHook postCheckpointInstall | ||||||||||
| ''; | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| /* Build a derivation based on the checkpoint output generated by | ||||||||||
| * the `prepareCheckpointBuild function. | ||||||||||
| * | ||||||||||
| * Usage: | ||||||||||
| * let | ||||||||||
| * checkpointArtifacts = prepareCheckpointBuild drv | ||||||||||
| * in mkCheckpointedBuild drv checkpointArtifacts | ||||||||||
| */ | ||||||||||
| mkCheckpointedBuild = drv: previousBuildArtifacts: 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. | ||||||||||
| preBuild = (old.preBuild or "") + '' | ||||||||||
| set +e | ||||||||||
| diff -ur ${previousBuildArtifacts}/sources ./ > sourceDifference.patch | ||||||||||
| 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 | ||||||||||
| ''; | ||||||||||
| }); | ||||||||||
| } | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| { hello, checkpointBuildTools, runCommandNoCC, texinfo, stdenv, rsync }: | ||
| let | ||
| baseHelloArtifacts = checkpointBuildTools.prepareCheckpointBuild hello; | ||
| patchedHello = hello.overrideAttrs (old: { | ||
| buildInputs = [ texinfo ]; | ||
| src = runCommandNoCC "patch-hello-src" { } '' | ||
| mkdir -p $out | ||
| cd $out | ||
| tar xf ${hello.src} --strip-components=1 | ||
| patch -p1 < ${./hello.patch} | ||
| ''; | ||
| }); | ||
| checkpointBuiltHello = checkpointBuildTools.mkCheckpointedBuild patchedHello baseHelloArtifacts; | ||
|
|
||
| checkpointBuiltHelloWithCheck = checkpointBuiltHello.overrideAttrs (old: { | ||
| doCheck = true; | ||
| checkPhase = '' | ||
| echo "checking if unchanged source file is not recompiled" | ||
| [ "$(stat --format="%Y" lib/exitfail.o)" = "$(stat --format="%Y" ${baseHelloArtifacts}/outputs/lib/exitfail.o)" ] | ||
| ''; | ||
| }); | ||
|
|
||
| baseHelloRemoveFileArtifacts = checkpointBuildTools.prepareCheckpointBuild (hello.overrideAttrs (old: { | ||
| patches = [ ./hello-additionalFile.patch ]; | ||
| })); | ||
|
|
||
| preparedHelloRemoveFileSrc = runCommandNoCC "patch-hello-src" { } '' | ||
| mkdir -p $out | ||
| cd $out | ||
| tar xf ${hello.src} --strip-components=1 | ||
| patch -p1 < ${./hello-additionalFile.patch} | ||
| ''; | ||
|
|
||
| patchedHelloRemoveFile = hello.overrideAttrs (old: { | ||
| buildInputs = [ texinfo ]; | ||
| src = runCommandNoCC "patch-hello-src" { } '' | ||
| mkdir -p $out | ||
| cd $out | ||
| ${rsync}/bin/rsync -cutU --chown=$USER:$USER --chmod=+w -r ${preparedHelloRemoveFileSrc}/* . | ||
| patch -p1 < ${./hello-removeFile.patch} | ||
| ''; | ||
| }); | ||
|
|
||
| checkpointBuiltHelloWithRemovedFile = checkpointBuildTools.mkCheckpointedBuild patchedHelloRemoveFile baseHelloRemoveFileArtifacts; | ||
| in | ||
| stdenv.mkDerivation { | ||
| name = "patched-hello-returns-correct-output"; | ||
| buildCommand = '' | ||
| touch $out | ||
|
|
||
| echo "testing output of hello binary" | ||
| [ "$(${checkpointBuiltHelloWithCheck}/bin/hello)" = "Hello, incremental world!" ] | ||
| echo "testing output of hello with removed file" | ||
| [ "$(${checkpointBuiltHelloWithRemovedFile}/bin/hello)" = "Hello, incremental world!" ] | ||
| ''; | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| :100644 100644 0000000 0000000 M Makefile.in | ||
| :000000 100644 0000000 0000000 A src/additionalFile.c | ||
| :100644 100644 0000000 0000000 M src/hello.c | ||
| :100644 100644 0000000 0000000 M src/system.h | ||
|
|
||
| diff --git a/Makefile.in b/Makefile.in | ||
| index 1597d39..f63f830 100644 | ||
| --- a/Makefile.in | ||
| +++ b/Makefile.in | ||
| @@ -312,7 +312,7 @@ am_lib_libhello_a_OBJECTS = lib/basename-lgpl.$(OBJEXT) \ | ||
| lib/version-etc.$(OBJEXT) lib/version-etc-fsf.$(OBJEXT) \ | ||
| lib/wctype-h.$(OBJEXT) lib/xmalloc.$(OBJEXT) \ | ||
| lib/xalloc-die.$(OBJEXT) lib/xstriconv.$(OBJEXT) \ | ||
| - lib/xstrndup.$(OBJEXT) | ||
| + lib/xstrndup.$(OBJEXT) src/additionalFile.$(OBJEXT) | ||
| lib_libhello_a_OBJECTS = $(am_lib_libhello_a_OBJECTS) | ||
| am_hello_OBJECTS = src/hello.$(OBJEXT) | ||
| hello_OBJECTS = $(am_hello_OBJECTS) | ||
| @@ -1842,7 +1842,7 @@ lib_libhello_a_SOURCES = lib/basename-lgpl.c lib/c-ctype.h \ | ||
| $(am__append_4) $(am__append_5) lib/version-etc.h \ | ||
| lib/version-etc.c lib/version-etc-fsf.c lib/wctype-h.c \ | ||
| lib/xmalloc.c lib/xalloc-die.c lib/xstriconv.h lib/xstriconv.c \ | ||
| - lib/xstrndup.h lib/xstrndup.c | ||
| + lib/xstrndup.h lib/xstrndup.c src/additionalFile.c | ||
| lib_libhello_a_LIBADD = $(gl_LIBOBJS) | ||
| lib_libhello_a_DEPENDENCIES = $(gl_LIBOBJS) | ||
| EXTRA_lib_libhello_a_SOURCES = lib/close.c lib/stripslash.c lib/dup2.c \ | ||
| diff --git a/src/additionalFile.c b/src/additionalFile.c | ||
| new file mode 100644 | ||
| index 0000000..34d683d | ||
| --- /dev/null | ||
| +++ b/src/additionalFile.c | ||
| @@ -0,0 +1,6 @@ | ||
| +#include "config.h" | ||
| +#include "system.h" | ||
| + | ||
| +int somefunc() { | ||
| + return 0; | ||
| +} | ||
| diff --git a/src/hello.c b/src/hello.c | ||
| index 2e7d38e..a8e36dc 100644 | ||
| --- a/src/hello.c | ||
| +++ b/src/hello.c | ||
| @@ -146,7 +146,11 @@ main (int argc, char *argv[]) | ||
| #endif | ||
|
|
||
| /* Having initialized gettext, get the default message. */ | ||
| - greeting_msg = _("Hello, world!"); | ||
| + if (somefunc() == 0) { | ||
| + greeting_msg = _("Hello, world!"); | ||
| + } else { | ||
| + greeting_msg = _("Hello, incremental world!"); | ||
| + } | ||
|
|
||
| /* Even exiting has subtleties. On exit, if any writes failed, change | ||
| the exit status. The /dev/full device on GNU/Linux can be used for | ||
| diff --git a/src/system.h b/src/system.h | ||
| index d39cdb9..dc425d2 100644 | ||
| --- a/src/system.h | ||
| +++ b/src/system.h | ||
| @@ -59,4 +59,6 @@ | ||
| } \ | ||
| while (0) | ||
|
|
||
| +int somefunc(); | ||
| + | ||
| #endif /* HELLO_SYSTEM_H */ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| :100644 100644 0000000 0000000 M Makefile.in | ||
| :100644 000000 0000000 0000000 D src/additionalFile.c | ||
| :100644 100644 0000000 0000000 M src/hello.c | ||
| :100755 100755 0000000 0000000 M tests/hello-1 | ||
|
|
||
| diff --git a/Makefile.in b/Makefile.in | ||
| index f63f830..1597d39 100644 | ||
| --- a/Makefile.in | ||
| +++ b/Makefile.in | ||
| @@ -312,7 +312,7 @@ am_lib_libhello_a_OBJECTS = lib/basename-lgpl.$(OBJEXT) \ | ||
| lib/version-etc.$(OBJEXT) lib/version-etc-fsf.$(OBJEXT) \ | ||
| lib/wctype-h.$(OBJEXT) lib/xmalloc.$(OBJEXT) \ | ||
| lib/xalloc-die.$(OBJEXT) lib/xstriconv.$(OBJEXT) \ | ||
| - lib/xstrndup.$(OBJEXT) src/additionalFile.$(OBJEXT) | ||
| + lib/xstrndup.$(OBJEXT) | ||
| lib_libhello_a_OBJECTS = $(am_lib_libhello_a_OBJECTS) | ||
| am_hello_OBJECTS = src/hello.$(OBJEXT) | ||
| hello_OBJECTS = $(am_hello_OBJECTS) | ||
| @@ -1842,7 +1842,7 @@ lib_libhello_a_SOURCES = lib/basename-lgpl.c lib/c-ctype.h \ | ||
| $(am__append_4) $(am__append_5) lib/version-etc.h \ | ||
| lib/version-etc.c lib/version-etc-fsf.c lib/wctype-h.c \ | ||
| lib/xmalloc.c lib/xalloc-die.c lib/xstriconv.h lib/xstriconv.c \ | ||
| - lib/xstrndup.h lib/xstrndup.c src/additionalFile.c | ||
| + lib/xstrndup.h lib/xstrndup.c | ||
| lib_libhello_a_LIBADD = $(gl_LIBOBJS) | ||
| lib_libhello_a_DEPENDENCIES = $(gl_LIBOBJS) | ||
| EXTRA_lib_libhello_a_SOURCES = lib/close.c lib/stripslash.c lib/dup2.c \ | ||
| diff --git a/src/additionalFile.c b/src/additionalFile.c | ||
| deleted file mode 100644 | ||
| index 34d683d..0000000 | ||
| --- a/src/additionalFile.c | ||
| +++ /dev/null | ||
| @@ -1,6 +0,0 @@ | ||
| -#include "config.h" | ||
| -#include "system.h" | ||
| - | ||
| -int somefunc() { | ||
| - return 0; | ||
| -} | ||
| diff --git a/src/hello.c b/src/hello.c | ||
| index a8e36dc..53722d9 100644 | ||
| --- a/src/hello.c | ||
| +++ b/src/hello.c | ||
| @@ -126,6 +126,10 @@ parse_options (int argc, char *argv[], const char **greeting_msg) | ||
| } | ||
| } | ||
|
|
||
| +int somefunc() { | ||
| + return 1; | ||
| +} | ||
| + | ||
| int | ||
| main (int argc, char *argv[]) | ||
| { | ||
| diff --git a/tests/hello-1 b/tests/hello-1 | ||
| index 96ffef8..f0b9f8d 100755 | ||
| --- a/tests/hello-1 | ||
| +++ b/tests/hello-1 | ||
| @@ -21,7 +21,7 @@ export LANGUAGE LC_ALL LC_MESSAGES LANG | ||
|
|
||
| tmpfiles="hello-test1.ok" | ||
| cat <<EOF > hello-test1.ok | ||
| -Hello, world! | ||
| +Hello, incremental world! | ||
| EOF | ||
|
|
||
| tmpfiles="$tmpfiles hello-test1.out" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| diff --git a/src/hello.c b/src/hello.c | ||
| index 182303c..453962f 100644 | ||
| --- a/src/hello.c | ||
| +++ b/src/hello.c | ||
| @@ -57,7 +57,7 @@ main (int argc, char *argv[]) | ||
| #endif | ||
|
|
||
| /* Having initialized gettext, get the default message. */ | ||
| - greeting_msg = _("Hello, world!"); | ||
| + greeting_msg = _("Hello, incremental world!"); | ||
|
|
||
| /* Even exiting has subtleties. On exit, if any writes failed, change | ||
| the exit status. The /dev/full device on GNU/Linux can be used for | ||
| diff --git a/tests/hello-1 b/tests/hello-1 | ||
| index 3b7a815..e15fa95 100755 | ||
| --- a/tests/hello-1 | ||
| +++ b/tests/hello-1 | ||
| @@ -21,7 +21,7 @@ export LANGUAGE LC_ALL LC_MESSAGES LANG | ||
|
|
||
| tmpfiles="hello-test1.ok" | ||
| cat <<EOF > hello-test1.ok | ||
| -Hello, world! | ||
| +Hello, incremental world! | ||
| EOF | ||
|
|
||
| tmpfiles="$tmpfiles hello-test1.out" |
Uh oh!
There was an error while loading. Please reload this page.