1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-06-25 10:41:16 +02:00

Merge pull request #12759 from roberth/c-api-libflake-settings

C API / settings: remove nix-flake-c global init
This commit is contained in:
John Ericson 2025-03-27 12:38:25 -04:00 committed by GitHub
commit a26a15d05c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 277 additions and 189 deletions

View file

@ -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);
```

View file

@ -103,4 +103,4 @@ Path getNixDefExpr()
: getHome() + "/.nix-defexpr"; : getHome() + "/.nix-defexpr";
} }
} } // namespace nix

View file

@ -7,6 +7,7 @@
namespace nix { namespace nix {
class EvalState; class EvalState;
struct PrimOp;
struct EvalSettings : Config struct EvalSettings : Config
{ {
@ -50,6 +51,8 @@ struct EvalSettings : Config
LookupPathHooks lookupPathHooks; LookupPathHooks lookupPathHooks;
std::vector<PrimOp> extraPrimOps;
Setting<bool> enableNativeCode{this, false, "allow-unsafe-native-code-during-evaluation", R"( Setting<bool> enableNativeCode{this, false, "allow-unsafe-native-code-during-evaluation", R"(
Enable built-in functions that allow executing native code. Enable built-in functions that allow executing native code.

View file

@ -288,10 +288,6 @@ EvalState::EvalState(
CanonPath("derivation-internal.nix"), CanonPath("derivation-internal.nix"),
#include "primops/derivation.nix.gen.hh" #include "primops/derivation.nix.gen.hh"
)} )}
, callFlakeInternal{internalFS->addFile(
CanonPath("call-flake.nix"),
#include "call-flake.nix.gen.hh"
)}
, store(store) , store(store)
, buildStore(buildStore ? buildStore : store) , buildStore(buildStore ? buildStore : store)
, debugRepl(nullptr) , debugRepl(nullptr)
@ -353,7 +349,7 @@ EvalState::EvalState(
#include "fetchurl.nix.gen.hh" #include "fetchurl.nix.gen.hh"
); );
createBaseEnv(); createBaseEnv(settings);
} }

View file

@ -274,14 +274,12 @@ public:
/** /**
* In-memory filesystem for internal, non-user-callable Nix * In-memory filesystem for internal, non-user-callable Nix
* expressions like call-flake.nix. * expressions like `derivation.nix`.
*/ */
const ref<MemorySourceAccessor> internalFS; const ref<MemorySourceAccessor> internalFS;
const SourcePath derivationInternal; const SourcePath derivationInternal;
const SourcePath callFlakeInternal;
/** /**
* Store used to materialise .drv files. * Store used to materialise .drv files.
*/ */
@ -633,7 +631,7 @@ private:
unsigned int baseEnvDispl = 0; unsigned int baseEnvDispl = 0;
void createBaseEnv(); void createBaseEnv(const EvalSettings & settings);
Value * addConstant(const std::string & name, Value & v, Constant info); Value * addConstant(const std::string & name, Value & v, Constant info);

View file

@ -126,7 +126,6 @@ generated_headers = []
foreach header : [ foreach header : [
'imported-drv-to-derivation.nix', 'imported-drv-to-derivation.nix',
'fetchurl.nix', 'fetchurl.nix',
'call-flake.nix',
] ]
generated_headers += gen_header.process(header) generated_headers += gen_header.process(header)
endforeach endforeach

View file

@ -4675,7 +4675,7 @@ RegisterPrimOp::RegisterPrimOp(PrimOp && primOp)
} }
void EvalState::createBaseEnv() void EvalState::createBaseEnv(const EvalSettings & evalSettings)
{ {
baseEnv.up = 0; baseEnv.up = 0;
@ -4934,6 +4934,12 @@ void EvalState::createBaseEnv()
addPrimOp(std::move(primOpAdjusted)); 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 /* Add a wrapper around the derivation primop that computes the
`drvPath' and `outPath' attributes lazily. `drvPath' and `outPath' attributes lazily.

View file

@ -1,6 +1,7 @@
#include "nix_api_flake.h" #include "nix_api_flake.h"
#include "nix_api_flake_internal.hh" #include "nix_api_flake_internal.hh"
#include "nix_api_util_internal.h" #include "nix_api_util_internal.h"
#include "nix_api_expr_internal.h"
#include "flake/flake.hh" #include "flake/flake.hh"
@ -18,15 +19,11 @@ void nix_flake_settings_free(nix_flake_settings * settings)
delete 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<nix::flake::Settings> registeredSettings;
try { try {
if (registeredSettings) settings->settings->configureEvalSettings(builder->settings);
throw nix::Error("nix_flake_init_global already initialized");
registeredSettings = settings->settings;
nix::flake::initLib(*registeredSettings);
} }
NIXC_CATCH_ERRS NIXC_CATCH_ERRS
} }

View file

@ -35,9 +35,15 @@ nix_flake_settings * nix_flake_settings_new(nix_c_context * context);
void nix_flake_settings_free(nix_flake_settings * settings); 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 #ifdef __cplusplus
} // extern "C" } // extern "C"

View file

