Skip to content

Commit

Permalink
Added rerunFixedDerivationOnChange.
Browse files Browse the repository at this point in the history
Support for rerunning fixed output derivations if any of their inputs change
  • Loading branch information
kolloch committed May 2, 2020
1 parent d1ad5c5 commit 77c0ad6
Show file tree
Hide file tree
Showing 7 changed files with 445 additions and 8 deletions.
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,55 @@

[![Build Status](https://travis-ci.com/kolloch/nur-packages.svg?branch=master)](https://travis-ci.com/kolloch/nur-packages)
[![Cachix Cache](https://img.shields.io/badge/cachix-eigenvalue-blue.svg)](https://eigenvalue.cachix.org)

## rerunFixedDerivationOnChange

Creates a fixed output derivation that reruns if any of its inputs change.

Type: `rerunFixedDerivationOnChange -> derivation -> derivation`

Example:

```nix
let myFixedOutputDerivation = pkgs.stdenv.mkDerivation {
# ...
outputHash = "sha256:...";
outputHashMode = "recursive";
};
in
nur.repos.kolloch.lib.rerunFixedDerivationOnChange myFixedOutputDerivation;
```

Usually, fixed output derivations are only rerun if their name or hash
changes (i.e. when their output path changes). This makes the derivation
definition irrelevant if the output is in the cache.

With this helper, you can make sure that the commands that are specified
where at least ran once -- with the exact versions of the buildInputs that
you specified. It does so by putting a hash of all the inputs into the
name.

Caveat: It uses "import from derivation" under the hood.

### Implementation

Getting the hash is easy, in principle, because nix already does that work
for us when creating the output paths for non-fixed output derivations.
Therefore, if we create a non-fixed output derivation with the same inputs,
the hash in the output path should change with every change in inputs.
Unfortunately, we cannot use parts of the output path in the name of our
derivation directly.

Nix prevents this because supposedly it is not what you want. I don't think
that it is actually problematic in principle.

To work around this, I use an import-from-derivation call to get the output
path. The disadvantage is that all the dependencies of your fixed output
derivation will be build in the eval phase.

## Nix unit tests with nice diffing output

`nur.repos.kolloch.lib.runTest` exposes `nix-test-runner` runTest.

Docs are at the [nix-test-runner
repository](https://github.com/stoeffel/nix-test-runner).
17 changes: 14 additions & 3 deletions default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,25 @@
# commands such as:
# nix-build -A mypackage

{ pkgs ? import <nixpkgs> {} }:
{ sources ? import ./nix/sources.nix
, nixpkgs ? sources.nixpkgs
, pkgs ? import nixpkgs {}
, nixTestRunnerSrc ? sources.nix-test-runner
, nixTestRunner ? pkgs.callPackage nixTestRunnerSrc {}
}:

{
rec {
# The `lib`, `modules`, and `overlay` names are special
lib = import ./lib { inherit pkgs; }; # functions
lib = pkgs.callPackage ./lib { inherit nixTestRunner; }; # functions
modules = import ./modules; # NixOS modules
overlays = import ./overlays; # nixpkgs overlays

tests = pkgs.lib.callPackageWith
(pkgs // { inherit sources; nurKollochLib = lib; } )
./tests {};

nix-test-runner = nixTestRunner.package;

# Packages.
# example-package = pkgs.callPackage ./pkgs/example-package { };
# some-qt5-package = pkgs.libsForQt5.callPackage ./pkgs/some-qt5-package { };
Expand Down
12 changes: 7 additions & 5 deletions lib/default.nix
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
{ pkgs }:
{ pkgs, lib, nixTestRunner }:

with pkgs.lib; {
# Add your library functions here
#
# hexint = x: hexvals.${toLower x};
rec {
# I use this to keep individual features also importable independently
# of other code in this NUR repo.
inherit (pkgs.callPackage ./rerun-fixed.nix {}) rerunFixedDerivationOnChange;

runTests = nixTestRunner.runTests;
}

125 changes: 125 additions & 0 deletions lib/rerun-fixed.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
{ pkgs, lib }:

rec {
/* Creates a fixed output derivation that reruns if any of its inputs change.
Type: rerunFixedDerivationOnChange -> derivation -> derivation
Example:
let myFixedOutputDerivation = pkgs.stdenv.mkDerivation {
# ...
outputHash = "sha256:...";
outputHashMode = "recursive";
};
in
nur.repos.kolloch.lib.rerunFixedDerivationOnChange myFixedOutputDerivation;
Usually, fixed output derivations are only rerun if their name or hash
changes (i.e. when their output path changes). This makes the derivation
definition irrelevant if the output is in the cache.
With this helper, you can make sure that the commands that are specified
where at least ran once -- with the exact versions of the buildInputs that
you specified. It does so by putting a hash of all the inputs into the
name.
Caveat: It uses "import from derivation" under the hood. See implementation
for an explanation why.
Implementation
Getting the hash is easy, in principle, because nix already does that work
for us when creating the output paths for non-fixed output derivations.
Therefore, if we create a non-fixed output derivation with the same inputs,
the hash in the output path should change with every change in inputs.
Unfortunately, we cannot use parts of the output path in the name of our
derivation directly.
Nix prevents this because supposedly it is not what you want. I don't think
that it is actually problematic in principle.
To work around this, I use an import-from-derivation call to get the output
path. The disadvantage is that all the dependencies of your fixed output
derivation will be build in the eval phase.
*/
rerunFixedDerivationOnChange = fixedOutputDerivation:
assert (lib.assertMsg
(fixedOutputDerivation ? outputHash)
"rerunOnChangedInputs expects a fixedOutputDerivation");

let
# The name of the original derivation.
name = fixedOutputDerivation.name or "fixed";

# A hash that includes all inputs of the fixedOutputDerivation.
#
# Creates a derivation that has all the same inputs and
# simply outputs it's output path.
#
# If we could simply access the output path from nix
# and use it as a part of the name, this would be much easier.
inputsHash =
let
# Args to rename.
renameArgs = [ "builder" "args" ];
renamedArgs =
let declash = attrName:
if fixedOutputDerivation ? attrName
then declash "${attrName}_"
else attrName;
copyName = attrName: "original_${declash attrName}";
copy = attrName:
{
name = copyName attrName;
value = fixedOutputDerivation."${attrName}";
};
copies = builtins.map copy renameArgs;
in
builtins.listToAttrs copies;
# Args to replace with null
nullArgs = renameArgs ++ [ "outputHash" "outputHashAlgo" "outputHashMode" ];
nulledArgs =
let nullMe = name:
{
inherit name;
value = null;
};
nulled = builtins.map nullMe nullArgs;
in
builtins.listToAttrs nulled;
outputPathBuilderArgs =
assert builtins.isAttrs renamedArgs;
assert builtins.isAttrs nulledArgs;
renamedArgs // nulledArgs // {
name = "outpath-for-${name}";
builder = "${pkgs.bash}/bin/bash";
args = ["-c" "set -x; echo $out > $out" ];
};
outputPathBuilder =
assert builtins.isAttrs outputPathBuilderArgs;
if fixedOutputDerivation ? overrideAttrs
then fixedOutputDerivation.overrideAttrs (attrs: outputPathBuilderArgs)
else fixedOutputDerivation.overrideDerivation (attrs: outputPathBuilderArgs);
outputPath =
assert lib.isDerivation outputPathBuilder;
builtins.readFile outputPathBuilder;
in builtins.substring 11 32 outputPath;

# This is the basic idea: Add the inputsHash to the name
# to force a rebuild whenever the inputs change.
nameWithHash = { name = "${inputsHash}_${name}"; };

# Make "overrideAttrs" and "overrideDerivation" work as expected.
# That means that we need to keep overriding the name.
overrideAttrs = f: rerunFixedDerivationOnChange (fixedOutputDerivation.overrideAttrs f);
overrideDerivation = f: rerunFixedDerivationOnChange (fixedOutputDerivation.overrideDerivation f);
in
if fixedOutputDerivation ? overrideAttrs
then
(fixedOutputDerivation.overrideAttrs (attrs: nameWithHash))
// { inherit overrideAttrs overrideDerivation; }
else
(fixedOutputDerivation.overrideDerivation (attrs: nameWithHash))
// { inherit overrideDerivation; };
}

38 changes: 38 additions & 0 deletions nix/sources.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"niv": {
"branch": "master",
"description": "Easy dependency management for Nix projects",
"homepage": "https://github.com/nmattia/niv",
"owner": "nmattia",
"repo": "niv",
"rev": "f73bf8d584148677b01859677a63191c31911eae",
"sha256": "0jlmrx633jvqrqlyhlzpvdrnim128gc81q5psz2lpp2af8p8q9qs",
"type": "tarball",
"url": "https://github.com/nmattia/niv/archive/f73bf8d584148677b01859677a63191c31911eae.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
},
"nix-test-runner": {
"branch": "master",
"description": "Test runner for nix expressions",
"homepage": "",
"owner": "stoeffel",
"repo": "nix-test-runner",
"rev": "545306ab20a561ef79567e68fa0e25e1ed340ef1",
"sha256": "0iz5kmls6mwwa7lxpi4cq3yq36ygk2vrf0k54xyis736ikx6xky1",
"type": "tarball",
"url": "https://github.com/stoeffel/nix-test-runner/archive/545306ab20a561ef79567e68fa0e25e1ed340ef1.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
},
"nixpkgs": {
"branch": "nixos-20.03",
"description": "A read-only mirror of NixOS/nixpkgs tracking the released channels. Send issues and PRs to",
"homepage": "https://github.com/NixOS/nixpkgs",
"owner": "NixOS",
"repo": "nixpkgs-channels",
"rev": "ab3adfe1c769c22b6629e59ea0ef88ec8ee4563f",
"sha256": "1m4wvrrcvif198ssqbdw897c8h84l0cy7q75lyfzdsz9khm1y2n1",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs-channels/archive/ab3adfe1c769c22b6629e59ea0ef88ec8ee4563f.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
}
}
Loading

0 comments on commit 77c0ad6

Please sign in to comment.