lib, treewide: introduce `repoRevToName`, use it in most `fetch*` functions

This patch adds `lib.repoRevToName` function that generalizes away most of the
code used for derivation name generation by `fetch*` functions (`fetchzip`,
`fetchFromGitHub`, etc, except those which are delayed until latter commits
for mass-rebuild reasons).

It's first argument controls how the resulting name will look (see below).

Since `lib` has no equivalent of Nixpkgs' `config`, this patch adds
`config.fetchedSourceNameDefault` option to Nixpkgs and then re-exposes
`lib.repoRevToName config.fetchedSourceNameDefault` expression as
`pkgs.repoRevToNameMaybe` which is then used in `fetch*` derivations.

The result is that different values of `config.fetchedSourceNameDefault` now
control how the `src` derivations produced by `fetch*` functions are to be
named, e.g.:

- `fetchedSourceNameDefault = "source"` (the default):

```
$ nix-instantiate -A fuse.src
/nix/store/<hash>-source.drv
```

- `fetchedSourceNameDefault = "versioned"`:

```
$ nix-instantiate -A fuse.src
/nix/store/<hash>-libfuse-2.9.9-source.drv
```

- `fetchedSourceNameDefault = "full"`:

```
$ nix-instantiate -A fuse.src
/nix/store/<hash>-libfuse-2.9.9-github-source.drv
```

See the documentation of `config.fetchedSourceNameDefault` for more info.

