Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions pkgs/build-support/fetchgithub/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
repoRevToNameMaybe,
fetchgit,
fetchzip,
fetchurl,
jq,
pure-export-subst,
}:

lib.makeOverridable (
Expand All @@ -21,6 +24,7 @@ lib.makeOverridable (
sparseCheckout ? [ ],
githubBase ? "github.com",
varPrefix ? null,
pureExportSubst ? false,
meta ? { },
... # For hash agility
}@args:
Expand All @@ -30,6 +34,20 @@ lib.makeOverridable (
rev == null
)) "fetchFromGitHub requires one of either `rev` or `tag` to be provided (not both)."
);
# pureExportSubst relies on fetching commit data from GitHub API by tag,
# and currently doesn't support any options that rely on fetchgit.
assert
pureExportSubst
-> (
(tag != null)
&& (rev == null)
&& !fetchSubmodules
&& (leaveDotGit == null)
&& !deepClone
&& !forceFetchGit
&& !fetchLFS
&& (sparseCheckout == [ ])
);

let

Expand Down Expand Up @@ -61,6 +79,7 @@ lib.makeOverridable (
"private"
"githubBase"
"varPrefix"
"pureExportSubst"
];
varBase = "NIX${lib.optionalString (varPrefix != null) "_${varPrefix}"}_GITHUB_PRIVATE_";
useFetchGit =
Expand All @@ -75,6 +94,8 @@ lib.makeOverridable (
fetcher =
if useFetchGit then
fetchgit
else if pureExportSubst then
fetchurl
# fetchzip may not be overridable when using external tools, for example nix-prefetch
else if fetchzip ? override then
fetchzip.override { withUnzip = false; }
Expand Down Expand Up @@ -124,6 +145,56 @@ lib.makeOverridable (
url = gitRepoUrl;
}
// lib.optionalAttrs (leaveDotGit != null) { inherit leaveDotGit; }
else if pureExportSubst then
let
apiBase = "https://${
if githubBase == "github.com" then "api.github.com" else "${githubBase}/api/v3"
}";
in
{
url = "${apiBase}/repos/${owner}/${repo}/git/refs/tags/${tag}";
recursiveHash = true;
downloadToTemp = true;
nativeBuildInputs = [
jq
pure-export-subst
];
postFetch = ''
set -euo pipefail

# $downloadedFile contains JSON about ref
source <(jq -r '.object | "obj_type=" + .type + "\nobj_url=" + .url + "\nobj_sha=" + .sha + "\n"' "$downloadedFile")
if [ "$obj_type" != "commit" ]; then
echo "Unexpected obj_type '$obj_type', expected 'commit'."
exit 1
fi

rm "$downloadedFile"
success=
tryDownload "$obj_url"
if ! test -n "$success"; then
exit 1
fi
tree_sha="$(jq -r .tree.sha "$downloadedFile")"
mv "$downloadedFile" "$TMPDIR/commit.json"

success=
tryDownload "${apiBase}/repos/${owner}/${repo}/tarball/$tree_sha"
if ! test -n "$success"; then
exit 1
fi

unpackDir="$TMPDIR/unpack"
mkdir "$unpackDir"
cd "$unpackDir"
mv "$downloadedFile" "$downloadedFile.tar.gz"
unpackFile "$downloadedFile.tar.gz"
onlyDir="$unpackDir/$(ls -A)"
cd "$onlyDir"
pure-export-subst "${tag}" "$TMPDIR/commit.json"
mv "$onlyDir" "$out"
'';
}
else
{
# Use the API endpoint for private repos, as the archive URI doesn't
Expand Down
33 changes: 33 additions & 0 deletions pkgs/build-support/fetchgithub/tests.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{ testers, fetchFromGitHub, ... }:
{
simple-rev = testers.invalidateFetcherByDrvHash fetchFromGitHub {
name = "simple-rev";
owner = "smallstep";
repo = "certificates";
rev = "0cf1c5688708ec4a910c007d7f151c617b722268";
hash = "sha256-5W39Nc6WuxhrXbEfPWMaWWAUX6UnjYqlEAPlDCeYgrY=";
};
simple-tag = testers.invalidateFetcherByDrvHash fetchFromGitHub {
name = "simple-tag";
owner = "smallstep";
repo = "certificates";
tag = "v0.28.3";
hash = "sha256-5W39Nc6WuxhrXbEfPWMaWWAUX6UnjYqlEAPlDCeYgrY=";
};
pureExportSubst-simple = testers.invalidateFetcherByDrvHash fetchFromGitHub {
name = "export-subst-simple";
owner = "smallstep";
repo = "certificates";
tag = "v0.28.3";
hash = "sha256-5W39Nc6WuxhrXbEfPWMaWWAUX6UnjYqlEAPlDCeYgrY=";
pureExportSubst = true;
};
pureExportSubst-noop = testers.invalidateFetcherByDrvHash fetchFromGitHub {
name = "export-subst-noop";
owner = "smallstep";
repo = "certificates";
tag = "v0.8.4"; # They didn't have export-subst at that point
hash = "sha256-pHs87xXu1ueMMlmUSzrDKYnD1yxpxU2UvOFtjI9ATCw=";
pureExportSubst = true;
};
}
150 changes: 150 additions & 0 deletions pkgs/by-name/pu/pure-export-subst/package.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
{
writeShellApplication,
jq,
gitMinimal,

# for tests
testers,
fetchFromGitHub,
runCommand,
pure-export-subst,
}:
let
jqSedGenProg = ''
{
H: .sha,
h: .sha[:7], # we always shorten hashes to the default of 7 characters for reproducibility
T: .tree.sha,
t: .tree.sha[:7],
P: [.parents[] | .sha] | join(" "),
p: [.parents[] | .sha[:7]] | join(" "),
an: .author.name,
ae: .author.email,
ai: .author.date, # man calls this "ISO 8601-like format", but we just use ISO 8601 provided by GitHub
aI: .author.date,
cn: .committer.name,
ce: .committer.email,
ci: .committer.date,
cI: .committer.date,
d: " (tag: " + $tag + ")",
D: "tag: " + $tag,
s: .message | split("\n\n") | .[0],
b: .message | split("\n\n") | .[1:] | join("\n\n"),
B: .message,
}
# Convert to a list of key-value objects
| to_entries
# For each entry generate a command like 's/\$Format:%<key>\$/<value>/g'
| map("s/\\$Format:%" + .key + "\\$/" + (.value | gsub("/"; "\\/") | gsub("\n"; "\\n")) + "/g")
# Exit with an error if any unsupported format strings remain
+ ["/\\$Format:%[a-zA-Z]{1,2}\\$/{
s/.*\\$Format:(%[a-zA-Z]{1,2})\\$.*/Unsupported format string \\1/
w /dev/stderr
Q 1
}"]
# Join into one big program
| join("\n")
'';
in
writeShellApplication {
name = "pure-export-subst";
runtimeInputs = [
jq
gitMinimal
];
text = ''
usage() {
echo "Substitute template strings in current directory like Git's export-subst, but only using provided inputs"
echo "Usage: pure-export-subst <tag> <commit.json>" >&2
echo "<tag> is the the tag name to use in substitutions" >&2
echo "<commit.json> is a file with a JSON representation of a commit in GitHub format" >&2
echo "See the response format at https://docs.github.com/en/rest/git/commits?apiVersion=2022-11-28#get-a-commit-object" >&2
exit 1
}

if [ $# -ne 2 ]; then
usage
fi
tag="$1"
commitJSON="$2"
if [ -z "$tag" ] || [ ! -f "$commitJSON" ]; then
usage
fi

tmpPath="$(realpath "$(mktemp -d --tmpdir pure-export-subst-XXXXXXXX)")"
cleanup() {
rm -rf "$tmpPath"
}
trap cleanup EXIT

jq -r -f ${builtins.toFile "prog.jq" jqSedGenProg} --arg tag "$tag" "$commitJSON" > "$tmpPath/sedprog"

gitDir="$tmpPath/gitrepo"
git -c advice.defaultBranchName= init --bare "$gitDir"
find . -type f | \
git -C "$gitDir" -c core.attributesfile="$PWD/.gitattributes" check-attr --stdin export-subst | \
(
set +e
grep --only-matching --perl-regexp '^.*(?=: .*: set$)'
rv=$?
set -e
# ignore exit code 1 which means that there is no such lines
if [ $rv != 0 ] && [ $rv != 1 ]; then
exit $rv
fi
) | \
xargs --no-run-if-empty sed -i -E -f "$tmpPath/sedprog"
'';

passthru.tests =
let
doTest =
{
name,
githubArgs,
outputHash,
commitJSON,
}:
runCommand name
{
src = fetchFromGitHub githubArgs;
nativeBuildInputs = [ pure-export-subst ];
inherit outputHash;
outputHashMode = "recursive";
}
''
cp -r $src source
chmod -R u+w source
pushd source
pure-export-subst "${githubArgs.tag}" "${commitJSON}"
popd
mv source $out
'';
in
{
simple = testers.invalidateFetcherByDrvHash doTest {
name = "pure-export-subst-simple";
githubArgs = {
owner = "smallstep";
repo = "certificates";
tag = "v0.28.3";
hash = "sha256-y5Um8TvtYt1fZU/Jnzt4dE3/M6QUg8k99fXhr71olYU=";
forceFetchGit = true;
};
outputHash = "sha256-5W39Nc6WuxhrXbEfPWMaWWAUX6UnjYqlEAPlDCeYgrY=";
commitJSON = ./test-v0.28.3.json;
};
noop = testers.invalidateFetcherByDrvHash doTest {
name = "pure-export-subst-noop";
githubArgs = {
owner = "smallstep";
repo = "certificates";
tag = "v0.8.4"; # They didn't have export-subst at that point
hash = "sha256-pHs87xXu1ueMMlmUSzrDKYnD1yxpxU2UvOFtjI9ATCw=";
forceFetchGit = true;
};
outputHash = "sha256-pHs87xXu1ueMMlmUSzrDKYnD1yxpxU2UvOFtjI9ATCw=";
commitJSON = ./test-v0.8.4.json;
};
};
}
35 changes: 35 additions & 0 deletions pkgs/by-name/pu/pure-export-subst/test-v0.28.3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"sha": "0cf1c5688708ec4a910c007d7f151c617b722268",
"node_id": "C_kwDOCUcpPNoAKDBjZjFjNTY4ODcwOGVjNGE5MTBjMDA3ZDdmMTUxYzYxN2I3MjIyNjg",
"url": "https://api.github.com/repos/smallstep/certificates/git/commits/0cf1c5688708ec4a910c007d7f151c617b722268",
"html_url": "https://github.com/smallstep/certificates/commit/0cf1c5688708ec4a910c007d7f151c617b722268",
"author": {
"name": "max furman",
"email": "mx.furman@gmail.com",
"date": "2025-03-18T01:52:31Z"
},
"committer": {
"name": "max furman",
"email": "mx.furman@gmail.com",
"date": "2025-03-18T01:56:02Z"
},
"tree": {
"sha": "6a4b9e306f8354dd98268d43165cfdacc525a3d2",
"url": "https://api.github.com/repos/smallstep/certificates/git/trees/6a4b9e306f8354dd98268d43165cfdacc525a3d2"
},
"message": "empty commit",
"parents": [
{
"sha": "78745ba9ff05d489f4bb95789f163217818adf26",
"url": "https://api.github.com/repos/smallstep/certificates/git/commits/78745ba9ff05d489f4bb95789f163217818adf26",
"html_url": "https://github.com/smallstep/certificates/commit/78745ba9ff05d489f4bb95789f163217818adf26"
}
],
"verification": {
"verified": true,
"reason": "valid",
"signature": "-----BEGIN SSH SIGNATURE-----\nU1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgA8ntr7PdJsl4k9y5+5PSPGHxF9\nApcOp8nX+7zWn4lrUAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5\nAAAAQOPQaCIYk9phX7ma1OBeeDWTACwsa65QcrHD39sD5UYyjkU7WS9ZGYuQNLH+ltXXnV\nMqoymAxogsWE8qt7Yopgk=\n-----END SSH SIGNATURE-----",
"payload": "tree 6a4b9e306f8354dd98268d43165cfdacc525a3d2\nparent 78745ba9ff05d489f4bb95789f163217818adf26\nauthor max furman <mx.furman@gmail.com> 1742262751 -0700\ncommitter max furman <mx.furman@gmail.com> 1742262962 -0700\n\nempty commit\n",
"verified_at": "2025-03-18T01:56:22Z"
}
}
35 changes: 35 additions & 0 deletions pkgs/by-name/pu/pure-export-subst/test-v0.8.4.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"sha": "c94fb80f4be7df16b93e41d98ff310e801b368c9",
"node_id": "MDY6Q29tbWl0MTU1NjU4NTU2OmM5NGZiODBmNGJlN2RmMTZiOTNlNDFkOThmZjMxMGU4MDFiMzY4Yzk=",
"url": "https://api.github.com/repos/smallstep/certificates/git/commits/c94fb80f4be7df16b93e41d98ff310e801b368c9",
"html_url": "https://github.com/smallstep/certificates/commit/c94fb80f4be7df16b93e41d98ff310e801b368c9",
"author": {
"name": "max furman",
"email": "mx.furman@gmail.com",
"date": "2019-02-12T20:56:58Z"
},
"committer": {
"name": "max furman",
"email": "mx.furman@gmail.com",
"date": "2019-02-12T20:56:58Z"
},
"tree": {
"sha": "6bddc4ce69d89d5e0126d9920b52d1496dc5f915",
"url": "https://api.github.com/repos/smallstep/certificates/git/trees/6bddc4ce69d89d5e0126d9920b52d1496dc5f915"
},
"message": "dep update cli",
"parents": [
{
"sha": "ef99dd28e53380af8e6564eaaf0577d9465ac988",
"url": "https://api.github.com/repos/smallstep/certificates/git/commits/ef99dd28e53380af8e6564eaaf0577d9465ac988",
"html_url": "https://github.com/smallstep/certificates/commit/ef99dd28e53380af8e6564eaaf0577d9465ac988"
}
],
"verification": {
"verified": false,
"reason": "unsigned",
"signature": null,
"payload": null,
"verified_at": null
}
}
3 changes: 2 additions & 1 deletion pkgs/development/python-modules/ancp-bids/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ buildPythonPackage rec {
owner = "ANCPLabOldenburg";
repo = "ancp-bids";
tag = version;
hash = "sha256-n8QfQ2PGdAO6kTfkbFpj3f2gYa3vwuYg+vPpZlGNpb0=";
hash = "sha256-EYapqNyfAPj6oCdiSqu/4828N1SPuBDLv75jRSNOjpo=";
pureExportSubst = true;
};

build-system = [ setuptools ];
Expand Down
1 change: 1 addition & 0 deletions pkgs/test/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ with pkgs;
fetchzip = recurseIntoAttrs (callPackages ../build-support/fetchzip/tests.nix { });
fetchgit = recurseIntoAttrs (callPackages ../build-support/fetchgit/tests.nix { });
fetchFromBitbucket = recurseIntoAttrs (callPackages ../build-support/fetchbitbucket/tests.nix { });
fetchFromGitHub = recurseIntoAttrs (callPackages ../build-support/fetchgithub/tests.nix { });
fetchFirefoxAddon = recurseIntoAttrs (
callPackages ../build-support/fetchfirefoxaddon/tests.nix { }
);
Expand Down
Loading