query
On this page

replaceDependencies

pkgs.replaceDependencies

Functor
Docs pulled from | This Revision | 29 minutes ago


Contribute
Enhance the ecosystem with your expertise! Contribute to fill the gaps in documentation. Your input can make a difference.

Noogle detected

This is a Functor

Learn about functors

Implementation

The following is the current implementation of this function.

{
  drv,
  replacements,
  cutoffPackages ? [ ],
  verbose ? true,
}:

let
  inherit (builtins) unsafeDiscardStringContext appendContext;
  inherit (lib)
    listToAttrs
    isStringLike
    readFile
    attrValues
    mapAttrs
    filter
    hasAttr
    mapAttrsToList
    ;
  inherit (lib.attrsets) mergeAttrsList;

  isNonCaStorePath =
    x:
    if isStringLike x then
      let
        str = toString x;
      in
      builtins.substring 0 1 str == "/" && (dirOf str == builtins.storeDir)
    else
      false;

  toContextlessString = x: unsafeDiscardStringContext (toString x);
  warn = if verbose then lib.warn else (x: y: y);

  referencesOf =
    drv:
    import
      (runCommandLocal "references.nix"
        {
          exportReferencesGraph = [
            "graph"
            drv
          ];
        }
        ''
          (echo {
          while read path
          do
              echo "  \"$path\" = ["
              read count
              read count
              while [ "0" != "$count" ]
              do
                  read ref_path
                  if [ "$ref_path" != "$path" ]
                  then
                      echo "    \"$ref_path\""
                  fi
                  count=$(($count - 1))
              done
              echo "  ];"
          done < graph
          echo }) > $out
        ''
      ).outPath;

  realisation =
    drv:
    if isNonCaStorePath 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 "realisation"
            {
              env = {
                inherit drv;
              };
            }
            ''
              echo -n "$drv" > $out
            ''
        )
      );
  rootReferences = referencesOf drv;
  relevantReplacements = filter (
    { oldDependency, newDependency }:
    if toString oldDependency == toString newDependency then
      warn "replaceDependencies: attempting to replace dependency ${oldDependency} of ${drv} with itself"
        # 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 "replaceDependencies: ${drv} does not depend on ${oldDependency}, so it will not be replaced"
        # 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;
  referencesMemo = listToAttrs (
    map (drv: {
      name = realisation drv;
      value = referencesOf drv;
    }) targetDerivations
  );
  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.
  storePathOrKnownTargetDerivationMemo =
    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: {
        name = realisation drv;
        value = drv;
      }) targetDerivations
    );

  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
      replaceDirectDependencies {
        drv = storePathOrKnownTargetDerivationMemo.${drv};
        replacements = mapAttrsToList (name: value: {
          oldDependency = name;
          newDependency = value;
        }) rewrites;
      }
    ) relevantReferences
    // listToAttrs (
      map (drv: {
        name = realisation drv;
        value = storePathOrKnownTargetDerivationMemo.${realisation drv};
      }) cutoffPackages
    )
    // listToAttrs (
      map (
        { oldDependency, newDependency }:
        {
          name = realisation oldDependency;
          value = rewriteMemo.${realisation newDependency};
        }
      ) relevantReplacements
    );
in
rewriteMemo.${realisation drv}