From ac177dff933cfa8de4ba87a7367e0efe80877958 Mon Sep 17 00:00:00 2001 From: nicoo Date: Sun, 15 Sep 2024 13:33:50 +0000 Subject: [PATCH] lib.fetchers: add `normalizeHash` and `withNormalizedHash` --- lib/fetchers.nix | 144 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 143 insertions(+), 1 deletion(-) diff --git a/lib/fetchers.nix b/lib/fetchers.nix index b2fe7872a12b..20e2914db919 100644 --- a/lib/fetchers.nix +++ b/lib/fetchers.nix @@ -1,6 +1,6 @@ # snippets that can be shared by multiple fetchers (pkgs/build-support) { lib }: -{ +rec { proxyImpureEnvVars = [ # We borrow these environment variables from the caller to allow @@ -14,4 +14,146 @@ "NIX_SSL_CERT_FILE" ]; + /** + Converts an attrset containing one of `hash`, `sha256` or `sha512`, + into one containing `outputHash{,Algo}` as accepted by `mkDerivation`. + + All other attributes in the set remain as-is. + + # Example + + ```nix + normalizeHash { } { hash = lib.fakeHash; foo = "bar"; } + => + { + outputHash = lib.fakeHash; + outputHashAlgo = null; + foo = "bar"; + } + ``` + + ```nix + normalizeHash { } { sha256 = lib.fakeSha256; } + => + { + outputHash = lib.fakeSha256; + outputHashAlgo = "sha256"; + } + ``` + + ```nix + normalizeHash { } { sha512 = lib.fakeSha512; } + => + { + outputHash = lib.fakeSha512; + outputHashAlgo = "sha512"; + } + ``` + + # Type + ``` + normalizeHash :: { hashTypes :: List String, required :: Bool } -> AttrSet -> AttrSet + ``` + + # Arguments + + hashTypes + : the set of attribute names accepted as hash inputs, in addition to `hash` + + required + : whether to throw if no hash was present in the input; otherwise returns the original input, unmodified + */ + normalizeHash = { + hashTypes ? [ "sha256" ], + required ? true, + }: args: + with builtins; with lib; + let + hNames = [ "hash" ] ++ hashTypes; + + # The argument hash, as a {name, value} pair + h = + let _h = attrsToList (intersectAttrs (genAttrs hNames (const {})) args); in + if _h == [] then + throw "fetcher called without `hash`" + else if tail _h != [] then + throw "fetcher called with mutually-incompatible arguments: ${concatMapStringsSep ", " (a: a.name) _h}" + else + head _h + ; + in + if args ? "outputHash" then + args + else + removeAttrs args hNames // { + outputHash = h.value; + outputHashAlgo = if h.name == "hash" then null else h.name; + } + ; + + /** + Wraps a function which accepts `outputHash{,Algo}` into one which accepts `hash` or `sha{256,512}` + + # Example + ```nix + withNormalizedHash { hashTypes = [ "sha256" "sha512" ]; } ( + { outputHash, outputHashAlgo, ... }: + ... + ) + ``` + is a function which accepts one of `hash`, `sha256`, or `sha512` (or the original's `outputHash` and `outputHashAlgo`). + + Its `functionArgs` metadata only lists `hash` as a parameter, optional iff. `outputHash` was an optional parameter of + the original function. `sha256`, `sha512`, `outputHash`, or `outputHashAlgo` are not mentioned in the `functionArgs` + metadata. + + # Type + ``` + withNormalizedHash :: { hashTypes :: List String } -> (AttrSet -> T) -> (AttrSet -> T) + ``` + + # Arguments + + hashTypes + : the set of attribute names accepted as hash inputs, in addition to `hash` + : they must correspond to a valid value for `outputHashAlgo`, currently one of: `md5`, `sha1`, `sha256`, or `sha512`. + + f + : the function to be wrapped + + ::: {.note} + In nixpkgs, `mkDerivation` rejects MD5 `outputHash`es, and SHA-1 is being deprecated. + + As such, there is no reason to add `md5` to `hashTypes`, and + `sha1` should only ever be included for backwards compatibility. + ::: + + # Output + + `withNormalizedHash { inherit hashTypes; } f` is functionally equivalent to + ```nix + args: f (normalizeHash { + inherit hashTypes; + required = !(lib.functionArgs f).outputHash; + } args) + ``` + + However, `withNormalizedHash` preserves `functionArgs` metadata insofar as possible, + and is implemented somewhat more efficiently. + */ + withNormalizedHash = { + hashTypes ? [ "sha256" ] + }: fetcher: + with builtins; with lib; + let + hAttrs = genAttrs ([ "hash" ] ++ hashTypes) (const {}); + fArgs = functionArgs fetcher; + in + # The o.g. fetcher must *only* accept outputHash and outputHashAlgo + assert !fArgs.outputHash && !fArgs.outputHashAlgo; + assert intersectAttrs fArgs hAttrs == {}; + + setFunctionArgs + (args: fetcher (normalizeHash { inherit hashTypes; } args)) + (removeAttrs fArgs [ "outputHash" "outputHashAlgo" ] // { hash = false; }); }