Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at python-updates 193 lines 6.7 kB view raw
1{ 2 lib, 3 runCommandLocal, 4 replaceDirectDependencies, 5}: 6 7# Replace some dependencies in the requisites tree of drv, propagating the change all the way up the tree, even within other replacements, without a full rebuild. 8# This can be useful, for example, to patch a security hole in libc and still use your system safely without rebuilding the world. 9# This should be a short term solution, as soon as a rebuild can be done the properly rebuilt derivation should be used. 10# Each old dependency and the corresponding new dependency MUST have the same-length name, and ideally should have close-to-identical directory layout. 11# 12# Example: safeFirefox = replaceDependencies { 13# drv = firefox; 14# replacements = [ 15# { 16# oldDependency = glibc; 17# newDependency = glibc.overrideAttrs (oldAttrs: { 18# patches = oldAttrs.patches ++ [ ./fix-glibc-hole.patch ]; 19# }); 20# } 21# { 22# oldDependency = libwebp; 23# newDependency = libwebp.overrideAttrs (oldAttrs: { 24# patches = oldAttrs.patches ++ [ ./fix-libwebp-hole.patch ]; 25# }); 26# } 27# ]; 28# }; 29# This will first rebuild glibc and libwebp with your security patches. 30# Then it copies over firefox (and all of its dependencies) without rebuilding further. 31# In particular, the glibc dependency of libwebp will be replaced by the patched version as well. 32# 33# In rare cases, it is possible for the replacement process to cause breakage (for example due to checksum mismatch). 34# The cutoffPackages argument can be used to exempt the problematic packages from the replacement process. 35{ 36 drv, 37 replacements, 38 cutoffPackages ? [ ], 39 verbose ? true, 40}: 41 42let 43 inherit (builtins) unsafeDiscardStringContext appendContext; 44 inherit (lib) 45 listToAttrs 46 isStorePath 47 readFile 48 attrValues 49 mapAttrs 50 filter 51 hasAttr 52 mapAttrsToList 53 ; 54 inherit (lib.attrsets) mergeAttrsList; 55 56 toContextlessString = x: unsafeDiscardStringContext (toString x); 57 warn = if verbose then lib.warn else (x: y: y); 58 59 referencesOf = 60 drv: 61 import 62 (runCommandLocal "references.nix" 63 { 64 exportReferencesGraph = [ 65 "graph" 66 drv 67 ]; 68 } 69 '' 70 (echo { 71 while read path 72 do 73 echo " \"$path\" = [" 74 read count 75 read count 76 while [ "0" != "$count" ] 77 do 78 read ref_path 79 if [ "$ref_path" != "$path" ] 80 then 81 echo " \"$ref_path\"" 82 fi 83 count=$(($count - 1)) 84 done 85 echo " ];" 86 done < graph 87 echo }) > $out 88 '' 89 ).outPath; 90 91 realisation = 92 drv: 93 if isStorePath drv then 94 # Input-addressed and fixed-output derivations have their realisation as outPath. 95 toContextlessString drv 96 else 97 # Floating and deferred derivations have a placeholder outPath. 98 # The realisation can only be obtained by performing an actual build. 99 unsafeDiscardStringContext ( 100 readFile ( 101 runCommandLocal "realisation" 102 { 103 env = { 104 inherit drv; 105 }; 106 } 107 '' 108 echo -n "$drv" > $out 109 '' 110 ) 111 ); 112 rootReferences = referencesOf drv; 113 relevantReplacements = filter ( 114 { oldDependency, newDependency }: 115 if toString oldDependency == toString newDependency then 116 warn "replaceDependencies: attempting to replace dependency ${oldDependency} of ${drv} with itself" 117 # Attempting to replace a dependency by itself is completely useless, and would only lead to infinite recursion. 118 # Hence it must not be attempted to apply this replacement in any case. 119 false 120 else if !hasAttr (realisation oldDependency) rootReferences then 121 warn "replaceDependencies: ${drv} does not depend on ${oldDependency}, so it will not be replaced" 122 # Strictly speaking, another replacement could introduce the dependency. 123 # However, handling this corner case would add significant complexity. 124 # So we just leave it to the user to apply the replacement at the correct place, but show a warning to let them know. 125 false 126 else 127 true 128 ) replacements; 129 targetDerivations = [ drv ] ++ map ({ newDependency, ... }: newDependency) relevantReplacements; 130 referencesMemo = listToAttrs ( 131 map (drv: { 132 name = realisation drv; 133 value = referencesOf drv; 134 }) targetDerivations 135 ); 136 relevantReferences = mergeAttrsList (attrValues referencesMemo); 137 # Make sure a derivation is returned even when no replacements are actually applied. 138 # Yes, even in the stupid edge case where the root derivation itself is replaced. 139 storePathOrKnownTargetDerivationMemo = 140 mapAttrs ( 141 drv: _references: 142 # builtins.storePath does not work in pure evaluation mode, even though it is not impure. 143 # This reimplementation in Nix works as long as the path is already allowed in the evaluation state. 144 # This is always the case here, because all paths come from the closure of the original derivation. 145 appendContext drv { ${drv}.path = true; } 146 ) relevantReferences 147 // listToAttrs ( 148 map (drv: { 149 name = realisation drv; 150 value = drv; 151 }) targetDerivations 152 ); 153 154 rewriteMemo = 155 # Mind the order of how the three attrsets are merged here. 156 # The order of precedence needs to be "explicitly specified replacements" > "rewrite exclusion (cutoffPackages)" > "rewrite". 157 # So the attrset merge order is the opposite. 158 mapAttrs ( 159 drv: references: 160 let 161 rewrittenReferences = filter (dep: dep != drv && toString rewriteMemo.${dep} != dep) references; 162 rewrites = listToAttrs ( 163 map (reference: { 164 name = reference; 165 value = rewriteMemo.${reference}; 166 }) rewrittenReferences 167 ); 168 in 169 replaceDirectDependencies { 170 drv = storePathOrKnownTargetDerivationMemo.${drv}; 171 replacements = mapAttrsToList (name: value: { 172 oldDependency = name; 173 newDependency = value; 174 }) rewrites; 175 } 176 ) relevantReferences 177 // listToAttrs ( 178 map (drv: { 179 name = realisation drv; 180 value = storePathOrKnownTargetDerivationMemo.${realisation drv}; 181 }) cutoffPackages 182 ) 183 // listToAttrs ( 184 map ( 185 { oldDependency, newDependency }: 186 { 187 name = realisation oldDependency; 188 value = rewriteMemo.${realisation newDependency}; 189 } 190 ) relevantReplacements 191 ); 192in 193rewriteMemo.${realisation drv}