on 19052d8 help sign in
haskell.nix/lib/materialize.nix
166 lines 7.1 kB history raw
1{ pkgs, nix, runCommand, writeShellScript, checkMaterialization }@defaults:
2{ sha256 ? null # Has to make this a fixed output derivation
3, sha256Arg ? "sha256"
4 # Name of the sha256 argument for more meaningful
5 # error messages when checking the materialization.
6, materialized # null or path where to find materialized version of
7 # the output. If this is set but does not exist
8 # the derivation will fail but with a message
9 # advising how to populate it.
10, this ? "this"
11 # A name to use when referring to the thing currently being
12 # materialized. Clients can pass in an attribute name or similar.
13, reasonNotSafe ? null
14 # Some times there a reasont the derivation will
15 # not produce output that can be safely materialized.
16 # Set this to a string explaining why and materialization
17 # will not be used (if sha256 was set an error will be
18 # displayed including the reasonNotSafe string).
19, checkMaterialization ? defaults.checkMaterialization
20 # When checkMaterialization is set the derivation
21 # will be calculated the slow way (without using `sha256`
22 # and `materialized`) the result will be used to check
23 # `sha256` and `materialized` (if set).
24}: derivation:
26let
27 inherit (derivation) name;
29 traceIgnoringSha256 = reason: x:
30 if sha256 != null
31 then builtins.trace ("Warning: ignoring sha256 for " + name + " " + reason) x
32 else x;
34 traceIgnoringMaterialized = reason: x:
35 if materialized != null
36 then builtins.trace ("Warning: ignoring materialized for " + name + " " + reason) x
37 else x;
39 unchecked =
40 let
41 sha256message = "To make ${this} a fixed-output derivation but not materialized, set `${sha256Arg}` to the output of the 'calculateMaterializedSha' script in 'passthru'.";
42 materializeMessage = "To materialize ${this} entirely, pass a writable path as the `materialized` argument and run the 'updateMaterialized' script in 'passthru'.";
43 in if reasonNotSafe != null
44 then
45 # Warn the user if they tried to pin stuff down when it is not safe
46 traceIgnoringSha256 reasonNotSafe
47 (traceIgnoringMaterialized reasonNotSafe calculateNoHash)
48 else if materialized != null
49 then calculateUseMaterialized
50 else if sha256 != null
51 then
52 # Let the user know how to materialize if they want to.
53 builtins.trace materializeMessage calculateUseHash
54 else # materialized == null && sha256 == null
55 # Let the user know how to calculate a sha256 or materialize if they want to.
56 builtins.trace sha256message (builtins.trace materializeMessage calculateNoHash);
58 # Build fully and check the hash and materialized versions
59 checked =
60 # Let the user know what we are checking. This is useful for debugging issues
61 # where materialization fails to prevent infinite recurstion when:
62 # checkMaterialization = true;
63 (if materialized != null
64 then __trace "Checking materialization in ${toString materialized}"
65 else x: x)
66 runCommand name {} (''
67 ERR=$(mktemp -d)/errors.txt
68 ''
69 + (pkgs.lib.optionalString (sha256 != null) ''
70 NEW_HASH=$(${calculateMaterializedSha})
71 if [[ ${sha256} != $NEW_HASH ]]; then
72 echo Changes to ${name} not reflected in ${sha256Arg}
73 diff -ru ${calculateUseHash} ${calculateNoHash} || true
74 echo "Calculated hash for ${name} was not ${sha256}. New hash is :" >> $ERR
75 echo " ${sha256Arg} = \"$NEW_HASH\";" >> $ERR
76 else
77 echo ${sha256Arg} used for ${name} is correct
78 fi
79 '')
80 + (
81 if materialized != null && !__pathExists materialized
82 then ''
83 echo "Materialized nix used for ${name} is missing. To fix run: ${updateMaterialized}" >> $ERR
84 cat $ERR
85 false
86 ''
87 else
88 (pkgs.lib.optionalString (materialized != null && __pathExists materialized) ''
89 if diff -qr ${materialized} ${calculateNoHash} &>/dev/null; then
90 echo materialized nix used for ${name} is correct
91 else
92 echo Changes to plan not reflected in materialized nix for ${name}
93 diff -ru ${materialized} ${calculateNoHash} || true
94 echo "Materialized nix used for ${name} incorrect. To fix run: ${updateMaterialized}" >> $ERR
95 fi
96 '')
97 + ''
98 if [[ -e $ERR ]]; then
99 cat $ERR
100 false
101 else
102 cp -Lr ${unchecked} $out
103 # Make sure output files can be removed from the sandbox
104 chmod -R +w $out
105 fi
106 '')
107 );
109 hashArgs = {
110 outputHashMode = "recursive";
111 outputHashAlgo = "sha256";
112 outputHash = sha256;
113 };
114 calculateNoHash = derivation;
115 calculateUseHash =
116 # Use `cp -Lr` here to get rid of symlinks so we know the result
117 # can be safely materialized (no symlinks to the store).
118 runCommand name hashArgs ''
119 cp -Lr ${derivation} $out
120 # Make sure output files can be removed from the sandbox
121 chmod -R +w $out
122 '';
123 calculateUseMaterialized =
124 assert materialized != null;
125 assert __pathExists materialized;
126 runCommand name (pkgs.lib.optionalAttrs (sha256 == null) hashArgs) ''
127 cp -Lr ${materialized} $out
128 # Make sure output files can be removed from the sandbox
129 chmod -R +w $out
130 '';
131 calculateMaterializedSha =
132 writeShellScript "calculateSha" ''${nix}/bin/nix-hash --base32 --type sha256 ${calculateNoHash}'';
134 # Generate the materialized files in a particular path.
135 generateMaterialized =
136 writeShellScript "generateMaterialized" ''
137 TARGET=$1
139 # Crudely try and guard people from writing to the Nix store accidentally
140 if [[ ''${TARGET##/nix/store/} != $TARGET ]]; then
141 echo "Attempted to write to $TARGET in the Nix store! Put your materialized files somewhere else!"
142 exit 1
143 fi
145 # Generate the files
146 rm -rf $TARGET
147 cp -r ${calculateNoHash} "$TARGET"
148 chmod -R +w "$TARGET"
149 '';
150 # Update the materialized files at 'materialized', which must already be set.
151 updateMaterialized =
152 assert materialized != null;
153 writeShellScript "updateMaterialized" ''${generateMaterialized} ${toString materialized}'';
155 # Materialized location was specified, but the files are not there.
156 missingMaterialized = materialized != null && !__pathExists materialized;
158 # Use the checked version if requested or if the `materialized` version
159 # is missing (perhaps deleted or not created yet).
160 result = if checkMaterialization || missingMaterialized
161 then checked
162 else unchecked;
164in result
165 # Also include the scripts for fixing materialization files in passthru.
166 // { passthru = (result.passthru or {}) // { inherit generateMaterialized updateMaterialized calculateMaterializedSha; }; }