Unverified Commit d474d1d2 authored by Alyssa Ross's avatar Alyssa Ross Committed by GitHub
Browse files

lib/customization: various performance improvements (#515116)

parents b599111c 0b29c328
Loading
Loading
Loading
Loading
+134 −151
Original line number Diff line number Diff line
@@ -6,6 +6,8 @@ let
    unsafeGetAttrPos
    ;
  inherit (lib)
    all
    attrValues
    functionArgs
    isFunction
    mirrorFunctionArgs
@@ -19,8 +21,6 @@ let
    sortOn
    take
    length
    filterAttrs
    flip
    head
    pipe
    isDerivation
@@ -98,19 +98,18 @@ rec {
  */
  overrideDerivation =
    drv: f:
    let
      newDrv = derivation (drv.drvAttrs // (f drv));
    in
    flip (extendDerivation (seq drv.drvPath true)) newDrv (
    (extendDerivation (seq drv.drvPath true)) (
      {
        meta = drv.meta or { };
        passthru = if drv ? passthru then drv.passthru else { };
        passthru = drv.passthru or { };
      }
      // (drv.passthru or { })
      // optionalAttrs (drv ? __spliced) {
        __spliced = { } // (mapAttrs (_: sDrv: overrideDerivation sDrv f) drv.__spliced);
      // {
        ${if drv ? __spliced then "__spliced" else null} = mapAttrs (
          _: sDrv: overrideDerivation sDrv f
        ) drv.__spliced;
      }
    );
    ) (derivation (drv.drvAttrs // (f drv)));

  /**
    `makeOverridable` takes a function from attribute set to attribute set and
@@ -155,32 +154,12 @@ rec {
    let
      # Creates a functor with the same arguments as f
      mirrorArgs = mirrorFunctionArgs f;
      # Recover overrider and additional attributes for f
      # When f is a callable attribute set,
      # it may contain its own `f.override` and additional attributes.
      # This helper function recovers those attributes and decorate the overrider.
      recoverMetadata =
        if isAttrs f then
          fDecorated:
          # Preserve additional attributes for f
          f
          // fDecorated
          # Decorate f.override if presented
          // lib.optionalAttrs (f ? override) {
            override = fdrv: makeOverridable (f.override fdrv);
          }
        else
          id;
      decorate = f': recoverMetadata (mirrorArgs f');
    in
    decorate (

      f' =
        origArgs:
        let
          result = f origArgs;

        # Changes the original arguments with (potentially a function that returns) a set of new attributes
        overrideWith = newArgs: origArgs // (if isFunction newArgs then newArgs origArgs else newArgs);

          # Re-call the function but with different arguments
          overrideArgs = mirrorArgs (
            /**
@@ -190,16 +169,15 @@ rec {

              This function was provided by `lib.makeOverridable`.
            */
          newArgs: makeOverridable f (overrideWith newArgs)
            newArgs: makeOverridable f (origArgs // (if isFunction newArgs then newArgs origArgs else newArgs))
          );
        # Change the result of the function call by applying g to it
        overrideResult = g: makeOverridable (mirrorArgs (args: g (f args))) origArgs;
        in
        if isAttrs result then
          result
          // {
            override = overrideArgs;
          overrideDerivation = fdrv: overrideResult (x: overrideDerivation x fdrv);
            overrideDerivation =
              fdrv: makeOverridable (mirrorArgs (args: overrideDerivation (f args) fdrv)) origArgs;
            ${if result ? overrideAttrs then "overrideAttrs" else null} =
              /**
                Override the attributes that were passed to `mkDerivation` in order to generate this derivation.
@@ -211,7 +189,7 @@ rec {
              */
              # NOTE: part of the above documentation had to be duplicated in `mkDerivation`'s `overrideAttrs`.
              #       design/tech debt issue: https://github.com/NixOS/nixpkgs/issues/273815
            fdrv: overrideResult (x: x.overrideAttrs fdrv);
              fdrv: makeOverridable (mirrorArgs (args: (f args).overrideAttrs fdrv)) origArgs;
          }
        else if isFunction result then
          # Transform the result into a functor while propagating its arguments
@@ -220,8 +198,23 @@ rec {
            override = overrideArgs;
          }
        else
        result
    );
          result;
    in
    # Recover overrider and additional attributes for f
    # When f is a callable attribute set,
    # it may contain its own `f.override` and additional attributes.
    # This recovers those attributes and decorates the overrider.
    if isAttrs f then
      # Preserve additional attributes for f
      f
      // (mirrorArgs f')
      # Decorate f.override if presented
      // {
        ${if f ? override then "override" else null} = fdrv: makeOverridable (f.override fdrv);
      }

    else
      mirrorArgs f';

  /**
    Call the package function in the file `fn` with the required
@@ -272,25 +265,16 @@ rec {
    ```
  */
  callPackageWith =
    autoArgs: fn: args:
    let
      f = if isFunction fn then fn else import fn;
      fargs = functionArgs f;

      # All arguments that will be passed to the function
      # This includes automatic ones and ones passed explicitly
      allArgs = intersectAttrs fargs autoArgs // args;

      # a list of argument names that the function requires, but
      # wouldn't be passed to it
      missingArgs =
        # Filter out arguments that have a default value
        (
          filterAttrs (name: value: !value)
            # Filter out arguments that would be passed
            (removeAttrs fargs (attrNames allArgs))
      makeErrorMessage =
        autoArgs: fn: args: fargs: unpassedArgs:
        let
          # The first missing arg
          arg = head (
            # Filter out the default args. We did a similar computation in the
            # happy path, but we're okay recomputing it in an error case
            filter (name: !fargs.${name}) (attrNames unpassedArgs)
          );

          # Get a list of suggested argument names for a given missing one
          getSuggestions =
            arg:
@@ -316,27 +300,34 @@ rec {
            else
              ", did you mean ${concatStringsSep ", " (lib.init suggestions)} or ${lib.last suggestions}?";

      errorForArg =
        arg:
        let
          loc = unsafeGetAttrPos arg fargs;
          loc' = if loc != null then loc.file + ":" + toString loc.line else "<unknown location>";
        in
        "Function called without required argument \"${arg}\" at "
        + "${loc'}${prettySuggestions (getSuggestions arg)}";
        "lib.customisation.callPackageWith: Function called without required argument \"${arg}\" at ${loc'}${prettySuggestions (getSuggestions arg)}";
    in
    autoArgs: fn: args:
    let
      f = if isFunction fn then fn else import fn;
      fargs = functionArgs f;

      # Only show the error for the first missing argument
      error = errorForArg (head (attrNames missingArgs));
      # All arguments that will be passed to the function
      # This includes automatic ones and ones passed explicitly
      allArgs = intersectAttrs fargs autoArgs // args;

      # arguments that weren't passed automatically to the function
      unpassedArgs = removeAttrs fargs (attrNames allArgs);

    in
    if missingArgs == { } then
    # if nonempty, check if the function has defaults for those other args
    if unpassedArgs == { } || all (value: value) (attrValues unpassedArgs) then
      makeOverridable f allArgs
    else
      # Only show the error for the first missing argument
      # This needs to be an abort so it can't be caught with `builtins.tryEval`,
      # which is used by nix-env and ofborg to filter out packages that don't evaluate.
      # This way we're forced to fix such errors in Nixpkgs,
      # which is especially relevant with allowAliases = false
    else
      abort "lib.customisation.callPackageWith: ${error}";
      abort (makeErrorMessage autoArgs fn args fargs unpassedArgs);

  /**
    Like `callPackage`, but for a function that returns an attribute
@@ -409,16 +400,12 @@ rec {
  extendDerivation =
    condition: passthru: drv:
    let
      outputs = drv.outputs or [ "out" ];

      commonAttrs =
        drv // (listToAttrs outputsList) // { all = map (x: x.value) outputsList; } // passthru;

      outputToAttrListElement = outputName: {
      outputsList = map (outputName: {
        name = outputName;
        value =
          commonAttrs
          // {
        value = commonAttrs // {
          inherit (drv.${outputName}) type outputName;
          outputSpecified = true;
          drvPath =
@@ -427,18 +414,14 @@ rec {
          outPath =
            assert condition;
            drv.${outputName}.outPath;
          }
          //
          # TODO: give the derivation control over the outputs.
          #       `overrideAttrs` may not be the only attribute that needs
          #       updating when switching outputs.
            optionalAttrs (passthru ? overrideAttrs) {
          # TODO: also add overrideAttrs when overrideAttrs is not custom, e.g. when not splicing.
              overrideAttrs = f: (passthru.overrideAttrs f).${outputName};
          ${if passthru ? overrideAttrs then "overrideAttrs" else null} =
            f: (passthru.overrideAttrs f).${outputName};
        };
      };

      outputsList = map outputToAttrListElement outputs;
      }) (drv.outputs or [ "out" ]);
    in
    commonAttrs
    // {