diff --git a/Makefile b/Makefile index 227aaf6d9..132fe29cc 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ makefiles = \ src/libfetchers/local.mk \ src/libmain/local.mk \ src/libexpr/local.mk \ + src/libflake/local.mk \ src/libcmd/local.mk \ src/nix/local.mk \ src/libutil-c/local.mk \ @@ -45,7 +46,8 @@ makefiles += \ tests/unit/libstore-support/local.mk \ tests/unit/libfetchers/local.mk \ tests/unit/libexpr/local.mk \ - tests/unit/libexpr-support/local.mk + tests/unit/libexpr-support/local.mk \ + tests/unit/libflake/local.mk endif ifeq ($(ENABLE_FUNCTIONAL_TESTS), yes) diff --git a/maintainers/flake-module.nix b/maintainers/flake-module.nix index 5febb1011..cc7241d21 100644 --- a/maintainers/flake-module.nix +++ b/maintainers/flake-module.nix @@ -65,14 +65,6 @@ ''^src/libexpr/eval-settings\.hh$'' ''^src/libexpr/eval\.cc$'' ''^src/libexpr/eval\.hh$'' - ''^src/libexpr/flake/config\.cc$'' - ''^src/libexpr/flake/flake\.cc$'' - ''^src/libexpr/flake/flake\.hh$'' - ''^src/libexpr/flake/flakeref\.cc$'' - ''^src/libexpr/flake/flakeref\.hh$'' - ''^src/libexpr/flake/lockfile\.cc$'' - ''^src/libexpr/flake/lockfile\.hh$'' - ''^src/libexpr/flake/url-name\.cc$'' ''^src/libexpr/function-trace\.cc$'' ''^src/libexpr/gc-small-vector\.hh$'' ''^src/libexpr/get-drvs\.cc$'' @@ -127,6 +119,14 @@ ''^src/libfetchers/tarball\.hh$'' ''^src/libfetchers/git\.cc$'' ''^src/libfetchers/mercurial\.cc$'' + ''^src/libflake/flake/config\.cc$'' + ''^src/libflake/flake/flake\.cc$'' + ''^src/libflake/flake/flake\.hh$'' + ''^src/libflake/flake/flakeref\.cc$'' + ''^src/libflake/flake/flakeref\.hh$'' + ''^src/libflake/flake/lockfile\.cc$'' + ''^src/libflake/flake/lockfile\.hh$'' + ''^src/libflake/flake/url-name\.cc$'' ''^src/libmain/common-args\.cc$'' ''^src/libmain/common-args\.hh$'' ''^src/libmain/loggers\.cc$'' @@ -436,8 +436,6 @@ ''^tests/unit/libexpr/derived-path\.cc'' ''^tests/unit/libexpr/error_traces\.cc'' ''^tests/unit/libexpr/eval\.cc'' - ''^tests/unit/libexpr/flake/flakeref\.cc'' - ''^tests/unit/libexpr/flake/url-name\.cc'' ''^tests/unit/libexpr/json\.cc'' ''^tests/unit/libexpr/main\.cc'' ''^tests/unit/libexpr/primops\.cc'' @@ -446,6 +444,8 @@ ''^tests/unit/libexpr/value/context\.cc'' ''^tests/unit/libexpr/value/print\.cc'' ''^tests/unit/libfetchers/public-key\.cc'' + ''^tests/unit/libflake/flakeref\.cc'' + ''^tests/unit/libflake/url-name\.cc'' ''^tests/unit/libstore-support/tests/derived-path\.cc'' ''^tests/unit/libstore-support/tests/derived-path\.hh'' ''^tests/unit/libstore-support/tests/nix_api_store\.hh'' diff --git a/src/libcmd/common-eval-args.cc b/src/libcmd/common-eval-args.cc index 77dba546d..393fed532 100644 --- a/src/libcmd/common-eval-args.cc +++ b/src/libcmd/common-eval-args.cc @@ -15,7 +15,20 @@ namespace nix { EvalSettings evalSettings { - settings.readOnlyMode + settings.readOnlyMode, + { + { + "flake", + [](ref store, std::string_view rest) { + experimentalFeatureSettings.require(Xp::Flakes); + // FIXME `parseFlakeRef` should take a `std::string_view`. + auto flakeRef = parseFlakeRef(std::string { rest }, {}, true, false); + debug("fetching flake search path element '%s''", rest); + auto storePath = flakeRef.resolve(store).fetchTree(store).first; + return store->toRealPath(storePath); + }, + }, + }, }; static GlobalConfig::Register rEvalSettings(&evalSettings); diff --git a/src/libcmd/local.mk b/src/libcmd/local.mk index 9aa33a9d3..a270333f4 100644 --- a/src/libcmd/local.mk +++ b/src/libcmd/local.mk @@ -6,10 +6,10 @@ libcmd_DIR := $(d) libcmd_SOURCES := $(wildcard $(d)/*.cc) -libcmd_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libfetchers) $(INCLUDE_libexpr) $(INCLUDE_libmain) +libcmd_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libfetchers) $(INCLUDE_libexpr) $(INCLUDE_libflake) $(INCLUDE_libmain) libcmd_LDFLAGS = $(EDITLINE_LIBS) $(LOWDOWN_LIBS) $(THREAD_LDFLAGS) -libcmd_LIBS = libstore libutil libexpr libmain libfetchers +libcmd_LIBS = libutil libstore libfetchers libflake libexpr libmain $(eval $(call install-file-in, $(buildprefix)$(d)/nix-cmd.pc, $(libdir)/pkgconfig, 0644)) diff --git a/src/libexpr/eval-settings.cc b/src/libexpr/eval-settings.cc index 11a62b0fd..6b7b52cef 100644 --- a/src/libexpr/eval-settings.cc +++ b/src/libexpr/eval-settings.cc @@ -45,8 +45,9 @@ static Strings parseNixPath(const std::string & s) return res; } -EvalSettings::EvalSettings(bool & readOnlyMode) +EvalSettings::EvalSettings(bool & readOnlyMode, EvalSettings::LookupPathHooks lookupPathHooks) : readOnlyMode{readOnlyMode} + , lookupPathHooks{lookupPathHooks} { auto var = getEnv("NIX_PATH"); if (var) nixPath = parseNixPath(*var); diff --git a/src/libexpr/eval-settings.hh b/src/libexpr/eval-settings.hh index 2689a8465..5eae708a2 100644 --- a/src/libexpr/eval-settings.hh +++ b/src/libexpr/eval-settings.hh @@ -5,9 +5,40 @@ namespace nix { +class Store; + struct EvalSettings : Config { - EvalSettings(bool & readOnlyMode); + /** + * Function used to interpet look path entries of a given scheme. + * + * The argument is the non-scheme part of the lookup path entry (see + * `LookupPathHooks` below). + * + * The return value is (a) whether the entry was valid, and, if so, + * what does it map to. + * + * @todo Return (`std::optional` of) `SourceAccssor` or something + * more structured instead of mere `std::string`? + */ + using LookupPathHook = std::optional(ref store, std::string_view); + + /** + * Map from "scheme" to a `LookupPathHook`. + * + * Given a lookup path value (i.e. either the whole thing, or after + * the `=`) in the form of: + * + * ``` + * : + * ``` + * + * if `` is a key in this map, then `` is + * passed to the hook that is the value in this map. + */ + using LookupPathHooks = std::map>; + + EvalSettings(bool & readOnlyMode, LookupPathHooks lookupPathHooks = {}); bool & readOnlyMode; @@ -17,6 +48,8 @@ struct EvalSettings : Config static std::string resolvePseudoUrl(std::string_view url); + LookupPathHooks lookupPathHooks; + Setting enableNativeCode{this, false, "allow-unsafe-native-code-during-evaluation", R"( Enable built-in functions that allow executing native code. diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 717ccc803..e2ae493cf 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -21,7 +21,6 @@ #include "url.hh" #include "fetch-to-store.hh" #include "tarball.hh" -#include "flake/flakeref.hh" #include "parser-tab.hh" #include @@ -2760,14 +2759,18 @@ std::optional EvalState::resolveLookupPathPath(const LookupPath::Pa auto i = lookupPathResolved.find(value); if (i != lookupPathResolved.end()) return i->second; - std::optional res; + auto finish = [&](std::string res) { + debug("resolved search path element '%s' to '%s'", value, res); + lookupPathResolved.emplace(value, res); + return res; + }; if (EvalSettings::isPseudoUrl(value)) { try { auto accessor = fetchers::downloadTarball( EvalSettings::resolvePseudoUrl(value)).accessor; auto storePath = fetchToStore(*store, SourcePath(accessor), FetchMode::Copy); - res = { store->toRealPath(storePath) }; + return finish(store->toRealPath(storePath)); } catch (Error & e) { logWarning({ .msg = HintFmt("Nix search path entry '%1%' cannot be downloaded, ignoring", value) @@ -2775,15 +2778,17 @@ std::optional EvalState::resolveLookupPathPath(const LookupPath::Pa } } - else if (hasPrefix(value, "flake:")) { - experimentalFeatureSettings.require(Xp::Flakes); - auto flakeRef = parseFlakeRef(value.substr(6), {}, true, false); - debug("fetching flake search path element '%s''", value); - auto storePath = flakeRef.resolve(store).fetchTree(store).first; - res = { store->toRealPath(storePath) }; + if (auto colPos = value.find(':'); colPos != value.npos) { + auto scheme = value.substr(0, colPos); + auto rest = value.substr(colPos + 1); + if (auto * hook = get(settings.lookupPathHooks, scheme)) { + auto res = (*hook)(store, rest); + if (res) + return finish(std::move(*res)); + } } - else { + { auto path = absPath(value); /* Allow access to paths in the search path. */ @@ -2800,22 +2805,17 @@ std::optional EvalState::resolveLookupPathPath(const LookupPath::Pa } if (pathExists(path)) - res = { path }; + return finish(std::move(path)); else { logWarning({ .msg = HintFmt("Nix search path entry '%1%' does not exist, ignoring", value) }); - res = std::nullopt; } } - if (res) - debug("resolved search path element '%s' to '%s'", value, *res); - else - debug("failed to resolve search path element '%s'", value); + debug("failed to resolve search path element '%s'", value); + return std::nullopt; - lookupPathResolved.emplace(value, res); - return res; } diff --git a/src/libexpr/local.mk b/src/libexpr/local.mk index d128064a5..95ce4de63 100644 --- a/src/libexpr/local.mk +++ b/src/libexpr/local.mk @@ -8,7 +8,6 @@ libexpr_SOURCES := \ $(wildcard $(d)/*.cc) \ $(wildcard $(d)/value/*.cc) \ $(wildcard $(d)/primops/*.cc) \ - $(wildcard $(d)/flake/*.cc) \ $(d)/lexer-tab.cc \ $(d)/parser-tab.cc # Not just for this library itself, but also for downstream libraries using this library @@ -45,8 +44,6 @@ $(eval $(call install-file-in, $(buildprefix)$(d)/nix-expr.pc, $(libdir)/pkgconf $(foreach i, $(wildcard src/libexpr/value/*.hh), \ $(eval $(call install-file-in, $(i), $(includedir)/nix/value, 0644))) -$(foreach i, $(wildcard src/libexpr/flake/*.hh), \ - $(eval $(call install-file-in, $(i), $(includedir)/nix/flake, 0644))) $(d)/primops.cc: $(d)/imported-drv-to-derivation.nix.gen.hh diff --git a/src/libfetchers/fetch-settings.hh b/src/libfetchers/fetch-settings.hh index 50cd4d161..629967697 100644 --- a/src/libfetchers/fetch-settings.hh +++ b/src/libfetchers/fetch-settings.hh @@ -70,30 +70,6 @@ struct FetchSettings : public Config Setting warnDirty{this, true, "warn-dirty", "Whether to warn about dirty Git/Mercurial trees."}; - Setting flakeRegistry{this, "https://channels.nixos.org/flake-registry.json", "flake-registry", - R"( - Path or URI of the global flake registry. - - When empty, disables the global flake registry. - )", - {}, true, Xp::Flakes}; - - Setting useRegistries{this, true, "use-registries", - "Whether to use flake registries to resolve flake references.", - {}, true, Xp::Flakes}; - - Setting acceptFlakeConfig{this, false, "accept-flake-config", - "Whether to accept nix configuration from a flake without prompting.", - {}, true, Xp::Flakes}; - - Setting commitLockFileSummary{ - this, "", "commit-lock-file-summary", - R"( - The commit summary to use when committing changed flake lock files. If - empty, the summary is generated based on the action performed. - )", - {"commit-lockfile-summary"}, true, Xp::Flakes}; - Setting trustTarballsFromGitForges{ this, true, "trust-tarballs-from-git-forges", R"( @@ -108,7 +84,6 @@ struct FetchSettings : public Config `narHash` attribute is specified, e.g. `github:NixOS/patchelf/7c2f768bf9601268a4e71c2ebe91e2011918a70f?narHash=sha256-PPXqKY2hJng4DBVE0I4xshv/vGLUskL7jl53roB8UdU%3D`. )"}; - }; // FIXME: don't use a global variable. diff --git a/src/libfetchers/registry.cc b/src/libfetchers/registry.cc index e00b9de46..52cbac5e0 100644 --- a/src/libfetchers/registry.cc +++ b/src/libfetchers/registry.cc @@ -1,12 +1,11 @@ #include "registry.hh" #include "tarball.hh" #include "users.hh" +#include "config-global.hh" #include "globals.hh" #include "store-api.hh" #include "local-fs-store.hh" -#include "fetch-settings.hh" - #include namespace nix::fetchers { @@ -149,10 +148,25 @@ void overrideRegistry( flagRegistry->add(from, to, extraAttrs); } +struct RegistrySettings : Config +{ + Setting flakeRegistry{this, "https://channels.nixos.org/flake-registry.json", "flake-registry", + R"( + Path or URI of the global flake registry. + + When empty, disables the global flake registry. + )", + {}, true, Xp::Flakes}; +}; + +RegistrySettings registrySettings; + +static GlobalConfig::Register rRegistrySettings(®istrySettings); + static std::shared_ptr getGlobalRegistry(ref store) { static auto reg = [&]() { - auto path = fetchSettings.flakeRegistry.get(); + auto path = registrySettings.flakeRegistry.get(); if (path == "") { return std::make_shared(Registry::Global); // empty registry } diff --git a/src/libflake/flake-settings.cc b/src/libflake/flake-settings.cc new file mode 100644 index 000000000..ba97e0ce7 --- /dev/null +++ b/src/libflake/flake-settings.cc @@ -0,0 +1,12 @@ +#include "flake-settings.hh" +#include "config-global.hh" + +namespace nix { + +FlakeSettings::FlakeSettings() {} + +FlakeSettings flakeSettings; + +static GlobalConfig::Register rFlakeSettings(&flakeSettings); + +} diff --git a/src/libflake/flake-settings.hh b/src/libflake/flake-settings.hh new file mode 100644 index 000000000..1087c0eba --- /dev/null +++ b/src/libflake/flake-settings.hh @@ -0,0 +1,53 @@ +#pragma once +///@file + +#include "types.hh" +#include "config.hh" +#include "util.hh" + +#include +#include + +#include + +namespace nix { + +struct FlakeSettings : public Config +{ + FlakeSettings(); + + Setting useRegistries{ + this, + true, + "use-registries", + "Whether to use flake registries to resolve flake references.", + {}, + true, + Xp::Flakes}; + + Setting acceptFlakeConfig{ + this, + false, + "accept-flake-config", + "Whether to accept nix configuration from a flake without prompting.", + {}, + true, + Xp::Flakes}; + + Setting commitLockFileSummary{ + this, + "", + "commit-lockfile-summary", + R"( + The commit summary to use when committing changed flake lock files. If + empty, the summary is generated based on the action performed. + )", + {}, + true, + Xp::Flakes}; +}; + +// TODO: don't use a global variable. +extern FlakeSettings flakeSettings; + +} diff --git a/src/libexpr/flake/config.cc b/src/libflake/flake/config.cc similarity index 96% rename from src/libexpr/flake/config.cc rename to src/libflake/flake/config.cc index b348f6d44..498595359 100644 --- a/src/libexpr/flake/config.cc +++ b/src/libflake/flake/config.cc @@ -1,6 +1,6 @@ #include "users.hh" #include "config-global.hh" -#include "fetch-settings.hh" +#include "flake-settings.hh" #include "flake.hh" #include @@ -51,7 +51,7 @@ void ConfigFile::apply() else assert(false); - if (!whitelist.count(baseName) && !nix::fetchSettings.acceptFlakeConfig) { + if (!whitelist.count(baseName) && !nix::flakeSettings.acceptFlakeConfig) { bool trusted = false; auto trustedList = readTrustedList(); auto tlname = get(trustedList, name); diff --git a/src/libexpr/flake/flake.cc b/src/libflake/flake/flake.cc similarity index 99% rename from src/libexpr/flake/flake.cc rename to src/libflake/flake/flake.cc index 67b19bd57..93d528d61 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libflake/flake/flake.cc @@ -9,6 +9,7 @@ #include "fetchers.hh" #include "finally.hh" #include "fetch-settings.hh" +#include "flake-settings.hh" #include "value-to-json.hh" #include "local-fs-store.hh" @@ -346,7 +347,7 @@ LockedFlake lockFlake( FlakeCache flakeCache; - auto useRegistries = lockFlags.useRegistries.value_or(fetchSettings.useRegistries); + auto useRegistries = lockFlags.useRegistries.value_or(flakeSettings.useRegistries); auto flake = getFlake(state, topRef, useRegistries, flakeCache); @@ -691,7 +692,7 @@ LockedFlake lockFlake( if (lockFlags.commitLockFile) { std::string cm; - cm = fetchSettings.commitLockFileSummary.get(); + cm = flakeSettings.commitLockFileSummary.get(); if (cm == "") { cm = fmt("%s: %s", relPath, lockFileExists ? "Update" : "Add"); @@ -811,7 +812,7 @@ static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, V LockFlags { .updateLockFile = false, .writeLockFile = false, - .useRegistries = !state.settings.pureEval && fetchSettings.useRegistries, + .useRegistries = !state.settings.pureEval && flakeSettings.useRegistries, .allowUnlocked = !state.settings.pureEval, }), v); diff --git a/src/libexpr/flake/flake.hh b/src/libflake/flake/flake.hh similarity index 100% rename from src/libexpr/flake/flake.hh rename to src/libflake/flake/flake.hh diff --git a/src/libexpr/flake/flakeref.cc b/src/libflake/flake/flakeref.cc similarity index 100% rename from src/libexpr/flake/flakeref.cc rename to src/libflake/flake/flakeref.cc diff --git a/src/libexpr/flake/flakeref.hh b/src/libflake/flake/flakeref.hh similarity index 100% rename from src/libexpr/flake/flakeref.hh rename to src/libflake/flake/flakeref.hh diff --git a/src/libexpr/flake/lockfile.cc b/src/libflake/flake/lockfile.cc similarity index 100% rename from src/libexpr/flake/lockfile.cc rename to src/libflake/flake/lockfile.cc diff --git a/src/libexpr/flake/lockfile.hh b/src/libflake/flake/lockfile.hh similarity index 100% rename from src/libexpr/flake/lockfile.hh rename to src/libflake/flake/lockfile.hh diff --git a/src/libexpr/flake/url-name.cc b/src/libflake/flake/url-name.cc similarity index 100% rename from src/libexpr/flake/url-name.cc rename to src/libflake/flake/url-name.cc diff --git a/src/libexpr/flake/url-name.hh b/src/libflake/flake/url-name.hh similarity index 100% rename from src/libexpr/flake/url-name.hh rename to src/libflake/flake/url-name.hh diff --git a/src/libflake/local.mk b/src/libflake/local.mk new file mode 100644 index 000000000..2cceda2bf --- /dev/null +++ b/src/libflake/local.mk @@ -0,0 +1,17 @@ +libraries += libflake + +libflake_NAME = libnixflake + +libflake_DIR := $(d) + +libflake_SOURCES := $(wildcard $(d)/*.cc $(d)/flake/*.cc) + +# Not just for this library itself, but also for downstream libraries using this library + +INCLUDE_libflake := -I $(d) + +libflake_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libfetchers) $(INCLUDE_libexpr) $(INCLUDE_libflake) + +libflake_LDFLAGS += $(THREAD_LDFLAGS) + +libflake_LIBS = libutil libstore libfetchers libexpr diff --git a/src/nix/local.mk b/src/nix/local.mk index 9883509fb..4b6117330 100644 --- a/src/nix/local.mk +++ b/src/nix/local.mk @@ -24,9 +24,9 @@ ifdef HOST_UNIX INCLUDE_nix += -I $(d)/unix endif -nix_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libfetchers) $(INCLUDE_libexpr) $(INCLUDE_libmain) -I src/libcmd -I doc/manual $(INCLUDE_nix) +nix_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libfetchers) $(INCLUDE_libexpr) $(INCLUDE_libflake) $(INCLUDE_libmain) -I src/libcmd -I doc/manual $(INCLUDE_nix) -nix_LIBS = libexpr libmain libfetchers libstore libutil libcmd +nix_LIBS = libexpr libmain libfetchers libflake libstore libutil libcmd nix_LDFLAGS = $(THREAD_LDFLAGS) $(SODIUM_LIBS) $(EDITLINE_LIBS) $(BOOST_LDFLAGS) $(LOWDOWN_LIBS) diff --git a/tests/unit/libexpr/flake/flakeref.cc b/tests/unit/libflake/flakeref.cc similarity index 100% rename from tests/unit/libexpr/flake/flakeref.cc rename to tests/unit/libflake/flakeref.cc diff --git a/tests/unit/libflake/local.mk b/tests/unit/libflake/local.mk new file mode 100644 index 000000000..590bcf7c0 --- /dev/null +++ b/tests/unit/libflake/local.mk @@ -0,0 +1,43 @@ +check: libflake-tests_RUN + +programs += libflake-tests + +libflake-tests_NAME := libnixflake-tests + +libflake-tests_ENV := _NIX_TEST_UNIT_DATA=$(d)/data GTEST_OUTPUT=xml:$$testresults/libflake-tests.xml + +libflake-tests_DIR := $(d) + +ifeq ($(INSTALL_UNIT_TESTS), yes) + libflake-tests_INSTALL_DIR := $(checkbindir) +else + libflake-tests_INSTALL_DIR := +endif + +libflake-tests_SOURCES := \ + $(wildcard $(d)/*.cc) \ + $(wildcard $(d)/value/*.cc) \ + $(wildcard $(d)/flake/*.cc) + +libflake-tests_EXTRA_INCLUDES = \ + -I tests/unit/libflake-support \ + -I tests/unit/libstore-support \ + -I tests/unit/libutil-support \ + $(INCLUDE_libflake) \ + $(INCLUDE_libexpr) \ + $(INCLUDE_libfetchers) \ + $(INCLUDE_libstore) \ + $(INCLUDE_libutil) \ + +libflake-tests_CXXFLAGS += $(libflake-tests_EXTRA_INCLUDES) + +libflake-tests_LIBS = \ + libexpr-test-support libstore-test-support libutil-test-support \ + libflake libexpr libfetchers libstore libutil + +libflake-tests_LDFLAGS := -lrapidcheck $(GTEST_LIBS) -lgmock + +ifdef HOST_WINDOWS + # Increase the default reserved stack size to 65 MB so Nix doesn't run out of space + libflake-tests_LDFLAGS += -Wl,--stack,$(shell echo $$((65 * 1024 * 1024))) +endif diff --git a/tests/unit/libexpr/flake/url-name.cc b/tests/unit/libflake/url-name.cc similarity index 100% rename from tests/unit/libexpr/flake/url-name.cc rename to tests/unit/libflake/url-name.cc