diff --git a/maintainers/flake-module.nix b/maintainers/flake-module.nix index 8dcff9c63f0..4c37da80d95 100644 --- a/maintainers/flake-module.nix +++ b/maintainers/flake-module.nix @@ -100,6 +100,7 @@ # Don't format vendored code ''^doc/manual/redirects\.js$'' ''^doc/manual/theme/highlight\.js$'' + ''^src/libfetchers/builtin-flake-registry\.json$'' ]; }; shellcheck = { diff --git a/src/libcmd/common-eval-args.cc b/src/libcmd/common-eval-args.cc index d3a1df73312..39e7139f945 100644 --- a/src/libcmd/common-eval-args.cc +++ b/src/libcmd/common-eval-args.cc @@ -127,7 +127,7 @@ MixEvalArgs::MixEvalArgs() fetchers::Attrs extraAttrs; if (to.subdir != "") extraAttrs["dir"] = to.subdir; - fetchers::overrideRegistry(fetchSettings, from.input, to.input, extraAttrs); + fetchers::overrideRegistry(from.input, to.input, extraAttrs); }}, .completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) { completeFlakeRef(completions, openStore(), prefix); diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index aa51ec278d9..37959431a18 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -185,7 +185,6 @@ MixFlakeOptions::MixFlakeOptions() } overrideRegistry( - fetchSettings, fetchers::Input::fromAttrs(fetchSettings, {{"type", "indirect"}, {"id", inputName}}), input3->lockedRef.input, extraAttrs); diff --git a/src/libfetchers/builtin-flake-registry.json b/src/libfetchers/builtin-flake-registry.json new file mode 100644 index 00000000000..65e973290a0 --- /dev/null +++ b/src/libfetchers/builtin-flake-registry.json @@ -0,0 +1,425 @@ +{ + "flakes": [ + { + "from": { + "id": "agda", + "type": "indirect" + }, + "to": { + "owner": "agda", + "repo": "agda", + "type": "github" + } + }, + { + "from": { + "id": "agenix", + "type": "indirect" + }, + "to": { + "owner": "ryantm", + "repo": "agenix", + "type": "github" + } + }, + { + "from": { + "id": "arion", + "type": "indirect" + }, + "to": { + "owner": "hercules-ci", + "repo": "arion", + "type": "github" + } + }, + { + "from": { + "id": "blender-bin", + "type": "indirect" + }, + "to": { + "dir": "blender", + "owner": "edolstra", + "repo": "nix-warez", + "type": "github" + } + }, + { + "from": { + "id": "bundlers", + "type": "indirect" + }, + "to": { + "owner": "NixOS", + "repo": "bundlers", + "type": "github" + } + }, + { + "from": { + "id": "cachix", + "type": "indirect" + }, + "to": { + "owner": "cachix", + "repo": "cachix", + "type": "github" + } + }, + { + "from": { + "id": "composable", + "type": "indirect" + }, + "to": { + "owner": "ComposableFi", + "repo": "composable", + "type": "github" + } + }, + { + "from": { + "id": "disko", + "type": "indirect" + }, + "to": { + "owner": "nix-community", + "repo": "disko", + "type": "github" + } + }, + { + "from": { + "id": "dreampkgs", + "type": "indirect" + }, + "to": { + "owner": "nix-community", + "repo": "dreampkgs", + "type": "github" + } + }, + { + "from": { + "id": "dwarffs", + "type": "indirect" + }, + "to": { + "owner": "edolstra", + "repo": "dwarffs", + "type": "github" + } + }, + { + "from": { + "id": "emacs-overlay", + "type": "indirect" + }, + "to": { + "owner": "nix-community", + "repo": "emacs-overlay", + "type": "github" + } + }, + { + "from": { + "id": "fenix", + "type": "indirect" + }, + "to": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + { + "from": { + "id": "flake-parts", + "type": "indirect" + }, + "to": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + { + "from": { + "id": "flake-utils", + "type": "indirect" + }, + "to": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + { + "from": { + "id": "helix", + "type": "indirect" + }, + "to": { + "owner": "helix-editor", + "repo": "helix", + "type": "github" + } + }, + { + "from": { + "id": "hercules-ci-agent", + "type": "indirect" + }, + "to": { + "owner": "hercules-ci", + "repo": "hercules-ci-agent", + "type": "github" + } + }, + { + "from": { + "id": "hercules-ci-effects", + "type": "indirect" + }, + "to": { + "owner": "hercules-ci", + "repo": "hercules-ci-effects", + "type": "github" + } + }, + { + "from": { + "id": "home-manager", + "type": "indirect" + }, + "to": { + "owner": "nix-community", + "repo": "home-manager", + "type": "github" + } + }, + { + "from": { + "id": "hydra", + "type": "indirect" + }, + "to": { + "owner": "NixOS", + "repo": "hydra", + "type": "github" + } + }, + { + "from": { + "id": "mach-nix", + "type": "indirect" + }, + "to": { + "owner": "DavHau", + "repo": "mach-nix", + "type": "github" + } + }, + { + "from": { + "id": "ngipkgs", + "type": "indirect" + }, + "to": { + "owner": "ngi-nix", + "repo": "ngipkgs", + "type": "github" + } + }, + { + "from": { + "id": "nickel", + "type": "indirect" + }, + "to": { + "owner": "tweag", + "repo": "nickel", + "type": "github" + } + }, + { + "from": { + "id": "nix", + "type": "indirect" + }, + "to": { + "owner": "NixOS", + "repo": "nix", + "type": "github" + } + }, + { + "from": { + "id": "nix-darwin", + "type": "indirect" + }, + "to": { + "owner": "nix-darwin", + "repo": "nix-darwin", + "type": "github" + } + }, + { + "from": { + "id": "nix-serve", + "type": "indirect" + }, + "to": { + "owner": "edolstra", + "repo": "nix-serve", + "type": "github" + } + }, + { + "from": { + "id": "nixops", + "type": "indirect" + }, + "to": { + "owner": "NixOS", + "repo": "nixops", + "type": "github" + } + }, + { + "from": { + "id": "nixos-anywhere", + "type": "indirect" + }, + "to": { + "owner": "nix-community", + "repo": "nixos-anywhere", + "type": "github" + } + }, + { + "from": { + "id": "nixos-hardware", + "type": "indirect" + }, + "to": { + "owner": "NixOS", + "repo": "nixos-hardware", + "type": "github" + } + }, + { + "from": { + "id": "nixos-homepage", + "type": "indirect" + }, + "to": { + "owner": "NixOS", + "repo": "nixos-homepage", + "type": "github" + } + }, + { + "from": { + "id": "nixos-search", + "type": "indirect" + }, + "to": { + "owner": "NixOS", + "repo": "nixos-search", + "type": "github" + } + }, + { + "from": { + "id": "nixpkgs", + "type": "indirect" + }, + "to": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + { + "from": { + "id": "nur", + "type": "indirect" + }, + "to": { + "owner": "nix-community", + "repo": "NUR", + "type": "github" + } + }, + { + "from": { + "id": "patchelf", + "type": "indirect" + }, + "to": { + "owner": "NixOS", + "repo": "patchelf", + "type": "github" + } + }, + { + "from": { + "id": "poetry2nix", + "type": "indirect" + }, + "to": { + "owner": "nix-community", + "repo": "poetry2nix", + "type": "github" + } + }, + { + "from": { + "id": "pridefetch", + "type": "indirect" + }, + "to": { + "owner": "SpyHoodle", + "repo": "pridefetch", + "type": "github" + } + }, + { + "from": { + "id": "sops-nix", + "type": "indirect" + }, + "to": { + "owner": "Mic92", + "repo": "sops-nix", + "type": "github" + } + }, + { + "from": { + "id": "systems", + "type": "indirect" + }, + "to": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + { + "from": { + "id": "templates", + "type": "indirect" + }, + "to": { + "owner": "NixOS", + "repo": "templates", + "type": "github" + } + } + ], + "version": 2 +} diff --git a/src/libfetchers/include/nix/fetchers/registry.hh b/src/libfetchers/include/nix/fetchers/registry.hh index bde5263611c..8978231a059 100644 --- a/src/libfetchers/include/nix/fetchers/registry.hh +++ b/src/libfetchers/include/nix/fetchers/registry.hh @@ -12,8 +12,6 @@ namespace nix::fetchers { struct Registry { - const Settings & settings; - enum RegistryType { Flag = 0, User = 1, @@ -33,14 +31,16 @@ struct Registry std::vector entries; - Registry(const Settings & settings, RegistryType type) - : settings{settings} - , type{type} + Registry(RegistryType type) + : type{type} { } static std::shared_ptr read(const Settings & settings, const Path & path, RegistryType type); + static std::shared_ptr + read(const Settings & settings, std::string_view whence, std::string_view jsonStr, RegistryType type); + void write(const Path & path); void add(const Input & from, const Input & to, const Attrs & extraAttrs); @@ -58,7 +58,7 @@ Path getUserRegistryPath(); Registries getRegistries(const Settings & settings, ref store); -void overrideRegistry(const Settings & settings, const Input & from, const Input & to, const Attrs & extraAttrs); +void overrideRegistry(const Input & from, const Input & to, const Attrs & extraAttrs); enum class UseRegistries : int { No, diff --git a/src/libfetchers/meson.build b/src/libfetchers/meson.build index 25d4d7821d7..8b6bd55df90 100644 --- a/src/libfetchers/meson.build +++ b/src/libfetchers/meson.build @@ -56,6 +56,13 @@ sources = files( subdir('include/nix/fetchers') +# Generate builtin-flake-registry.json.gen.hh +subdir('nix-meson-build-support/generate-header') + +sources += gen_header.process( + 'builtin-flake-registry.json', +) + subdir('nix-meson-build-support/export-all-symbols') subdir('nix-meson-build-support/windows-version') diff --git a/src/libfetchers/package.nix b/src/libfetchers/package.nix index b6b061e2d9f..1a30ac29301 100644 --- a/src/libfetchers/package.nix +++ b/src/libfetchers/package.nix @@ -28,6 +28,7 @@ mkMesonLibrary (finalAttrs: { ./.version ./meson.build ./include/nix/fetchers/meson.build + ./builtin-flake-registry.json (fileset.fileFilter (file: file.hasExt "cc") ./.) (fileset.fileFilter (file: file.hasExt "hh") ./.) ]; diff --git a/src/libfetchers/registry.cc b/src/libfetchers/registry.cc index 48dc96fcffa..33553710824 100644 --- a/src/libfetchers/registry.cc +++ b/src/libfetchers/registry.cc @@ -14,14 +14,25 @@ std::shared_ptr Registry::read(const Settings & settings, const Path & { debug("reading registry '%s'", path); - auto registry = std::make_shared(settings, type); - if (!pathExists(path)) - return std::make_shared(settings, type); + return std::make_shared(type); + + try { + return read(settings, path, readFile(path), type); + } catch (Error & e) { + warn("cannot read flake registry '%s': %s", path, e.what()); + return std::make_shared(type); + } +} + +std::shared_ptr +Registry::read(const Settings & settings, std::string_view whence, std::string_view jsonStr, RegistryType type) +{ + auto registry = std::make_shared(type); try { - auto json = nlohmann::json::parse(readFile(path)); + auto json = nlohmann::json::parse(jsonStr); auto version = json.value("version", 0); @@ -45,12 +56,10 @@ std::shared_ptr Registry::read(const Settings & settings, const Path & } else - throw Error("flake registry '%s' has unsupported version %d", path, version); + warn("flake registry '%s' has unsupported version %d", whence, version); } catch (nlohmann::json::exception & e) { - warn("cannot parse flake registry '%s': %s", path, e.what()); - } catch (Error & e) { - warn("cannot read flake registry '%s': %s", path, e.what()); + warn("cannot parse flake registry '%s': %s", whence, e.what()); } return registry; @@ -118,33 +127,47 @@ std::shared_ptr getCustomRegistry(const Settings & settings, const Pat return customRegistry; } -std::shared_ptr getFlagRegistry(const Settings & settings) +std::shared_ptr getFlagRegistry() { - static auto flagRegistry = std::make_shared(settings, Registry::Flag); + static auto flagRegistry = std::make_shared(Registry::Flag); return flagRegistry; } -void overrideRegistry(const Settings & settings, const Input & from, const Input & to, const Attrs & extraAttrs) +void overrideRegistry(const Input & from, const Input & to, const Attrs & extraAttrs) { - getFlagRegistry(settings)->add(from, to, extraAttrs); + getFlagRegistry()->add(from, to, extraAttrs); } static std::shared_ptr getGlobalRegistry(const Settings & settings, ref store) { static auto reg = [&]() { - auto path = settings.flakeRegistry.get(); - if (path == "") { - return std::make_shared(settings, Registry::Global); // empty registry - } + try { + auto path = settings.flakeRegistry.get(); + if (path == "") { + return std::make_shared(Registry::Global); // empty registry + } - if (!isAbsolute(path)) { - auto storePath = downloadFile(store, settings, path, "flake-registry.json").storePath; - if (auto store2 = store.dynamic_pointer_cast()) - store2->addPermRoot(storePath, getCacheDir() + "/flake-registry.json"); - path = store->toRealPath(storePath); - } + if (!isAbsolute(path)) { + auto storePath = downloadFile(store, settings, path, "flake-registry.json").storePath; + if (auto store2 = store.dynamic_pointer_cast()) + store2->addPermRoot(storePath, getCacheDir() + "/flake-registry.json"); + path = store->toRealPath(storePath); + } - return Registry::read(settings, path, Registry::Global); + return Registry::read(settings, path, Registry::Global); + } catch (Error & e) { + warn( + "cannot fetch global flake registry '%s', will use builtin fallback registry: %s", + settings.flakeRegistry.get(), + e.info().msg); + // Use builtin registry as fallback + return Registry::read( + settings, + "builtin flake registry", +#include "builtin-flake-registry.json.gen.hh" + , + Registry::Global); + } }(); return reg; @@ -153,7 +176,7 @@ static std::shared_ptr getGlobalRegistry(const Settings & settings, re Registries getRegistries(const Settings & settings, ref store) { Registries registries; - registries.push_back(getFlagRegistry(settings)); + registries.push_back(getFlagRegistry()); registries.push_back(getUserRegistry(settings)); registries.push_back(getSystemRegistry(settings)); registries.push_back(getGlobalRegistry(settings, store)); diff --git a/src/nix/registry-resolve.md b/src/nix/registry-resolve.md new file mode 100644 index 00000000000..7de25a26a96 --- /dev/null +++ b/src/nix/registry-resolve.md @@ -0,0 +1,28 @@ +R""( + +# Examples + +* Resolve the `nixpkgs` and `blender-bin` flakerefs: + + ```console + # nix registry resolve nixpkgs blender-bin + github:NixOS/nixpkgs/nixpkgs-unstable + github:edolstra/nix-warez?dir=blender + ``` + +* Resolve an indirect flakeref with a branch override: + + ```console + # nix registry resolve nixpkgs/25.05 + github:NixOS/nixpkgs/25.05 + ``` + +# Description + +This command resolves indirect flakerefs (e.g. `nixpkgs`) to direct flakerefs (e.g. `github:NixOS/nixpkgs`) using the flake registries. It looks up each provided flakeref in all available registries (flag, user, system, and global) and returns the resolved direct flakeref on a separate line on standard output. It does not fetch any flakes. + +The resolution process may apply multiple redirections if necessary until a direct flakeref is found. If an indirect flakeref cannot be found in any registry, an error will be thrown. + +See the [`nix registry` manual page](./nix3-registry.md) for more details on the registry. + +)"" \ No newline at end of file diff --git a/src/nix/registry.cc b/src/nix/registry.cc index 7c6f808967f..38e20283ee5 100644 --- a/src/nix/registry.cc +++ b/src/nix/registry.cc @@ -202,6 +202,40 @@ struct CmdRegistryPin : RegistryCommand, EvalCommand } }; +struct CmdRegistryResolve : StoreCommand +{ + std::vector urls; + + std::string description() override + { + return "resolve flake references using the registry"; + } + + std::string doc() override + { + return +#include "registry-resolve.md" + ; + } + + CmdRegistryResolve() + { + expectArgs({ + .label = "flake-refs", + .handler = {&urls}, + }); + } + + void run(nix::ref store) override + { + for (auto & url : urls) { + auto ref = parseFlakeRef(fetchSettings, url); + auto resolved = ref.resolve(fetchSettings, store); + logger->cout("%s", resolved.to_string()); + } + } +}; + struct CmdRegistry : NixMultiCommand { CmdRegistry() @@ -212,6 +246,7 @@ struct CmdRegistry : NixMultiCommand {"add", []() { return make_ref(); }}, {"remove", []() { return make_ref(); }}, {"pin", []() { return make_ref(); }}, + {"resolve", []() { return make_ref(); }}, }) { } diff --git a/tests/functional/flakes/flakes.sh b/tests/functional/flakes/flakes.sh index 9458a8e45d3..0efd6b125a1 100755 --- a/tests/functional/flakes/flakes.sh +++ b/tests/functional/flakes/flakes.sh @@ -265,15 +265,20 @@ nix flake lock "$flake3Dir" nix flake update --flake "$flake3Dir" --override-flake flake2 nixpkgs [[ -n $(git -C "$flake3Dir" diff master || echo failed) ]] -# Testing the nix CLI +# Test `nix registry` commands. nix registry add flake1 flake3 [[ $(nix registry list | wc -l) == 5 ]] +[[ $(nix registry resolve flake1) = "git+file://$percentEncodedFlake3Dir" ]] nix registry pin flake1 [[ $(nix registry list | wc -l) == 5 ]] nix registry pin flake1 flake3 [[ $(nix registry list | wc -l) == 5 ]] nix registry remove flake1 [[ $(nix registry list | wc -l) == 4 ]] +[[ $(nix registry resolve flake1) = "git+file://$flake1Dir" ]] + +# Test the builtin fallback registry. +[[ $(nix registry resolve nixpkgs --flake-registry http://fail.invalid.org/sdklsdklsd --download-attempts 1) = github:NixOS/nixpkgs/nixpkgs-unstable ]] # Test 'nix registry list' with a disabled global registry. nix registry add user-flake1 git+file://"$flake1Dir"