@ -25,13 +25,13 @@ TEST_F(nix_api_store_test, nix_api_init_global_getFlake_exists)
assert_ctx_ok(); assert_ctx_ok();
ASSERT_NE(nullptr, settings); 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); nix_eval_state_builder * builder = nix_eval_state_builder_new(ctx, store);
ASSERT_NE(nullptr, builder); ASSERT_NE(nullptr, builder);
assert_ctx_ok(); 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); auto state = nix_eval_state_build(ctx, builder);
assert_ctx_ok(); assert_ctx_ok();
ASSERT_NE(nullptr, state); ASSERT_NE(nullptr, state);

View file

@ -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<bool> & 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<EvalError>(
"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<bool>{attr.value->boolean()});
} else if (t == nString) {
attrs.emplace(state.symbols[attr.name], std::string(attr.value->string_view()));
} else {
state
.error<EvalError>(
"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

View file

@ -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

View file

@ -16,6 +16,8 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "memory-source-accessor.hh"
namespace nix { namespace nix {
using namespace flake; using namespace flake;
@ -921,6 +923,25 @@ LockedFlake lockFlake(
} }
} }
static ref<SourceAccessor> makeInternalFS() {
auto internalFS = make_ref<MemorySourceAccessor>(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, void callFlake(EvalState & state,
const LockedFlake & lockedFlake, const LockedFlake & lockedFlake,
Value & vRes) Value & vRes)
@ -960,8 +981,7 @@ void callFlake(EvalState & state,
auto & vOverrides = state.allocValue()->mkAttrs(overrides); auto & vOverrides = state.allocValue()->mkAttrs(overrides);
auto vCallFlake = state.allocValue(); Value * vCallFlake = requireInternalFile(state, CanonPath("call-flake.nix"));
state.evalFile(state.callFlakeInternal, *vCallFlake);
auto vLocks = state.allocValue(); auto vLocks = state.allocValue();
vLocks->mkString(lockFileStr); vLocks->mkString(lockFileStr);
@ -973,155 +993,6 @@ void callFlake(EvalState & state,
state.callFunction(*vCallFlake, args, vRes, noPos); 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<bool> & 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<EvalError>("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<bool> { attr.value->boolean() });
} else if (t == nString) {
attrs.emplace(state.symbols[attr.name],
std::string(attr.value->string_view()));
} else {
state.error<EvalError>(
"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<Fingerprint> LockedFlake::getFingerprint( std::optional<Fingerprint> LockedFlake::getFingerprint(

View file

@ -14,14 +14,6 @@ namespace flake {
struct Settings; 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; struct FlakeInput;
typedef std::map<FlakeId, FlakeInput> FlakeInputs; typedef std::map<FlakeId, FlakeInput> FlakeInputs;

View file

@ -1,7 +1,15 @@
#include "flake/settings.hh" #include "flake/settings.hh"
#include "flake/flake-primops.hh"
namespace nix::flake { namespace nix::flake {
Settings::Settings() {} 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

View file

@ -1,21 +1,24 @@
#pragma once #pragma once
///@file ///@file
#include "types.hh"
#include "config.hh" #include "config.hh"
#include "util.hh"
#include <map>
#include <limits>
#include <sys/types.h> #include <sys/types.h>
namespace nix {
// Forward declarations
struct EvalSettings;
} // namespace nix
namespace nix::flake { namespace nix::flake {
struct Settings : public Config struct Settings : public Config
{ {
Settings(); Settings();
void configureEvalSettings(nix::EvalSettings & evalSettings) const;
Setting<bool> useRegistries{ Setting<bool> useRegistries{
this, this,
true, true,

View file

@ -39,11 +39,21 @@ add_project_arguments(
subdir('nix-meson-build-support/common') 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( sources = files(
'flake/config.cc', 'flake/config.cc',
'flake/flake.cc', 'flake/flake.cc',
'flake/flakeref.cc', 'flake/flakeref.cc',
'flake/lockfile.cc', 'flake/lockfile.cc',
'flake/flake-primops.cc',
'flake/settings.cc', 'flake/settings.cc',
'flake/url-name.cc', 'flake/url-name.cc',
) )
@ -61,6 +71,7 @@ headers = files(
this_library = library( this_library = library(
'nixflake', 'nixflake',
sources, sources,
generated_headers,
dependencies : deps_public + deps_private + deps_other, dependencies : deps_public + deps_private + deps_other,
prelink : true, # For C++ static initializers prelink : true, # For C++ static initializers
install : true, install : true,

View file

@ -28,6 +28,7 @@ mkMesonLibrary (finalAttrs: {
../../.version ../../.version
./.version ./.version
./meson.build ./meson.build
./call-flake.nix
(fileset.fileFilter (file: file.hasExt "cc") ./.) (fileset.fileFilter (file: file.hasExt "cc") ./.)
(fileset.fileFilter (file: file.hasExt "hh") ./.) (fileset.fileFilter (file: file.hasExt "hh") ./.)
]; ];

View file

@ -18,6 +18,7 @@
#include "network-proxy.hh" #include "network-proxy.hh"
#include "eval-cache.hh" #include "eval-cache.hh"
#include "flake/flake.hh" #include "flake/flake.hh"
#include "flake/settings.hh"
#include "self-exe.hh" #include "self-exe.hh"
#include "json-utils.hh" #include "json-utils.hh"
#include "crash-handler.hh" #include "crash-handler.hh"
@ -368,7 +369,7 @@ void mainWrapped(int argc, char * * argv)
initNix(); initNix();
initGC(); initGC();
flake::initLib(flakeSettings); flakeSettings.configureEvalSettings(evalSettings);
/* Set the build hook location /* Set the build hook location