2023-09-24 18:32:50 +00:00
{
lib ,
2024-01-12 09:14:38 +00:00
runCommandLocal ,
replaceDirectDependencies ,
2023-09-24 18:32:50 +00:00
} :
# 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.
# This can be useful, for example, to patch a security hole in libc and still use your system safely without rebuilding the world.
# This should be a short term solution, as soon as a rebuild can be done the properly rebuilt derivation should be used.
# Each old dependency and the corresponding new dependency MUST have the same-length name, and ideally should have close-to-identical directory layout.
#
# Example: safeFirefox = replaceDependencies {
# drv = firefox;
# replacements = [
# {
# oldDependency = glibc;
# newDependency = glibc.overrideAttrs (oldAttrs: {
# patches = oldAttrs.patches ++ [ ./fix-glibc-hole.patch ];
# });
# }
# {
# oldDependency = libwebp;
# newDependency = libwebp.overrideAttrs (oldAttrs: {
# patches = oldAttrs.patches ++ [ ./fix-libwebp-hole.patch ];
# });
# }
# ];
# };
# This will first rebuild glibc and libwebp with your security patches.
# Then it copies over firefox (and all of its dependencies) without rebuilding further.
# In particular, the glibc dependency of libwebp will be replaced by the patched version as well.
#
# In rare cases, it is possible for the replacement process to cause breakage (for example due to checksum mismatch).
# The cutoffPackages argument can be used to exempt the problematic packages from the replacement process.
{
drv ,
replacements ,
cutoffPackages ? [ ] ,
verbose ? true ,
} :
let
inherit ( builtins ) unsafeDiscardStringContext appendContext ;
inherit ( lib )
listToAttrs
2024-01-27 10:59:26 +00:00
isStorePath
readFile
2023-09-24 18:32:50 +00:00
attrValues
mapAttrs
filter
hasAttr
2024-01-12 09:14:38 +00:00
mapAttrsToList
2023-09-24 18:32:50 +00:00
;
inherit ( lib . attrsets ) mergeAttrsList ;
toContextlessString = x : unsafeDiscardStringContext ( toString x ) ;
2024-06-07 16:35:36 +00:00
warn = if verbose then lib . warn else ( x : y : y ) ;
2023-09-24 18:32:50 +00:00
referencesOf =
drv :
import
( runCommandLocal " r e f e r e n c e s . n i x "
{
exportReferencesGraph = [
" g r a p h "
drv
] ;
}
''
( echo {
while read path
do
echo " \" $ p a t h \" = [ "
read count
read count
while [ " 0 " != " $ c o u n t " ]
do
read ref_path
if [ " $ r e f _ p a t h " != " $ p a t h " ]
then
echo " \" $ r e f _ p a t h \" "
fi
count = $ ( ( $ count - 1 ) )
done
echo " ] ; "
done < graph
echo } ) > $ out
''
) . outPath ;
2024-01-27 10:59:26 +00:00
realisation =
drv :
if isStorePath drv then
# Input-addressed and fixed-output derivations have their realisation as outPath.
toContextlessString drv
else
# Floating and deferred derivations have a placeholder outPath.
# The realisation can only be obtained by performing an actual build.
unsafeDiscardStringContext (
readFile (
runCommandLocal " r e a l i s a t i o n "
{
env = {
inherit drv ;
} ;
}
''
echo - n " $ d r v " > $ out
''
)
) ;
2024-06-07 16:52:45 +00:00
rootReferences = referencesOf drv ;
relevantReplacements = filter (
{ oldDependency , newDependency }:
if toString oldDependency == toString newDependency then
warn " r e p l a c e D e p e n d e n c i e s : a t t e m p t i n g t o r e p l a c e d e p e n d e n c y ${ oldDependency } o f ${ drv } w i t h i t s e l f "
# Attempting to replace a dependency by itself is completely useless, and would only lead to infinite recursion.
# Hence it must not be attempted to apply this replacement in any case.
false
else if ! hasAttr ( realisation oldDependency ) rootReferences then
warn " r e p l a c e D e p e n d e n c i e s : ${ drv } d o e s n o t d e p e n d o n ${ oldDependency } , s o i t w i l l n o t b e r e p l a c e d "
# Strictly speaking, another replacement could introduce the dependency.
# However, handling this corner case would add significant complexity.
# So we just leave it to the user to apply the replacement at the correct place, but show a warning to let them know.
false
else
true
) replacements ;
targetDerivations = [ drv ] ++ map ( { newDependency , . . . }: newDependency ) relevantReplacements ;
2023-09-24 18:32:50 +00:00
referencesMemo = listToAttrs (
map ( drv : {
2024-01-27 10:59:26 +00:00
name = realisation drv ;
2023-09-24 18:32:50 +00:00
value = referencesOf drv ;
2024-01-27 10:59:26 +00:00
} ) targetDerivations
2023-09-24 18:32:50 +00:00
) ;
relevantReferences = mergeAttrsList ( attrValues referencesMemo ) ;
# Make sure a derivation is returned even when no replacements are actually applied.
# Yes, even in the stupid edge case where the root derivation itself is replaced.
2024-01-27 10:59:26 +00:00
storePathOrKnownTargetDerivationMemo =
2023-09-24 18:32:50 +00:00
mapAttrs (
drv : _references :
# builtins.storePath does not work in pure evaluation mode, even though it is not impure.
# This reimplementation in Nix works as long as the path is already allowed in the evaluation state.
# This is always the case here, because all paths come from the closure of the original derivation.
appendContext drv { ${ drv } . path = true ; }
) relevantReferences
// listToAttrs (
map ( drv : {
2024-01-27 10:59:26 +00:00
name = realisation drv ;
2023-09-24 18:32:50 +00:00
value = drv ;
2024-01-27 10:59:26 +00:00
} ) targetDerivations
2023-09-24 18:32:50 +00:00
) ;
rewriteMemo =
# Mind the order of how the three attrsets are merged here.
# The order of precedence needs to be "explicitly specified replacements" > "rewrite exclusion (cutoffPackages)" > "rewrite".
# So the attrset merge order is the opposite.
mapAttrs (
drv : references :
let
rewrittenReferences = filter ( dep : dep != drv && toString rewriteMemo . ${ dep } != dep ) references ;
rewrites = listToAttrs (
map ( reference : {
name = reference ;
value = rewriteMemo . ${ reference } ;
} ) rewrittenReferences
) ;
in
2024-01-12 09:14:38 +00:00
replaceDirectDependencies {
2024-01-27 10:59:26 +00:00
drv = storePathOrKnownTargetDerivationMemo . ${ drv } ;
2024-01-12 09:14:38 +00:00
replacements = mapAttrsToList ( name : value : {
oldDependency = name ;
newDependency = value ;
} ) rewrites ;
}
2023-09-24 18:32:50 +00:00
) relevantReferences
// listToAttrs (
map ( drv : {
2024-01-27 10:59:26 +00:00
name = realisation drv ;
value = storePathOrKnownTargetDerivationMemo . ${ realisation drv } ;
2023-09-24 18:32:50 +00:00
} ) cutoffPackages
)
// listToAttrs (
map (
{ oldDependency , newDependency }:
{
2024-01-27 10:59:26 +00:00
name = realisation oldDependency ;
value = rewriteMemo . ${ realisation newDependency } ;
2023-09-24 18:32:50 +00:00
}
) relevantReplacements
) ;
in
2024-01-27 10:59:26 +00:00
rewriteMemo . ${ realisation drv }