From 03a1e210c754be7939361dbfa272220531281074 Mon Sep 17 00:00:00 2001 From: Victor Borja Date: Fri, 27 Feb 2026 21:16:26 -0600 Subject: [PATCH] nixlock schemes npins schemes --- dev/_bootstrap-tests.nix | 73 ++++++++++++++++++++++- modules/nixlock/default.nix | 54 +---------------- modules/nixlock/parse.nix | 113 ++++++++++++++++++++++++++++++++++++ modules/npins/default.nix | 25 ++++++-- modules/npins/npins.bash | 42 ++++++++++---- 5 files changed, 238 insertions(+), 69 deletions(-) create mode 100644 modules/nixlock/parse.nix diff --git a/dev/_bootstrap-tests.nix b/dev/_bootstrap-tests.nix index 69112d7..9064942 100644 --- a/dev/_bootstrap-tests.nix +++ b/dev/_bootstrap-tests.nix @@ -19,6 +19,31 @@ let outputs = _: { }; }; + all-inputs-schemes = bootstrap { + inputs.simple.url = "github:vic/empty-flake"; + inputs.withBranch.url = "github:vic/empty-flake/main"; + inputs.noflake = { + url = "github:vic/empty-flake/main"; + flake = false; + }; + inputs.gitHttps.url = "git+https://github.com/vic/empty-flake"; + inputs.tarball.url = "https://github.com/vic/empty-flake/archive/main.tar.gz"; + inputs.tarballPlus.url = "tarball+https://github.com/vic/empty-flake/archive/main.tar.gz"; + inputs.fileHttps.url = "file+https://github.com/vic/empty-flake/archive/main.tar.gz"; + inputs.attrGh = { + type = "github"; + owner = "vic"; + repo = "empty-flake"; + }; + inputs.attrGhRef = { + type = "github"; + owner = "vic"; + repo = "empty-flake"; + ref = "main"; + }; + inputs.followsSimple.follows = "simple"; + }; + flake-parts = bootstrap { inputs.flake-parts.url = "github:hercules-ci/flake-parts"; }; @@ -114,6 +139,28 @@ let ''; }; + test-npins-schemes = pkgs.writeShellApplication { + name = "test-npins-schemes"; + runtimeInputs = [ + (all-inputs-schemes.flake-file.apps.write-npins pkgs) + pkgs.jq + ]; + text = '' + write-npins + cat ${outdir}/npins/sources.json + jq -e '.pins | has("simple")' ${outdir}/npins/sources.json + jq -e '.pins | has("withBranch")' ${outdir}/npins/sources.json + jq -e '.pins | has("noflake")' ${outdir}/npins/sources.json + jq -e '.pins | has("gitHttps")' ${outdir}/npins/sources.json + jq -e '.pins | has("tarball")' ${outdir}/npins/sources.json + jq -e '.pins | has("tarballPlus")' ${outdir}/npins/sources.json + jq -e '.pins | has("fileHttps")' ${outdir}/npins/sources.json + jq -e '.pins | has("attrGh")' ${outdir}/npins/sources.json + jq -e '.pins | has("attrGhRef")' ${outdir}/npins/sources.json + jq -e '.pins | has("followsSimple") | not' ${outdir}/npins/sources.json + ''; + }; + test-unflake = pkgs.writeShellApplication { name = "test-unflake"; runtimeInputs = [ @@ -132,7 +179,29 @@ let ]; text = '' write-nixlock - grep vic/empty-flake/archive ${outdir}/nixlock.lock.nix + grep empty ${outdir}/nixlock.lock.nix + ''; + }; + + test-nixlock-schemes = pkgs.writeShellApplication { + name = "test-nixlock-schemes"; + runtimeInputs = [ + (all-inputs-schemes.flake-file.apps.write-nixlock pkgs) + ]; + text = '' + write-nixlock + cat ${outdir}/nixlock.lock.nix + grep '"simple"' ${outdir}/nixlock.lock.nix + grep '"withBranch"' ${outdir}/nixlock.lock.nix + grep '"noflake"' ${outdir}/nixlock.lock.nix + grep '"gitHttps"' ${outdir}/nixlock.lock.nix + grep '"tarball"' ${outdir}/nixlock.lock.nix + grep '"tarballPlus"' ${outdir}/nixlock.lock.nix + grep '"fileHttps"' ${outdir}/nixlock.lock.nix + grep '"attrGh"' ${outdir}/nixlock.lock.nix + grep '"attrGhRef"' ${outdir}/nixlock.lock.nix + if grep '"followsSimple"' ${outdir}/nixlock.lock.nix; then exit 1; fi + grep vic/empty-flake ${outdir}/nixlock.lock.nix ''; }; @@ -143,9 +212,11 @@ pkgs.mkShell { test-flake test-unflake test-npins + test-npins-schemes test-npins-skip test-npins-follows test-npins-transitive test-nixlock + test-nixlock-schemes ]; } diff --git a/modules/nixlock/default.nix b/modules/nixlock/default.nix index df888e2..6fb69d9 100644 --- a/modules/nixlock/default.nix +++ b/modules/nixlock/default.nix @@ -12,59 +12,7 @@ let nlLibs = (import "${nixlock-source}/${flake-file.nixlock.version}").libs; - parseGithub = - path: - let - parts = lib.splitString "/" path; - owner = builtins.elemAt parts 0; - repo = builtins.elemAt parts 1; - ref = if builtins.length parts > 2 then builtins.elemAt parts 2 else "HEAD"; - in - { - type = "gitArchive"; - url = "https://github.com/${owner}/${repo}"; - inherit ref; - }; - - parseGitlab = - path: - let - parts = lib.splitString "/" path; - owner = builtins.elemAt parts 0; - repo = builtins.elemAt parts 1; - ref = if builtins.length parts > 2 then builtins.elemAt parts 2 else "HEAD"; - in - { - type = "gitArchive"; - url = "https://gitlab.com/${owner}/${repo}"; - inherit ref; - }; - - flakeUrlToNixlock = - url: - let - scheme = builtins.head (lib.splitString ":" url); - rest = lib.concatStringsSep ":" (builtins.tail (lib.splitString ":" url)); - in - if scheme == "github" then - parseGithub rest - else if scheme == "gitlab" then - parseGitlab rest - else if lib.hasPrefix "git+" url then - { - type = "git"; - url = lib.removePrefix "git+" url; - ref = "HEAD"; - } - else if lib.hasPrefix "http" url then - { - type = "archive"; - inherit url; - } - else - null; - - toNixlockInput = _name: input: if input ? url then flakeUrlToNixlock input.url else null; + inherit (import ./parse.nix lib) toNixlockInput; inputsFile = lib.filterAttrs (_: v: v != null) (lib.mapAttrs toNixlockInput inputs); diff --git a/modules/nixlock/parse.nix b/modules/nixlock/parse.nix new file mode 100644 index 0000000..833d161 --- /dev/null +++ b/modules/nixlock/parse.nix @@ -0,0 +1,113 @@ +lib: +let + + parseRef = + urlWithParams: + let + pairs = builtins.filter (lib.hasPrefix "ref=") ( + lib.splitString "&" (lib.last (lib.splitString "?" urlWithParams)) + ); + in + if pairs != [ ] then lib.removePrefix "ref=" (builtins.head pairs) else "HEAD"; + + baseUrl = urlWithParams: builtins.head (lib.splitString "?" urlWithParams); + + parseGitHost = + base: path: + let + parts = lib.splitString "/" path; + owner = builtins.elemAt parts 0; + repo = builtins.elemAt parts 1; + ref = if builtins.length parts > 2 then builtins.elemAt parts 2 else "HEAD"; + in + { + type = "gitArchive"; + url = "${base}/${owner}/${repo}"; + inherit ref; + }; + + parseGithub = parseGitHost "https://github.com"; + parseGitlab = parseGitHost "https://gitlab.com"; + parseSourcehut = parseGitHost "https://git.sr.ht"; + + parseGitUrl = urlWithParams: { + type = "git"; + url = baseUrl urlWithParams; + ref = parseRef urlWithParams; + }; + + attrsetBases = input: { + github = "https://github.com"; + gitlab = "https://${input.host or "gitlab.com"}"; + sourcehut = "https://${input.host or "git.sr.ht"}"; + }; + + attrsetToNixlock = + input: + let + mgithost = (attrsetBases input).${input.type} or null; + in + if mgithost != null then + { + type = "gitArchive"; + url = "${mgithost}/${input.owner}/${input.repo}"; + ref = input.ref or "HEAD"; + } + else if input.type == "git" then + { + type = "git"; + url = input.url; + ref = input.ref or "HEAD"; + } + else if + lib.elem input.type [ + "tarball" + "file" + ] + then + { + type = "archive"; + url = input.url; + } + else + null; + + flakeUrlToNixlock = + url: + let + scheme = builtins.head (lib.splitString ":" url); + rest = lib.concatStringsSep ":" (builtins.tail (lib.splitString ":" url)); + in + if scheme == "github" then + parseGithub rest + else if scheme == "gitlab" then + parseGitlab rest + else if scheme == "sourcehut" then + parseSourcehut rest + else if lib.hasPrefix "git+" url then + parseGitUrl (lib.removePrefix "git+" url) + else if lib.hasPrefix "tarball+" url then + flakeUrlToNixlock (lib.removePrefix "tarball+" url) + else if lib.hasPrefix "file+" url then + flakeUrlToNixlock (lib.removePrefix "file+" url) + else if lib.hasPrefix "http" url then + { + type = "archive"; + inherit url; + } + else + null; + + toNixlockInput = + _name: input: + if input ? url then + flakeUrlToNixlock input.url + else if input ? type then + attrsetToNixlock input + else + null; + +in +{ + inherit toNixlockInput; +} diff --git a/modules/npins/default.nix b/modules/npins/default.nix index f566ea4..95c7ba4 100644 --- a/modules/npins/default.nix +++ b/modules/npins/default.nix @@ -4,15 +4,30 @@ let inherit (import ../lib.nix lib) inputsExpr; inputs = inputsExpr flake-file.inputs; - esc = lib.escapeShellArg; - pinnableInputs = lib.filterAttrs (_: v: v.url or "" != "") inputs; + # Synthesise a canonical URL from attrset-form inputs (no url field). + gitHostScheme = { github = "github"; gitlab = "gitlab"; sourcehut = "sourcehut"; }; + + syntheticUrl = input: + let + scheme = gitHostScheme.${input.type or ""} or null; + ref = if input.ref or "" != "" then "/${input.ref}" else ""; + in + if scheme != null && input.owner or "" != "" then + "${scheme}:${input.owner}/${input.repo or ""}${ref}" + else + null; + + inputUrl = input: + if input.url or "" != "" then input.url + else syntheticUrl input; + + pinnableInputs = lib.filterAttrs (_: input: inputUrl input != null) inputs; # Seed the runtime queue with one tab-separated "name\turl" line per declared input. queueSeed = - let - lines = lib.mapAttrsToList (name: input: "${name}\t${input.url or ""}") pinnableInputs; - in lib.concatStringsSep "\n" lines; + lib.concatStringsSep "\n" + (lib.mapAttrsToList (name: input: "${name}\t${inputUrl input}") pinnableInputs); # Collect names of inputs that are explicitly skipped (follows = "") at any nesting level. collectSkipped = diff --git a/modules/npins/npins.bash b/modules/npins/npins.bash index 9846880..c67cfd6 100644 --- a/modules/npins/npins.bash +++ b/modules/npins/npins.bash @@ -13,29 +13,51 @@ echo "$queueSeed" > "$QUEUE_FILE" # Add a pin by its flake-style URL (github:o/r, gitlab:o/r, channel URL, etc.) npins_add_url() { - local name="$1" url="$2" spec owner repo ref channel + local name="$1" url="$2" spec owner repo ref channel gitbase q + # Strip wrapper prefixes, then re-dispatch. case "$url" in + tarball+*) npins_add_url "$name" "${url#tarball+}"; return ;; + file+*) npins_add_url "$name" "${url#file+}"; return ;; + git+*) + gitbase="${url#git+}" + if [[ "$gitbase" == *"?"* ]]; then + q="${gitbase#*\?}" gitbase="${gitbase%%\?*}" + ref=$(printf '%s' "$q" | tr '&' '\n' | sed -n 's/^ref=//p' | head -1) + else + ref="" + fi + if [ -n "$ref" ]; then + npins add git --name "$name" -b "$ref" "$gitbase" + else + npins add git --name "$name" "$gitbase" 2>/dev/null \ + || npins add git --name "$name" -b main "$gitbase" 2>/dev/null \ + || npins add git --name "$name" -b master "$gitbase" + fi ;; github:*) spec="${url#github:}" owner="${spec%%/*}" spec="${spec#*/}" repo="${spec%%/*}" ref="${spec#*/}" if [ "$ref" != "$repo" ]; then - npins add github --name "$name" -b "$ref" "$owner" "$repo" + npins add github --name "$name" -b "$ref" "$owner" "$repo" else - # No explicit ref: prefer a release tag, fall back to common branches. - npins add github --name "$name" "$owner" "$repo" 2>/dev/null \ - || npins add github --name "$name" -b main "$owner" "$repo" 2>/dev/null \ - || npins add github --name "$name" -b master "$owner" "$repo" + npins add github --name "$name" "$owner" "$repo" 2>/dev/null \ + || npins add github --name "$name" -b main "$owner" "$repo" 2>/dev/null \ + || npins add github --name "$name" -b master "$owner" "$repo" fi ;; gitlab:*) spec="${url#gitlab:}" owner="${spec%%/*}" spec="${spec#*/}" repo="${spec%%/*}" ref="${spec#*/}" if [ "$ref" != "$repo" ]; then - npins add gitlab --name "$name" -b "$ref" "$owner" "$repo" + npins add gitlab --name "$name" -b "$ref" "$owner" "$repo" else - npins add gitlab --name "$name" "$owner" "$repo" 2>/dev/null \ - || npins add gitlab --name "$name" -b main "$owner" "$repo" 2>/dev/null \ - || npins add gitlab --name "$name" -b master "$owner" "$repo" + npins add gitlab --name "$name" "$owner" "$repo" 2>/dev/null \ + || npins add gitlab --name "$name" -b main "$owner" "$repo" 2>/dev/null \ + || npins add gitlab --name "$name" -b master "$owner" "$repo" fi ;; + sourcehut:*) + spec="${url#sourcehut:}" owner="${spec%%/*}" repo="${spec#*/}" + ref="${repo#*/}" repo="${repo%%/*}" + [ "$ref" = "$repo" ] && ref="main" + npins add git --name "$name" -b "$ref" "https://git.sr.ht/${owner}/${repo}" ;; https://channels.nixos.org/*|https://releases.nixos.org/*) channel=$(printf '%s' "$url" | sed 's|https://[^/]*/||;s|/.*||') npins add channel --name "$name" "$channel" ;;