Changed files
+193 -26
lib
pkgs
build-support
fetchbitbucket
fetchgit
fetchgithub
fetchgitiles
fetchgitlab
fetchrepoorcz
fetchsavannah
fetchsourcehut
fetchzip
top-level
+2
lib/default.nix
···
pathHasContext
canCleanSource
pathIsGitRepo
+
revOrTag
+
repoRevToName
;
inherit (self.modules)
evalModules
+101 -1
lib/sources.nix
···
# Tested in lib/tests/sources.sh
let
-
inherit (builtins)
+
inherit (lib.strings)
match
split
storeDir
···
};
};
+
# urlToName : (URL | Path | String) -> String
+
#
+
# Transform a URL (or path, or string) into a clean package name.
+
urlToName =
+
url:
+
let
+
inherit (lib.strings) stringLength;
+
base = baseNameOf (lib.removeSuffix "/" (lib.last (lib.splitString ":" (toString url))));
+
# chop away one git or archive-related extension
+
removeExt =
+
name:
+
let
+
matchExt = match "(.*)\\.(git|tar|zip|gz|tgz|bz|tbz|bz2|tbz2|lzma|txz|xz|zstd)$" name;
+
in
+
if matchExt != null then lib.head matchExt else name;
+
# apply function f to string x while the result shrinks
+
shrink =
+
f: x:
+
let
+
v = f x;
+
in
+
if stringLength v < stringLength x then shrink f v else x;
+
in
+
shrink removeExt base;
+
+
# shortRev : (String | Integer) -> String
+
#
+
# Given a package revision (like "refs/tags/v12.0"), produce a short revision ("12.0").
+
shortRev =
+
rev:
+
let
+
baseRev = baseNameOf (toString rev);
+
matchHash = match "[a-f0-9]+" baseRev;
+
matchVer = match "([A-Za-z]+[-_. ]?)*(v)?([0-9.]+.*)" baseRev;
+
in
+
if matchHash != null then
+
builtins.substring 0 7 baseRev
+
else if matchVer != null then
+
lib.last matchVer
+
else
+
baseRev;
+
+
# revOrTag : String -> String -> String
+
#
+
# Turn git `rev` and `tag` pair into a revision usable in `repoRevToName*`.
+
revOrTag =
+
rev: tag:
+
if tag != null then
+
tag
+
else if rev != null then
+
rev
+
else
+
"HEAD";
+
+
# repoRevToNameFull : (URL | Path | String) -> (String | Integer | null) -> (String | null) -> String
+
#
+
# See `repoRevToName` below.
+
repoRevToNameFull =
+
repo_: rev_: suffix_:
+
let
+
repo = urlToName repo_;
+
rev = if rev_ != null then "-${shortRev rev_}" else "";
+
suffix = if suffix_ != null then "-${suffix_}" else "";
+
in
+
"${repo}${rev}${suffix}-source";
+
+
# repoRevToName : String -> (URL | Path | String) -> (String | Integer | null) -> String -> String
+
#
+
# Produce derivation.name attribute for a given repository URL/path/name and (optionally) its revision/version tag.
+
#
+
# This is used by fetch(zip|git|FromGitHub|hg|svn|etc) to generate discoverable
+
# /nix/store paths.
+
#
+
# This uses a different implementation depending on the `pretty` argument:
+
# "source" -> name everything as "source"
+
# "versioned" -> name everything as "${repo}-${rev}-source"
+
# "full" -> name everything as "${repo}-${rev}-${fetcher}-source"
+
repoRevToName =
+
kind:
+
# match on `kind` first to minimize the thunk
+
if kind == "source" then
+
(
+
repo: rev: suffix:
+
"source"
+
)
+
else if kind == "versioned" then
+
(
+
repo: rev: suffix:
+
repoRevToNameFull repo rev null
+
)
+
else if kind == "full" then
+
repoRevToNameFull
+
else
+
throw "repoRevToName: invalid kind";
+
in
{
···
cleanSourceFilter
pathHasContext
canCleanSource
+
+
urlToName
+
shortRev
+
revOrTag
+
repoRevToName
sourceByRegex
sourceFilesBySuffices
+6 -2
pkgs/build-support/fetchbitbucket/default.nix
···
-
{ fetchzip, lib }:
+
{
+
lib,
+
repoRevToNameMaybe,
+
fetchzip,
+
}:
lib.makeOverridable (
{
owner,
repo,
rev,
-
name ? "source",
+
name ? repoRevToNameMaybe repo rev "bitbucket",
... # For hash agility
}@args:
fetchzip (
+8 -12
pkgs/build-support/fetchgit/default.nix
···
git-lfs,
cacert,
}:
+
let
urlToName =
url: rev:
let
-
inherit (lib) removeSuffix splitString last;
-
base = last (splitString ":" (baseNameOf (removeSuffix "/" url)));
-
-
matched = builtins.match "(.*)\\.git" base;
-
-
short = builtins.substring 0 7 rev;
-
-
appendShort = lib.optionalString ((builtins.match "[a-f0-9]*" rev) != null) "-${short}";
+
shortRev = lib.sources.shortRev rev;
+
appendShort = lib.optionalString ((builtins.match "[a-f0-9]*" rev) != null) "-${shortRev}";
in
-
"${if matched == null then base else builtins.head matched}${appendShort}";
+
"${lib.sources.urlToName url}${appendShort}";
in
+
lib.makeOverridable (
lib.fetchers.withNormalizedHash { } (
# NOTE Please document parameter additions or changes in
-
# doc/build-helpers/fetchers.chapter.md
+
# ../../../doc/build-helpers/fetchers.chapter.md
{
url,
tag ? null,
rev ? null,
+
name ? urlToName url (lib.revOrTag rev tag),
leaveDotGit ? deepClone || fetchTags,
outputHash ? lib.fakeHash,
outputHashAlgo ? null,
···
branchName ? null,
sparseCheckout ? [ ],
nonConeMode ? false,
-
name ? 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
···
"Please provide directories/patterns for sparse checkout as a list of strings. Passing a (multi-line) string is not supported any more."
else
stdenvNoCC.mkDerivation {
-
name = if name != null then name else urlToName url revWithTag;
+
inherit name;
builder = ./builder.sh;
fetcher = ./nix-prefetch-git;
+2 -1
pkgs/build-support/fetchgithub/default.nix
···
{
lib,
+
repoRevToNameMaybe,
fetchgit,
fetchzip,
}:
···
repo,
tag ? null,
rev ? null,
-
name ? "source",
+
name ? repoRevToNameMaybe repo (lib.revOrTag rev tag) "github",
fetchSubmodules ? false,
leaveDotGit ? null,
deepClone ? false,
+6 -2
pkgs/build-support/fetchgitiles/default.nix
···
-
{ fetchzip, lib }:
+
{
+
fetchzip,
+
repoRevToNameMaybe,
+
lib,
+
}:
lib.makeOverridable (
{
url,
rev ? null,
tag ? null,
-
name ? "source",
+
name ? repoRevToNameMaybe url (lib.revOrTag rev tag) "gitiles",
...
}@args:
+2 -1
pkgs/build-support/fetchgitlab/default.nix
···
{
lib,
+
repoRevToNameMaybe,
fetchgit,
fetchzip,
}:
···
repo,
rev ? null,
tag ? null,
+
name ? repoRevToNameMaybe repo (lib.revOrTag rev tag) "gitlab",
protocol ? "https",
domain ? "gitlab.com",
-
name ? "source",
group ? null,
fetchSubmodules ? false,
leaveDotGit ? false,
+6 -2
pkgs/build-support/fetchrepoorcz/default.nix
···
-
{ fetchzip }:
+
{
+
lib,
+
repoRevToNameMaybe,
+
fetchzip,
+
}:
# gitweb example, snapshot support is optional in gitweb
{
repo,
rev,
-
name ? "source",
+
name ? repoRevToNameMaybe repo rev "repoorcz",
... # For hash agility
}@args:
fetchzip (
+6 -2
pkgs/build-support/fetchsavannah/default.nix
···
-
{ fetchzip, lib }:
+
{
+
lib,
+
repoRevToNameMaybe,
+
fetchzip,
+
}:
lib.makeOverridable (
# cgit example, snapshot support is optional in cgit
{
repo,
rev,
-
name ? "source",
+
name ? repoRevToNameMaybe repo rev "savannah",
... # For hash agility
}@args:
fetchzip (
+3 -2
pkgs/build-support/fetchsourcehut/default.nix
···
{
+
lib,
+
repoRevToNameMaybe,
fetchgit,
fetchhg,
fetchzip,
-
lib,
}:
let
···
owner,
repo,
rev,
+
name ? repoRevToNameMaybe repo rev "sourcehut",
domain ? "sr.ht",
vc ? "git",
-
name ? "source",
fetchSubmodules ? false,
... # For hash agility
}@args:
+2 -1
pkgs/build-support/fetchzip/default.nix
···
{
lib,
+
repoRevToNameMaybe,
fetchurl,
withUnzip ? true,
unzip,
···
}:
{
-
name ? "source",
url ? "",
urls ? [ ],
+
name ? repoRevToNameMaybe (if url != "" then url else builtins.head urls) null "unpacked",
nativeBuildInputs ? [ ],
postFetch ? "",
extraPostFetch ? "",
+3
pkgs/top-level/all-packages.nix
···
stdenv = if stdenv.hostPlatform.isDarwin then llvmPackages_18.stdenv else stdenv;
};
+
# this is used by most `fetch*` functions
+
repoRevToNameMaybe = lib.repoRevToName config.fetchedSourceNameDefault;
+
fetchpatch =
callPackage ../build-support/fetchpatch {
# 0.3.4 would change hashes: https://github.com/NixOS/nixpkgs/issues/25154
+46
pkgs/top-level/config.nix
···
default = false;
};
+
fetchedSourceNameDefault = mkOption {
+
type = types.uniq (
+
types.enum [
+
"source"
+
"versioned"
+
"full"
+
]
+
);
+
default = "source";
+
description = ''
+
This controls the default derivation `name` attribute set by the
+
`fetch*` (`fetchzip`, `fetchFromGitHub`, etc) functions.
+
+
Possible values and the resulting `.name`:
+
+
- `"source"` -> `"source"`
+
- `"versioned"` -> `"''${repo}-''${rev}-source"`
+
- `"full"` -> `"''${repo}-''${rev}-''${fetcherName}-source"`
+
+
The default `"source"` is the best choice for minimal rebuilds, it
+
will ignore any non-hash changes (like branches being renamed, source
+
URLs changing, etc) at the cost of `/nix/store` being easily
+
cache-poisoned (see [NixOS/nix#969](https://github.com/NixOS/nix/issues/969)).
+
+
Setting this to `"versioned"` greatly helps with discoverability of
+
sources in `/nix/store` and makes cache-poisoning of `/nix/store` much
+
harder, at the cost of a single mass-rebuild for all `src`
+
derivations, and an occasional rebuild when a source changes some of
+
its non-hash attributes.
+
+
Setting this to `"full"` is similar to setting it to `"versioned"`,
+
but the use of `fetcherName` in the derivation name will force a
+
rebuild when `src` switches between `fetch*` functions, thus forcing
+
`nix` to check new derivation's `outputHash`, which is useful for
+
debugging.
+
+
Also, `"full"` is useful for easy collection and tracking of
+
statistics of where the packages you use are hosted.
+
+
If you are a developer, you should probably set this to at
+
least`"versioned"`.
+
+
Changing the default will cause a mass rebuild.
+
'';
+
};
+
doCheckByDefault = mkMassRebuild {
feature = "run `checkPhase` by default";
};