diff --git a/lib/customisation.nix b/lib/customisation.nix index 6159ce8ea27a4..63bea15755aa5 100644 --- a/lib/customisation.nix +++ b/lib/customisation.nix @@ -156,8 +156,25 @@ 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 - mirrorArgs ( + decorate ( origArgs: let result = f origArgs; diff --git a/lib/fetchers.nix b/lib/fetchers.nix index c09dbc7991032..cf9a8033a6580 100644 --- a/lib/fetchers.nix +++ b/lib/fetchers.nix @@ -117,24 +117,31 @@ rec { # All hashes passed in arguments (possibly 0 or >1) as a list of {name, value} pairs let hashesAsNVPairs = attrsToList (intersectAttrs hashSet args); + unspecifiedHash = hashesAsNVPairs == [ ]; + multipleHash = tail hashesAsNVPairs != [ ]; in - if hashesAsNVPairs == [ ] then - throwIf required "fetcher called without `hash`" null - else if tail hashesAsNVPairs != [ ] then - throw "fetcher called with mutually-incompatible arguments: ${ - concatMapStringsSep ", " (a: a.name) hashesAsNVPairs - }" - else - head hashesAsNVPairs; + { + errorMessage = + if unspecifiedHash && required then + "fetcher called without `hash`" + else if multipleHash then + "fetcher called with mutually-incompatible arguments: ${ + concatMapStringsSep ", " (a: a.name) hashesAsNVPairs + }" + else + null; + inherit (head hashesAsNVPairs) name value; + inherit unspecifiedHash; + }; in removeAttrs args hashNames - // (optionalAttrs (h != null) { - outputHashAlgo = if h.name == "hash" then null else h.name; - outputHash = - if h.value == "" then - fakeH.${h.name} or (throw "no “fake hash” defined for ${h.name}") - else - h.value; + // (optionalAttrs (required || !h.unspecifiedHash) { + outputHashAlgo = lib.throwIf (h.errorMessage != null) h.errorMessage ( + if h.name == "hash" then null else h.name + ); + outputHash = lib.throwIf (h.errorMessage != null) h.errorMessage ( + if h.value == "" then fakeH.${h.name} or (throw "no “fake hash” defined for ${h.name}") else h.value + ); }); /** diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index e4f2222b76850..4bf2a215129a7 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -202,6 +202,85 @@ runTests { }; }; + testOverridePreserveFunctionMetadata = + let + toCallableAttrs = f: setFunctionArgs f (functionArgs f); + constructDefinition = + { + a ? 3, + }: + toCallableAttrs ( + { + b ? 5, + }: + { + inherit a b; + } + ) + // { + inherit a; + c = 7; + }; + construct0 = makeOverridable constructDefinition { }; + construct1 = makeOverridable construct0; + construct0p = construct0.override { a = 11; }; + construct1p = construct1.override { a = 11; }; + in + { + expr = { + construct-metadata = { + inherit (construct1) a c; + }; + construct-overridden-metadata = { + v = construct0p.a; + inherit (construct1p) a c; + }; + construct-overridden-result-overrider = { + result-overriders-exist = mapAttrs (_: f: (f { }) ? override) { + inherit construct1 construct1p; + }; + result-overrider-functionality = { + overridden = { + inherit ((construct1p { }).override { b = 13; }) a b; + }; + direct = { + inherit (construct1p { b = 13; }) a b; + }; + v = { + inherit (construct0p { b = 13; }) a b; + }; + }; + }; + }; + expected = { + construct-metadata = { + inherit (construct0) a c; + }; + construct-overridden-metadata = { + v = 11; + inherit (construct0p) a c; + }; + construct-overridden-result-overrider = { + result-overriders-exist = { + construct1 = true; + construct1p = true; + }; + result-overrider-functionality = { + overridden = { + inherit (construct0p { b = 13; }) a b; + }; + direct = { + inherit (construct0p { b = 13; }) a b; + }; + v = { + a = 11; + b = 13; + }; + }; + }; + }; + }; + testCallPackageWithOverridePreservesArguments = let f = diff --git a/pkgs/build-support/fetchgit/builder.sh b/pkgs/build-support/fetchgit/builder.sh index 8f72881d3b911..704f14598deae 100644 --- a/pkgs/build-support/fetchgit/builder.sh +++ b/pkgs/build-support/fetchgit/builder.sh @@ -19,7 +19,7 @@ $SHELL $fetcher --builder --url "$url" --out "$out" --rev "$rev" --name "$name" ${deepClone:+--deepClone} \ ${fetchSubmodules:+--fetch-submodules} \ ${fetchTags:+--fetch-tags} \ - ${sparseCheckout:+--sparse-checkout "$sparseCheckout"} \ + ${sparseCheckoutText:+--sparse-checkout "$sparseCheckoutText"} \ ${nonConeMode:+--non-cone-mode} \ ${branchName:+--branch-name "$branchName"} \ ${rootDir:+--root-dir "$rootDir"} diff --git a/pkgs/build-support/fetchgit/default.nix b/pkgs/build-support/fetchgit/default.nix index 5a8ea3cf04880..a07af2edf4183 100644 --- a/pkgs/build-support/fetchgit/default.nix +++ b/pkgs/build-support/fetchgit/default.nix @@ -35,11 +35,6 @@ let else # FIXME fetching HEAD if no rev or tag is provided is problematic at best "HEAD"; -in - -lib.makeOverridable ( - lib.extendMkDerivation { - constructDrv = stdenvNoCC.mkDerivation; excludeDrvArgNames = [ # Additional stdenv.mkDerivation arguments from derived fetchers. @@ -66,14 +61,17 @@ lib.makeOverridable ( # when rootDir is specified, avoid invalidating the result when rev changes append = if rootDir != "" then "-${lib.strings.sanitizeDerivationName rootDir}" else ""; }, - leaveDotGit ? deepClone || fetchTags, + # When null, will default to: `deepClone || fetchTags` + leaveDotGit ? null, outputHash ? lib.fakeHash, outputHashAlgo ? null, fetchSubmodules ? true, deepClone ? false, branchName ? null, - sparseCheckout ? lib.optional (rootDir != "") rootDir, - nonConeMode ? rootDir != "", + # When null, will default to: `lib.optional (rootdir != "") rootdir` + sparseCheckout ? null, + # When null, will default to: `rootDir != ""` + nonConeMode ? null, nativeBuildInputs ? [ ], # Shell code executed before the file has been fetched. This, in # particular, can do things like set NIX_PREFETCH_GIT_CHECKOUT_HOOK to @@ -128,105 +126,155 @@ lib.makeOverridable ( server admins start using the new version? */ - assert nonConeMode -> (sparseCheckout != [ ]); - assert fetchTags -> leaveDotGit; - assert rootDir != "" -> !leaveDotGit; - - if builtins.isString sparseCheckout then - # Changed to throw on 2023-06-04 - throw - "Please provide directories/patterns for sparse checkout as a list of strings. Passing a (multi-line) string is not supported any more." - else - derivationArgs - // { - inherit name; + derivationArgs + // { + inherit name; - builder = ./builder.sh; - fetcher = ./nix-prefetch-git; + builder = ./builder.sh; + fetcher = ./nix-prefetch-git; - nativeBuildInputs = [ - git - cacert - ] - ++ lib.optionals fetchLFS [ git-lfs ] - ++ nativeBuildInputs; + nativeBuildInputs = [ + git + cacert + ] + ++ lib.optionals fetchLFS [ git-lfs ] + ++ nativeBuildInputs; - inherit outputHash outputHashAlgo; - outputHashMode = "recursive"; + inherit outputHash outputHashAlgo; + outputHashMode = "recursive"; + sparseCheckout = lib.defaultTo (lib.optional (finalAttrs.rootDir != "") finalAttrs.rootDir) sparseCheckout; + sparseCheckoutText = + # Changed to throw on 2023-06-04 + assert ( + lib.assertMsg (lib.isList finalAttrs.sparseCheckout) "Please provide directories/patterns for sparse checkout as a list of strings. Passing a (multi-line) string is not supported any more." + ); + assert finalAttrs.nonConeMode -> (finalAttrs.sparseCheckout != [ ]); # git-sparse-checkout(1) says: # > When the --stdin option is provided, the directories or patterns are read # > from standard in as a newline-delimited list instead of from the arguments. - sparseCheckout = builtins.concatStringsSep "\n" sparseCheckout; + builtins.concatStringsSep "\n" finalAttrs.sparseCheckout; - inherit - url + inherit + url + fetchLFS + fetchSubmodules + deepClone + branchName + preFetch + postFetch + fetchTags + rootDir + gitConfigFile + ; + leaveDotGit = + if leaveDotGit != null then + assert fetchTags -> leaveDotGit; + assert rootDir != "" -> !leaveDotGit; leaveDotGit - fetchLFS - fetchSubmodules - deepClone - branchName - nonConeMode - preFetch - postFetch - fetchTags - rootDir - gitConfigFile - ; - inherit tag; - revCustom = rev; - rev = getRevWithTag { - inherit (finalAttrs) tag; - rev = finalAttrs.revCustom; - }; - - postHook = - if netrcPhase == null then - null - else - '' - ${netrcPhase} - # required that git uses the netrc file - mv {,.}netrc - export NETRC=$PWD/.netrc - export HOME=$PWD - ''; - - impureEnvVars = - lib.fetchers.proxyImpureEnvVars - ++ netrcImpureEnvVars - ++ [ - "GIT_PROXY_COMMAND" - "NIX_GIT_SSL_CAINFO" - "SOCKS_SERVER" - - # This is a parameter intended to be set by setup hooks or preFetch - # scripts that want per-URL control over HTTP proxies used by Git - # (if per-URL control isn't needed, `http_proxy` etc. will - # suffice). It must be a whitespace-separated (with backslash as an - # escape character) list of pairs like this: - # - # http://domain1/path1 proxy1 https://domain2/path2 proxy2 - # - # where the URLs are as documented in the `git-config` manual page - # under `http..*`, and the proxies are as documented on the - # same page under `http.proxy`. - "FETCHGIT_HTTP_PROXIES" - ]; - - inherit preferLocalBuild meta allowedRequisites; - - passthru = { - gitRepoUrl = url; - } - // passthru; + else + deepClone || fetchTags; + nonConeMode = lib.defaultTo (finalAttrs.rootDir != "") nonConeMode; + inherit tag; + revCustom = rev; + rev = getRevWithTag { + inherit (finalAttrs) tag; + rev = finalAttrs.revCustom; + }; + + postHook = + if netrcPhase == null then + null + else + '' + ${netrcPhase} + # required that git uses the netrc file + mv {,.}netrc + export NETRC=$PWD/.netrc + export HOME=$PWD + ''; + + impureEnvVars = + lib.fetchers.proxyImpureEnvVars + ++ netrcImpureEnvVars + ++ [ + "GIT_PROXY_COMMAND" + "NIX_GIT_SSL_CAINFO" + "SOCKS_SERVER" + + # This is a parameter intended to be set by setup hooks or preFetch + # scripts that want per-URL control over HTTP proxies used by Git + # (if per-URL control isn't needed, `http_proxy` etc. will + # suffice). It must be a whitespace-separated (with backslash as an + # escape character) list of pairs like this: + # + # http://domain1/path1 proxy1 https://domain2/path2 proxy2 + # + # where the URLs are as documented in the `git-config` manual page + # under `http..*`, and the proxies are as documented on the + # same page under `http.proxy`. + "FETCHGIT_HTTP_PROXIES" + ]; + + inherit preferLocalBuild meta allowedRequisites; + + passthru = { + gitRepoUrl = url; } + // passthru; + } ); + resolveSpecialArgs = + finalAttrs: + { + deepClone ? false, + fetchTags ? false, + leaveDotGit ? null, + nonConeMode ? null, + rootDir ? "", + sparseCheckout ? null, + ... + }: + { + inherit + deepClone + fetchTags + rootDir + ; + leaveDotGit = + if leaveDotGit != null then + assert fetchTags -> leaveDotGit; + assert rootDir != "" -> !leaveDotGit; + leaveDotGit + else + deepClone || fetchTags; + nonConeMode = lib.defaultTo (finalAttrs.rootDir != "") nonConeMode; + sparseCheckout = lib.defaultTo (lib.optional (finalAttrs.rootDir != "") finalAttrs.rootDir) sparseCheckout; + }; +in + +lib.makeOverridable ( + lib.extendMkDerivation { + constructDrv = stdenvNoCC.mkDerivation; + + inherit excludeDrvArgNames extendDrvArgs; + # No ellipsis. inheritFunctionArgs = false; } ) // { - inherit getRevWithTag; + inherit + getRevWithTag + resolveSpecialArgs + ; + expectDrvArgs = + let + faRaw = lib.functionArgs (extendDrvArgs { }); + in + lib.zipAttrsWith (_: lib.any lib.id) [ + (lib.mapAttrs (_: v: !v) (removeAttrs faRaw excludeDrvArgNames)) + (lib.mapAttrs (_: _: true) (extendDrvArgs { } (faRaw // { derivationArgs = { }; }))) + ]; } diff --git a/pkgs/build-support/fetchgithub/default.nix b/pkgs/build-support/fetchgithub/default.nix index ae5ff338264a2..3654a4326aeb9 100644 --- a/pkgs/build-support/fetchgithub/default.nix +++ b/pkgs/build-support/fetchgithub/default.nix @@ -1,142 +1,117 @@ { lib, - repoRevToNameMaybe, - fetchgit, - fetchzip, + fetchFromGitProvider, }: lib.makeOverridable ( - { - owner, - repo, - tag ? null, - rev ? null, - # TODO(@ShamrockLee): Add back after reconstruction with lib.extendMkDerivation - # name ? repoRevToNameMaybe finalAttrs.repo (lib.revOrTag finalAttrs.revCustom finalAttrs.tag) "github", - fetchSubmodules ? false, - leaveDotGit ? null, - deepClone ? false, - private ? false, - forceFetchGit ? false, - fetchLFS ? false, - rootDir ? "", - sparseCheckout ? lib.optional (rootDir != "") rootDir, - githubBase ? "github.com", - varPrefix ? null, - passthru ? { }, - meta ? { }, - ... # For hash agility - }@args: - - assert ( - lib.assertMsg (lib.xor (tag == null) ( - rev == null - )) "fetchFromGitHub requires one of either `rev` or `tag` to be provided (not both)." - ); - - let - - position = ( - if args.meta.description or null != null then - builtins.unsafeGetAttrPos "description" args.meta - else if tag != null then - builtins.unsafeGetAttrPos "tag" args - else - builtins.unsafeGetAttrPos "rev" args - ); - baseUrl = "https://${githubBase}/${owner}/${repo}"; - newMeta = - meta - // { - homepage = meta.homepage or baseUrl; - } - // lib.optionalAttrs (position != null) { - # to indicate where derivation originates, similar to make-derivation.nix's mkDerivation - position = "${position.file}:${toString position.line}"; + lib.extendMkDerivation { + constructDrv = fetchFromGitProvider.override (previousArgs: { + fetchzip = + # fetchzip may not be overridable when using external tools, for example nix-prefetch + if previousArgs.fetchzip ? override then + previousArgs.fetchzip.override { + withUnzip = false; + } + else + previousArgs.fetchzip; + expectDrvArgsExtra = { + githubBase = true; + owner = true; }; - passthruAttrs = removeAttrs args [ + }); + + excludeDrvArgNames = [ + # Pass via derivationArgs + "githubBase" "owner" - "repo" - "tag" - "rev" - "fetchSubmodules" - "forceFetchGit" + + # Private attributes + # TODO(@ShamrockLee): check if those are still functional. "private" - "githubBase" "varPrefix" ]; - varBase = "NIX${lib.optionalString (varPrefix != null) "_${varPrefix}"}_GITHUB_PRIVATE_"; - useFetchGit = - fetchSubmodules - || (leaveDotGit == true) - || deepClone - || forceFetchGit - || fetchLFS - || (rootDir != "") - || (sparseCheckout != [ ]); - # We prefer fetchzip in cases we don't need submodules as the hash - # is more stable in that case. - fetcher = - if useFetchGit then - fetchgit - # fetchzip may not be overridable when using external tools, for example nix-prefetch - else if fetchzip ? override then - fetchzip.override { withUnzip = false; } - else - fetchzip; - privateAttrs = lib.optionalAttrs private { - netrcPhase = - # When using private repos: - # - Fetching with git works using https://github.com but not with the GitHub API endpoint - # - Fetching a tarball from a private repo requires to use the GitHub API endpoint - let - machineName = if githubBase == "github.com" && !useFetchGit then "api.github.com" else githubBase; - in - '' - if [ -z "''$${varBase}USERNAME" -o -z "''$${varBase}PASSWORD" ]; then - echo "Error: Private fetchFromGitHub requires the nix building process (nix-daemon in multi user mode) to have the ${varBase}USERNAME and ${varBase}PASSWORD env vars set." >&2 - exit 1 - fi - cat > netrc <&2 + exit 1 + fi + cat > netrc <.overrideAttrs` by default, + # and also enable manual specifying useFetchGit via `overrideAttrs`. + enableUseFetchGitFinal ? true, + expectDrvArgsExtra ? { }, +}: +let + accumulateConstructorMetadata = + constructDrv: + let + f = + cM1: + { + excludeDrvArgNames, + extendDrvArgs, + transformDrv, + ... + }@cfg: + if cM1 ? constructDrv then + cfg + // f cM1.constructDrv { + excludeDrvArgNames = excludeDrvArgNames ++ cM1.excludeDrvArgNames; + extendDrvArgs = + finalAttrs: args: + let + args' = extendDrvArgs finalAttrs args; + in + # Also let extendDrvArgs complete the original work of excludeDrvArgNames, + # making the performance close to direct-composition. + removeAttrs args' cM1.excludeDrvArgNames // cM1.extendDrvArgs finalAttrs args'; + transformDrv = drv: transformDrv (cM1.transformDrv drv); + } + else + cfg; + in + f constructDrv.constructDrv ( + constructDrv + // { + extendDrvArgs = + finalAttrs: args: + removeAttrs args constructDrv.excludeDrvArgNames // constructDrv.extendDrvArgs finalAttrs args; + } + ); + + expectDrvArgs = lib.zipAttrsWith (_: lib.any lib.id) [ + expectDrvArgsExtra + { + repo = true; + rev = true; + tag = true; + useFetchGit = true; + } + faUseFetchGit + (lib.zipAttrsWith (_: lib.all lib.id) [ + fetchgit.expectDrvArgs + fetchzip.expectDrvArgs + ]) + ]; + + # Here defines fetchFromGitHub arguments that determines useFetchGit, + # The attribute value is their default values. + # As fetchFromGitHub prefers fetchzip for hash stability, + # `defaultFetchGitArgs` attributes should lead to `useFetchGit = false`. + useFetchGitArgsDefault = { + fetchSubmodules = false; # This differs from fetchgit's default + leaveDotGit = null; + deepClone = false; + forceFetchGit = false; + fetchLFS = false; + rootDir = ""; + sparseCheckout = null; + }; + useFetchGitArgsDefaultNullable = { + leaveDotGit = false; + sparseCheckout = [ ]; + }; + + useFetchGitArgsDefaultNonNull = useFetchGitArgsDefault // useFetchGitArgsDefaultNullable; + useFetchGitArgsDefaultPassing = removeAttrs useFetchGitArgsDefault excludeUseFetchGitArgNames; + + excludeUseFetchGitArgNames = [ + "forceFetchGit" + ]; + + faUseFetchGit = lib.mapAttrs (_: _: true) useFetchGitArgsDefault; + + faFetchGitSpecific = removeAttrs (lib.functionArgs fetchgit) ( + lib.attrNames (faUseFetchGit // lib.functionArgs fetchzip) + ++ [ + "sha256" + "outputHash" + "outputHashAlgo" + ] + ); + faFetchZipSpecific = removeAttrs (lib.functionArgs fetchzip) ( + lib.attrNames (faUseFetchGit // lib.functionArgs fetchgit) + ++ [ + "sha256" + "outputHash" + "outputHashAlgo" + ] + ); + + excludeDrvArgNamesShared = [ + "providerName" + "repo" + "rev" + "tag" + "gitRepoUrl" + "useFetchGit" + "fetchgitArgs" + "fetchzipArgs" + ]; + + mirrorArgs = f: lib.setFunctionArgs f (lib.functionArgs (extendDrvArgsShared { }) // faUseFetchGit); + + # We prefer fetchzip in cases we don't need submodules as the hash + # is more stable in that case. + choices = { + fetchgit = accumulateConstructorMetadata ( + lib.extendMkDerivation { + constructDrv = fetchgit; + excludeDrvArgNames = + excludeUseFetchGitArgNames ++ lib.attrNames faFetchZipSpecific ++ excludeDrvArgNamesShared; + extendDrvArgs = finalAttrs: args: (extendDrvArgsShared finalAttrs args).fetchgitArgs; + } + ); + fetchzip = accumulateConstructorMetadata ( + lib.extendMkDerivation { + constructDrv = fetchzip; + excludeDrvArgNames = + lib.attrNames (useFetchGitArgsDefault // faFetchGitSpecific) ++ excludeDrvArgNamesShared; + extendDrvArgs = finalAttrs: args: (extendDrvArgsShared finalAttrs args).fetchzipArgs; + } + ); + }; + + excludeDrvArgNamesUnion = lib.uniqueStrings ( + choices.fetchgit.excludeDrvArgNames ++ choices.fetchzip.excludeDrvArgNames + ); + + extendDrvArgsShared = + finalAttrs: + { + name ? + repoRevToNameMaybe finalAttrs.repo (lib.revOrTag finalAttrs.revCustom finalAttrs.tag) + providerName, + derivationArgs ? { }, + passthru ? { }, + meta ? { }, + providerName ? "git-provider", + repo, + rev ? null, + tag ? null, + gitRepoUrl, + fetchgitArgs ? { }, + fetchzipArgs, + ... + }@args: + { + fetchgitArgs = + fetchgitArgs + // { + name = fetchgitArgs.name or name; + inherit tag rev; + url = gitRepoUrl; + derivationArgs = { + inherit repo; + } + // derivationArgs + // fetchgitArgs.derivationArgs or { }; + passthru = passthru // fetchgitArgs.passthru or { }; + meta = meta // fetchgitArgs.meta or { }; + } + // lib.overrideExisting useFetchGitArgsDefaultPassing args; + fetchzipArgs = fetchzipArgs // { + name = fetchzipArgs.name or name; + derivationArgs = { + inherit + repo + tag + ; + rev = fetchgit.getRevWithTag { + inherit (finalAttrs) tag; + rev = finalAttrs.revCustom; + }; + revCustom = rev; + } + // derivationArgs + // fetchzipArgs.derivationArgs or { }; + passthru = { + inherit gitRepoUrl; + } + // passthru + // fetchzipArgs.passthru or { }; + meta = meta // fetchzipArgs.meta or { }; + }; + }; +in +lib.extendMkDerivation { + constructDrv = stdenvNoCC.mkDerivation; + + excludeDrvArgNames = excludeDrvArgNamesUnion; + + extendDrvArgs = + finalAttrs: + mirrorArgs ( + args: + let + useFetchGitCurrent = + lib.mapAttrs ( + n: default: + if !(args ? ${n}) || (useFetchGitArgsDefaultNullable ? ${n} && args.${n} == null) then + default + else + args.${n} + ) useFetchGitArgsDefaultNonNull != useFetchGitArgsDefaultNonNull; + # accumulateConstructorMetadata make `extendDrvArgs` also produce + # the `removeAttrs args excludeDrvArgNames` result, + # so we don't need to do it again. + fetchgitDerivationArgs = choices.fetchgit.extendDrvArgs finalAttrs args; + fetchzipDerivationArgs = choices.fetchzip.extendDrvArgs finalAttrs args; + fetchgitSpecialArgs = fetchgit.resolveSpecialArgs finalAttrs args; + useFetchGitFinal = if enableUseFetchGitFinal then finalAttrs.useFetchGit else useFetchGitCurrent; + newDerivationArgsChosen = + if useFetchGitFinal then fetchgitDerivationArgs else fetchzipDerivationArgs; + faForbidden = removeAttrs ( + if useFetchGitCurrent then faFetchZipSpecific else faFetchGitSpecific + ) excludeDrvArgNamesShared; + forbiddenArgs = lib.filterAttrs (n: v: args.${n} or null != null) faForbidden; + forbiddenArgsThrown = + let + forbiddenArgNamesConcatenated = lib.concatStringsSep ", " (lib.attrNames forbiddenArgs); + thisBackendName = if useFetchGitCurrent then "fetchgit" else "fetchzip"; + otherBackendName = if useFetchGitCurrent then "fetchzip" else "fetchgit"; + message = '' + fetchGitProvider: Unexpected arguments for ${thisBackendName}: ${forbiddenArgNamesConcatenated} + Adjust arguments to use the ${otherBackendName} backend instead. + ''; + in + lib.mapAttrs (n: throw message) forbiddenArgs; + in + forbiddenArgsThrown + // ( + if enableUseFetchGitFinal then + # The final useFetchGitArgs extracted from fetchgitDerivationArgs + # causes FODs that uses the `fetchzip` backend + # to allocate both fetchgitDerivationArgs and fetchzipDerivationArgs + # due to the attribute names being always stict. + # This is why statically determining newDerivationArgsMixed does not optimise the evaluation. + # + # If we land the Nix features proposed in PR NixOS/nix#4090, + # that unstrict `a` in `(a // b).`, + # we could adjust `fetchgit` to group the attributes needed by `useFetchGitArgs` after the rest of attributes. + # Together with the static determination of newDerivationArgsMixed, + # we could make the performance overhead of aggressive dynamic switching negligible + # and enable it by default. + lib.mapAttrs ( + n: _: + if useFetchGitArgsDefault ? ${n} then + # The final useFetchGitArgs produced by fetchgit. + fetchgitSpecialArgs.${n} or useFetchGitArgsDefaultNonNull.${n} + else + newDerivationArgsChosen.${n} or null + ) expectDrvArgs + // lib.overrideExisting (lib.getAttrs excludeUseFetchGitArgNames useFetchGitArgsDefault) args + // { + useFetchGit = + (lib.mapAttrs (n: _: finalAttrs.${n}) useFetchGitArgsDefaultNonNull) + != useFetchGitArgsDefaultNonNull; + } + else + newDerivationArgsChosen // { useFetchGit = useFetchGitCurrent; } + ) + ); + + transformDrv = + drv: + drv.overrideAttrs ( + finalAttrs: previousAttrs: { + # Add rev xor tag assertion + # This rev is revOrTag + rev = + lib.throwIfNot (lib.xor (finalAttrs.tag or null == null) (finalAttrs.revCustom or null == null)) + '' + fetchFromGitProvider requires one of either `rev` or `tag` to be provided (not both). + For `rev` overriding, override `revCustom` in `.overrideAttrs`. + '' + previousAttrs.rev; + } + ); +} +// { + inherit expectDrvArgs; +} diff --git a/pkgs/build-support/fetchurl/default.nix b/pkgs/build-support/fetchurl/default.nix index 5ffb70e3a9f8a..d0e1007b76017 100644 --- a/pkgs/build-support/fetchurl/default.nix +++ b/pkgs/build-support/fetchurl/default.nix @@ -51,11 +51,6 @@ let ] ++ (map (site: "NIX_MIRRORS_${site}") sites); -in - -lib.extendMkDerivation { - constructDrv = stdenvNoCC.mkDerivation; - excludeDrvArgNames = [ # Passed via passthru "url" @@ -329,6 +324,26 @@ lib.extendMkDerivation { // passthru; }; +in + +lib.extendMkDerivation { + constructDrv = stdenvNoCC.mkDerivation; + + inherit + excludeDrvArgNames + extendDrvArgs + ; + # No ellipsis inheritFunctionArgs = false; } +// { + expectDrvArgs = + let + faRaw = lib.functionArgs (extendDrvArgs { }); + in + lib.zipAttrsWith (n: lib.any lib.id) [ + (lib.mapAttrs (n: v: !v) (removeAttrs faRaw excludeDrvArgNames)) + (lib.mapAttrs (n: v: true) (extendDrvArgs { } (faRaw // { derivationArgs = { }; }))) + ]; +} diff --git a/pkgs/build-support/fetchzip/default.nix b/pkgs/build-support/fetchzip/default.nix index 4376fef410854..7f2657b95ee2e 100644 --- a/pkgs/build-support/fetchzip/default.nix +++ b/pkgs/build-support/fetchzip/default.nix @@ -14,17 +14,7 @@ glibcLocalesUtf8, }: -lib.extendMkDerivation { - constructDrv = fetchurl; - - excludeDrvArgNames = [ - "extraPostFetch" - - # Pass via derivationArgs - "extension" - "stripRoot" - ]; - +let extendDrvArgs = finalAttrs: { @@ -122,4 +112,25 @@ lib.extendMkDerivation { ; }; }; +in +lib.extendMkDerivation { + constructDrv = fetchurl; + + excludeDrvArgNames = [ + "extraPostFetch" + + # Pass via derivationArgs + "extension" + "stripRoot" + ]; + + inherit extendDrvArgs; +} +// { + expectDrvArgs = lib.zipAttrsWith (_: lib.any lib.id) [ + (lib.mapAttrs (n: _: true) + (extendDrvArgs { } (lib.functionArgs extendDrvArgs // { derivationArgs = { }; })).derivationArgs + ) + fetchurl.expectDrvArgs + ]; } diff --git a/pkgs/stdenv/generic/make-derivation.nix b/pkgs/stdenv/generic/make-derivation.nix index d00df6ff4139c..cbd3dfe49bf69 100644 --- a/pkgs/stdenv/generic/make-derivation.nix +++ b/pkgs/stdenv/generic/make-derivation.nix @@ -17,6 +17,7 @@ let elemAt extendDerivation filter + filterAttrs findFirst getDev head @@ -715,8 +716,12 @@ let ]; } // ( + let + attrsOutputChecks = makeOutputChecks attrs; + attrsOutputChecksFiltered = filterAttrs (_: v: v != null) attrsOutputChecks; + in if !__structuredAttrs then - makeOutputChecks attrs + attrsOutputChecks else { outputChecks = builtins.listToAttrs ( @@ -725,7 +730,7 @@ let value = let raw = zipAttrsWith (_: builtins.concatLists) [ - (makeOutputChecks attrs) + attrsOutputChecksFiltered (makeOutputChecks attrs.outputChecks.${name} or { }) ]; in diff --git a/pkgs/test/overriding.nix b/pkgs/test/overriding.nix index a2221183a8a6f..d0ca30dec2cc4 100644 --- a/pkgs/test/overriding.nix +++ b/pkgs/test/overriding.nix @@ -62,6 +62,16 @@ let }).pname; expected = "hello-no-final-attrs-overridden"; }; + structuredAttrs-allowedRequisites-nullablility = { + expr = + lib.hasPrefix builtins.storeDir + (pkgs.stdenv.mkDerivation { + __structuredAttrs = true; + inherit (pkgs.hello) pname version src; + allowedRequisites = null; + }).drvPath; + expected = true; + }; }; test-extendMkDerivation = diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 870df36051cad..064e2536d4697 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -659,6 +659,8 @@ with pkgs; fetchCrate = callPackage ../build-support/rust/fetchcrate.nix { }; + fetchFromGitProvider = callPackage ../build-support/fetchgitprovider { }; + fetchFromGitea = callPackage ../build-support/fetchgitea { }; fetchFromGitHub = callPackage ../build-support/fetchgithub { };