diff --git a/doc/manual/rl-next/c-api-flake-init.md b/doc/manual/rl-next/c-api-flake-init.md new file mode 100644 index 000000000..d6e7c3890 --- /dev/null +++ b/doc/manual/rl-next/c-api-flake-init.md @@ -0,0 +1,20 @@ +--- +synopsis: C API `nix_flake_init_global` removed +prs: 12759 +issues: 5638 +--- + +In order to improve the modularity of the code base, we are removing a use of global state, and therefore the `nix_flake_init_global` function. + +Instead, use `nix_flake_settings_add_to_eval_state_builder`. For example: + +```diff +- nix_flake_init_global(ctx, settings); +- HANDLE_ERROR(ctx); +- + nix_eval_state_builder * builder = nix_eval_state_builder_new(ctx, store); + HANDLE_ERROR(ctx); + ++ nix_flake_settings_add_to_eval_state_builder(ctx, settings, builder); ++ HANDLE_ERROR(ctx); +``` diff --git a/src/libexpr/eval-settings.cc b/src/libexpr/eval-settings.cc index ade0abf9a..b54afdce1 100644 --- a/src/libexpr/eval-settings.cc +++ b/src/libexpr/eval-settings.cc @@ -103,4 +103,4 @@ Path getNixDefExpr() : getHome() + "/.nix-defexpr"; } -} +} // namespace nix \ No newline at end of file diff --git a/src/libexpr/eval-settings.hh b/src/libexpr/eval-settings.hh index fe947aefd..d16fd4035 100644 --- a/src/libexpr/eval-settings.hh +++ b/src/libexpr/eval-settings.hh @@ -7,6 +7,7 @@ namespace nix { class EvalState; +struct PrimOp; struct EvalSettings : Config { @@ -50,6 +51,8 @@ struct EvalSettings : Config LookupPathHooks lookupPathHooks; + std::vector extraPrimOps; + 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 369c49672..2dcee49d9 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -288,10 +288,6 @@ EvalState::EvalState( CanonPath("derivation-internal.nix"), #include "primops/derivation.nix.gen.hh" )} - , callFlakeInternal{internalFS->addFile( - CanonPath("call-flake.nix"), - #include "call-flake.nix.gen.hh" - )} , store(store) , buildStore(buildStore ? buildStore : store) , debugRepl(nullptr) @@ -353,7 +349,7 @@ EvalState::EvalState( #include "fetchurl.nix.gen.hh" ); - createBaseEnv(); + createBaseEnv(settings); } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index d57da35d1..8bb8bbd32 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -274,14 +274,12 @@ public: /** * In-memory filesystem for internal, non-user-callable Nix - * expressions like call-flake.nix. + * expressions like `derivation.nix`. */ const ref internalFS; const SourcePath derivationInternal; - const SourcePath callFlakeInternal; - /** * Store used to materialise .drv files. */ @@ -633,7 +631,7 @@ private: unsigned int baseEnvDispl = 0; - void createBaseEnv(); + void createBaseEnv(const EvalSettings & settings); Value * addConstant(const std::string & name, Value & v, Constant info); diff --git a/src/libexpr/meson.build b/src/libexpr/meson.build index dffcc1742..348c2320e 100644 --- a/src/libexpr/meson.build +++ b/src/libexpr/meson.build @@ -126,7 +126,6 @@ generated_headers = [] foreach header : [ 'imported-drv-to-derivation.nix', 'fetchurl.nix', - 'call-flake.nix', ] generated_headers += gen_header.process(header) endforeach diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index e011e5c7a..05c8ed3df 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -4675,7 +4675,7 @@ RegisterPrimOp::RegisterPrimOp(PrimOp && primOp) } -void EvalState::createBaseEnv() +void EvalState::createBaseEnv(const EvalSettings & evalSettings) { baseEnv.up = 0; @@ -4934,6 +4934,12 @@ void EvalState::createBaseEnv() addPrimOp(std::move(primOpAdjusted)); } + for (auto & primOp : evalSettings.extraPrimOps) { + auto primOpAdjusted = primOp; + primOpAdjusted.arity = std::max(primOp.args.size(), primOp.arity); + addPrimOp(std::move(primOpAdjusted)); + } + /* Add a wrapper around the derivation primop that computes the `drvPath' and `outPath' attributes lazily. diff --git a/src/libflake-c/nix_api_flake.cc b/src/libflake-c/nix_api_flake.cc index 17cf6572d..2479bf2e0 100644 --- a/src/libflake-c/nix_api_flake.cc +++ b/src/libflake-c/nix_api_flake.cc @@ -1,6 +1,7 @@ #include "nix_api_flake.h" #include "nix_api_flake_internal.hh" #include "nix_api_util_internal.h" +#include "nix_api_expr_internal.h" #include "flake/flake.hh" @@ -18,15 +19,11 @@ void nix_flake_settings_free(nix_flake_settings * settings) delete settings; } -nix_err nix_flake_init_global(nix_c_context * context, nix_flake_settings * settings) +nix_err nix_flake_settings_add_to_eval_state_builder( + nix_c_context * context, nix_flake_settings * settings, nix_eval_state_builder * builder) { - static std::shared_ptr registeredSettings; try { - if (registeredSettings) - throw nix::Error("nix_flake_init_global already initialized"); - - registeredSettings = settings->settings; - nix::flake::initLib(*registeredSettings); + settings->settings->configureEvalSettings(builder->settings); } NIXC_CATCH_ERRS } diff --git a/src/libflake-c/nix_api_flake.h b/src/libflake-c/nix_api_flake.h index 80051298d..75675835e 100644 --- a/src/libflake-c/nix_api_flake.h +++ b/src/libflake-c/nix_api_flake.h @@ -35,9 +35,15 @@ nix_flake_settings * nix_flake_settings_new(nix_c_context * context); void nix_flake_settings_free(nix_flake_settings * settings); /** - * @brief Register Flakes support process-wide. + * @brief Initialize a `nix_flake_settings` to contain `builtins.getFlake` and + * potentially more. + * + * @param[out] context Optional, stores error information + * @param[in] settings The settings to use for e.g. `builtins.getFlake` + * @param[in] builder The builder to modify */ -nix_err nix_flake_init_global(nix_c_context * context, nix_flake_settings * settings); +nix_err nix_flake_settings_add_to_eval_state_builder( + nix_c_context * context, nix_flake_settings * settings, nix_eval_state_builder * builder); #ifdef __cplusplus } // extern "C" diff --git a/src/libflake-tests/nix_api_flake.cc b/src/libflake-tests/nix_api_flake.cc index 1678e3e55..9aa7773a0 100644 --- a/src/libflake-tests/nix_api_flake.cc +++ b/src/libflake-tests/nix_api_flake.cc @@ -25,13 +25,13 @@ TEST_F(nix_api_store_test, nix_api_init_global_getFlake_exists) assert_ctx_ok(); ASSERT_NE(nullptr, settings); - nix_flake_init_global(ctx, settings); - assert_ctx_ok(); - nix_eval_state_builder * builder = nix_eval_state_builder_new(ctx, store); ASSERT_NE(nullptr, builder); assert_ctx_ok(); + nix_flake_settings_add_to_eval_state_builder(ctx, settings, builder); + assert_ctx_ok(); + auto state = nix_eval_state_build(ctx, builder); assert_ctx_ok(); ASSERT_NE(nullptr, state); diff --git a/src/libexpr/call-flake.nix b/src/libflake/call-flake.nix similarity index 100% rename from src/libexpr/call-flake.nix rename to src/libflake/call-flake.nix diff --git a/src/libflake/flake/flake-primops.cc b/src/libflake/flake/flake-primops.cc new file mode 100644 index 000000000..98ebdee5f --- /dev/null +++ b/src/libflake/flake/flake-primops.cc @@ -0,0 +1,160 @@ +#include "flake-primops.hh" +#include "eval.hh" +#include "flake.hh" +#include "flakeref.hh" +#include "settings.hh" + +namespace nix::flake::primops { + +PrimOp getFlake(const Settings & settings) +{ + auto prim_getFlake = [&settings](EvalState & state, const PosIdx pos, Value ** args, Value & v) { + std::string flakeRefS( + state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake")); + auto flakeRef = nix::parseFlakeRef(state.fetchSettings, flakeRefS, {}, true); + if (state.settings.pureEval && !flakeRef.input.isLocked()) + throw Error( + "cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", + flakeRefS, + state.positions[pos]); + + callFlake( + state, + lockFlake( + settings, + state, + flakeRef, + LockFlags{ + .updateLockFile = false, + .writeLockFile = false, + .useRegistries = !state.settings.pureEval && settings.useRegistries, + .allowUnlocked = !state.settings.pureEval, + }), + v); + }; + + return PrimOp{ + .name = "__getFlake", + .args = {"args"}, + .doc = R"( + Fetch a flake from a flake reference, and return its output attributes and some metadata. For example: + + ```nix + (builtins.getFlake "nix/55bc52401966fbffa525c574c14f67b00bc4fb3a").packages.x86_64-linux.nix + ``` + + Unless impure evaluation is allowed (`--impure`), the flake reference + must be "locked", e.g. contain a Git revision or content hash. An + example of an unlocked usage is: + + ```nix + (builtins.getFlake "github:edolstra/dwarffs").rev + ``` + )", + .fun = prim_getFlake, + .experimentalFeature = Xp::Flakes, + }; +} + +static void prim_parseFlakeRef(EvalState & state, const PosIdx pos, Value ** args, Value & v) +{ + std::string flakeRefS( + state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.parseFlakeRef")); + auto attrs = nix::parseFlakeRef(state.fetchSettings, flakeRefS, {}, true).toAttrs(); + auto binds = state.buildBindings(attrs.size()); + for (const auto & [key, value] : attrs) { + auto s = state.symbols.create(key); + auto & vv = binds.alloc(s); + std::visit( + overloaded{ + [&vv](const std::string & value) { vv.mkString(value); }, + [&vv](const uint64_t & value) { vv.mkInt(value); }, + [&vv](const Explicit & value) { vv.mkBool(value.t); }}, + value); + } + v.mkAttrs(binds); +} + +nix::PrimOp parseFlakeRef({ + .name = "__parseFlakeRef", + .args = {"flake-ref"}, + .doc = R"( + Parse a flake reference, and return its exploded form. + + For example: + + ```nix + builtins.parseFlakeRef "github:NixOS/nixpkgs/23.05?dir=lib" + ``` + + evaluates to: + + ```nix + { dir = "lib"; owner = "NixOS"; ref = "23.05"; repo = "nixpkgs"; type = "github"; } + ``` + )", + .fun = prim_parseFlakeRef, + .experimentalFeature = Xp::Flakes, +}); + +static void prim_flakeRefToString(EvalState & state, const PosIdx pos, Value ** args, Value & v) +{ + state.forceAttrs(*args[0], noPos, "while evaluating the argument passed to builtins.flakeRefToString"); + fetchers::Attrs attrs; + for (const auto & attr : *args[0]->attrs()) { + auto t = attr.value->type(); + if (t == nInt) { + auto intValue = attr.value->integer().value; + + if (intValue < 0) { + state + .error( + "negative value given for flake ref attr %1%: %2%", state.symbols[attr.name], intValue) + .atPos(pos) + .debugThrow(); + } + + attrs.emplace(state.symbols[attr.name], uint64_t(intValue)); + } else if (t == nBool) { + attrs.emplace(state.symbols[attr.name], Explicit{attr.value->boolean()}); + } else if (t == nString) { + attrs.emplace(state.symbols[attr.name], std::string(attr.value->string_view())); + } else { + state + .error( + "flake reference attribute sets may only contain integers, Booleans, " + "and strings, but attribute '%s' is %s", + state.symbols[attr.name], + showType(*attr.value)) + .debugThrow(); + } + } + auto flakeRef = FlakeRef::fromAttrs(state.fetchSettings, attrs); + v.mkString(flakeRef.to_string()); +} + +nix::PrimOp flakeRefToString({ + .name = "__flakeRefToString", + .args = {"attrs"}, + .doc = R"( + Convert a flake reference from attribute set format to URL format. + + For example: + + ```nix + builtins.flakeRefToString { + dir = "lib"; owner = "NixOS"; ref = "23.05"; repo = "nixpkgs"; type = "github"; + } + ``` + + evaluates to + + ```nix + "github:NixOS/nixpkgs/23.05?dir=lib" + ``` + )", + .fun = prim_flakeRefToString, + .experimentalFeature = Xp::Flakes, +}); + +} // namespace nix::flake::primops diff --git a/src/libflake/flake/flake-primops.hh b/src/libflake/flake/flake-primops.hh new file mode 100644 index 000000000..203060563 --- /dev/null +++ b/src/libflake/flake/flake-primops.hh @@ -0,0 +1,16 @@ +#pragma once + +#include "eval.hh" +#include "flake/settings.hh" + +namespace nix::flake::primops { + +/** + * Returns a `builtins.getFlake` primop with the given nix::flake::Settings. + */ +nix::PrimOp getFlake(const Settings & settings); + +extern nix::PrimOp parseFlakeRef; +extern nix::PrimOp flakeRefToString; + +} // namespace nix::flake \ No newline at end of file diff --git a/src/libflake/flake/flake.cc b/src/libflake/flake/flake.cc index 475e159cd..183259137 100644 --- a/src/libflake/flake/flake.cc +++ b/src/libflake/flake/flake.cc @@ -16,6 +16,8 @@ #include +#include "memory-source-accessor.hh" + namespace nix { using namespace flake; @@ -921,6 +923,25 @@ LockedFlake lockFlake( } } +static ref makeInternalFS() { + auto internalFS = make_ref(MemorySourceAccessor {}); + internalFS->setPathDisplay("«flakes-internal»", ""); + internalFS->addFile( + CanonPath("call-flake.nix"), + #include "call-flake.nix.gen.hh" + ); + return internalFS; +} + +static auto internalFS = makeInternalFS(); + +static Value * requireInternalFile(EvalState & state, CanonPath path) { + SourcePath p {internalFS, path}; + auto v = state.allocValue(); + state.evalFile(p, *v); // has caching + return v; +} + void callFlake(EvalState & state, const LockedFlake & lockedFlake, Value & vRes) @@ -960,8 +981,7 @@ void callFlake(EvalState & state, auto & vOverrides = state.allocValue()->mkAttrs(overrides); - auto vCallFlake = state.allocValue(); - state.evalFile(state.callFlakeInternal, *vCallFlake); + Value * vCallFlake = requireInternalFile(state, CanonPath("call-flake.nix")); auto vLocks = state.allocValue(); vLocks->mkString(lockFileStr); @@ -973,155 +993,6 @@ void callFlake(EvalState & state, state.callFunction(*vCallFlake, args, vRes, noPos); } -void initLib(const Settings & settings) -{ - auto prim_getFlake = [&settings](EvalState & state, const PosIdx pos, Value * * args, Value & v) - { - std::string flakeRefS(state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake")); - auto flakeRef = parseFlakeRef(state.fetchSettings, flakeRefS, {}, true); - if (state.settings.pureEval && !flakeRef.input.isLocked()) - throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, state.positions[pos]); - - callFlake(state, - lockFlake(settings, state, flakeRef, - LockFlags { - .updateLockFile = false, - .writeLockFile = false, - .useRegistries = !state.settings.pureEval && settings.useRegistries, - .allowUnlocked = !state.settings.pureEval, - }), - v); - }; - - RegisterPrimOp::primOps->push_back({ - .name = "__getFlake", - .args = {"args"}, - .doc = R"( - Fetch a flake from a flake reference, and return its output attributes and some metadata. For example: - - ```nix - (builtins.getFlake "nix/55bc52401966fbffa525c574c14f67b00bc4fb3a").packages.x86_64-linux.nix - ``` - - Unless impure evaluation is allowed (`--impure`), the flake reference - must be "locked", e.g. contain a Git revision or content hash. An - example of an unlocked usage is: - - ```nix - (builtins.getFlake "github:edolstra/dwarffs").rev - ``` - )", - .fun = prim_getFlake, - .experimentalFeature = Xp::Flakes, - }); -} - -static void prim_parseFlakeRef( - EvalState & state, - const PosIdx pos, - Value * * args, - Value & v) -{ - std::string flakeRefS(state.forceStringNoCtx(*args[0], pos, - "while evaluating the argument passed to builtins.parseFlakeRef")); - auto attrs = parseFlakeRef(state.fetchSettings, flakeRefS, {}, true).toAttrs(); - auto binds = state.buildBindings(attrs.size()); - for (const auto & [key, value] : attrs) { - auto s = state.symbols.create(key); - auto & vv = binds.alloc(s); - std::visit(overloaded { - [&vv](const std::string & value) { vv.mkString(value); }, - [&vv](const uint64_t & value) { vv.mkInt(value); }, - [&vv](const Explicit & value) { vv.mkBool(value.t); } - }, value); - } - v.mkAttrs(binds); -} - -static RegisterPrimOp r3({ - .name = "__parseFlakeRef", - .args = {"flake-ref"}, - .doc = R"( - Parse a flake reference, and return its exploded form. - - For example: - - ```nix - builtins.parseFlakeRef "github:NixOS/nixpkgs/23.05?dir=lib" - ``` - - evaluates to: - - ```nix - { dir = "lib"; owner = "NixOS"; ref = "23.05"; repo = "nixpkgs"; type = "github"; } - ``` - )", - .fun = prim_parseFlakeRef, - .experimentalFeature = Xp::Flakes, -}); - - -static void prim_flakeRefToString( - EvalState & state, - const PosIdx pos, - Value * * args, - Value & v) -{ - state.forceAttrs(*args[0], noPos, - "while evaluating the argument passed to builtins.flakeRefToString"); - fetchers::Attrs attrs; - for (const auto & attr : *args[0]->attrs()) { - auto t = attr.value->type(); - if (t == nInt) { - auto intValue = attr.value->integer().value; - - if (intValue < 0) { - state.error("negative value given for flake ref attr %1%: %2%", state.symbols[attr.name], intValue).atPos(pos).debugThrow(); - } - - attrs.emplace(state.symbols[attr.name], uint64_t(intValue)); - } else if (t == nBool) { - attrs.emplace(state.symbols[attr.name], - Explicit { attr.value->boolean() }); - } else if (t == nString) { - attrs.emplace(state.symbols[attr.name], - std::string(attr.value->string_view())); - } else { - state.error( - "flake reference attribute sets may only contain integers, Booleans, " - "and strings, but attribute '%s' is %s", - state.symbols[attr.name], - showType(*attr.value)).debugThrow(); - } - } - auto flakeRef = FlakeRef::fromAttrs(state.fetchSettings, attrs); - v.mkString(flakeRef.to_string()); -} - -static RegisterPrimOp r4({ - .name = "__flakeRefToString", - .args = {"attrs"}, - .doc = R"( - Convert a flake reference from attribute set format to URL format. - - For example: - - ```nix - builtins.flakeRefToString { - dir = "lib"; owner = "NixOS"; ref = "23.05"; repo = "nixpkgs"; type = "github"; - } - ``` - - evaluates to - - ```nix - "github:NixOS/nixpkgs/23.05?dir=lib" - ``` - )", - .fun = prim_flakeRefToString, - .experimentalFeature = Xp::Flakes, -}); - } std::optional LockedFlake::getFingerprint( diff --git a/src/libflake/flake/flake.hh b/src/libflake/flake/flake.hh index d8cd9aac0..d7a151587 100644 --- a/src/libflake/flake/flake.hh +++ b/src/libflake/flake/flake.hh @@ -14,14 +14,6 @@ namespace flake { struct Settings; -/** - * Initialize `libnixflake` - * - * So far, this registers the `builtins.getFlake` primop, which depends - * on the choice of `flake:Settings`. - */ -void initLib(const Settings & settings); - struct FlakeInput; typedef std::map FlakeInputs; diff --git a/src/libflake/flake/settings.cc b/src/libflake/flake/settings.cc index 6a0294e62..cac7c4384 100644 --- a/src/libflake/flake/settings.cc +++ b/src/libflake/flake/settings.cc @@ -1,7 +1,15 @@ #include "flake/settings.hh" +#include "flake/flake-primops.hh" namespace nix::flake { Settings::Settings() {} +void Settings::configureEvalSettings(nix::EvalSettings & evalSettings) const +{ + evalSettings.extraPrimOps.emplace_back(primops::getFlake(*this)); + evalSettings.extraPrimOps.emplace_back(primops::parseFlakeRef); + evalSettings.extraPrimOps.emplace_back(primops::flakeRefToString); } + +} // namespace nix diff --git a/src/libflake/flake/settings.hh b/src/libflake/flake/settings.hh index 991eaca1f..5f0d9fb21 100644 --- a/src/libflake/flake/settings.hh +++ b/src/libflake/flake/settings.hh @@ -1,21 +1,24 @@ #pragma once ///@file -#include "types.hh" #include "config.hh" -#include "util.hh" - -#include -#include #include +namespace nix { +// Forward declarations +struct EvalSettings; + +} // namespace nix + namespace nix::flake { struct Settings : public Config { Settings(); + void configureEvalSettings(nix::EvalSettings & evalSettings) const; + Setting useRegistries{ this, true, diff --git a/src/libflake/meson.build b/src/libflake/meson.build index b757d0d76..006d7911e 100644 --- a/src/libflake/meson.build +++ b/src/libflake/meson.build @@ -39,11 +39,21 @@ add_project_arguments( subdir('nix-meson-build-support/common') +subdir('nix-meson-build-support/generate-header') + +generated_headers = [] +foreach header : [ + 'call-flake.nix', +] + generated_headers += gen_header.process(header) +endforeach + sources = files( 'flake/config.cc', 'flake/flake.cc', 'flake/flakeref.cc', 'flake/lockfile.cc', + 'flake/flake-primops.cc', 'flake/settings.cc', 'flake/url-name.cc', ) @@ -61,6 +71,7 @@ headers = files( this_library = library( 'nixflake', sources, + generated_headers, dependencies : deps_public + deps_private + deps_other, prelink : true, # For C++ static initializers install : true, diff --git a/src/libflake/package.nix b/src/libflake/package.nix index 5240ce5e3..d7250c252 100644 --- a/src/libflake/package.nix +++ b/src/libflake/package.nix @@ -28,6 +28,7 @@ mkMesonLibrary (finalAttrs: { ../../.version ./.version ./meson.build + ./call-flake.nix (fileset.fileFilter (file: file.hasExt "cc") ./.) (fileset.fileFilter (file: file.hasExt "hh") ./.) ]; diff --git a/src/nix/main.cc b/src/nix/main.cc index 0a6b77e9e..188d424bc 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -18,6 +18,7 @@ #include "network-proxy.hh" #include "eval-cache.hh" #include "flake/flake.hh" +#include "flake/settings.hh" #include "self-exe.hh" #include "json-utils.hh" #include "crash-handler.hh" @@ -368,7 +369,7 @@ void mainWrapped(int argc, char * * argv) initNix(); initGC(); - flake::initLib(flakeSettings); + flakeSettings.configureEvalSettings(evalSettings); /* Set the build hook location