1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-13 14:02:42 +01:00

Merge commit '1af94bf47' into progress-bar

This commit is contained in:
Eelco Dolstra 2024-12-11 14:59:44 +01:00
commit a41fdfc5aa
2311 changed files with 113329 additions and 53630 deletions

View file

@ -11,11 +11,13 @@
#include "machines.hh"
#include "shared.hh"
#include "plugin.hh"
#include "pathlocks.hh"
#include "globals.hh"
#include "serialise.hh"
#include "build-result.hh"
#include "store-api.hh"
#include "strings.hh"
#include "derivations.hh"
#include "local-store.hh"
#include "legacy.hh"
@ -37,7 +39,7 @@ static std::string currentLoad;
static AutoCloseFD openSlotLock(const Machine & m, uint64_t slot)
{
return openLockFile(fmt("%s/%s-%d", currentLoad, escapeUri(m.storeUri), slot), true);
return openLockFile(fmt("%s/%s-%d", currentLoad, escapeUri(m.storeUri.render()), slot), true);
}
static bool allSupportedLocally(Store & store, const std::set<std::string>& requiredFeatures) {
@ -72,6 +74,7 @@ static int main_build_remote(int argc, char * * argv)
settings.set(name, value);
}
auto maxBuildJobs = settings.maxBuildJobs;
settings.maxBuildJobs.set("1"); // hack to make tests with local?root= work
initPlugins();
@ -112,10 +115,14 @@ static int main_build_remote(int argc, char * * argv)
drvPath = store->parseStorePath(readString(source));
auto requiredFeatures = readStrings<std::set<std::string>>(source);
auto canBuildLocally = amWilling
/* It would be possible to build locally after some builds clear out,
so don't show the warning now: */
bool couldBuildLocally = maxBuildJobs > 0
&& ( neededSystem == settings.thisSystem
|| settings.extraPlatforms.get().count(neededSystem) > 0)
&& allSupportedLocally(*store, requiredFeatures);
/* It's possible to build this locally right now: */
bool canBuildLocally = amWilling && couldBuildLocally;
/* Error ignored here, will be caught later */
mkdir(currentLoad.c_str(), 0777);
@ -130,13 +137,10 @@ static int main_build_remote(int argc, char * * argv)
Machine * bestMachine = nullptr;
uint64_t bestLoad = 0;
for (auto & m : machines) {
debug("considering building on remote machine '%s'", m.storeUri);
debug("considering building on remote machine '%s'", m.storeUri.render());
if (m.enabled
&& (neededSystem == "builtin"
|| std::find(m.systemTypes.begin(),
m.systemTypes.end(),
neededSystem) != m.systemTypes.end()) &&
if (m.enabled &&
m.systemSupported(neededSystem) &&
m.allSupported(requiredFeatures) &&
m.mandatoryMet(requiredFeatures))
{
@ -186,12 +190,12 @@ static int main_build_remote(int argc, char * * argv)
// build the hint template.
std::string errorText =
"Failed to find a machine for remote build!\n"
"derivation: %s\nrequired (system, features): (%s, %s)";
"derivation: %s\nrequired (system, features): (%s, [%s])";
errorText += "\n%s available machines:";
errorText += "\n(systems, maxjobs, supportedFeatures, mandatoryFeatures)";
for (unsigned int i = 0; i < machines.size(); ++i)
errorText += "\n(%s, %s, %s, %s)";
errorText += "\n([%s], %s, [%s], [%s])";
// add the template values.
std::string drvstr;
@ -200,7 +204,7 @@ static int main_build_remote(int argc, char * * argv)
else
drvstr = "<unknown>";
auto error = hintformat(errorText);
auto error = HintFmt::fromFormatString(errorText);
error
% drvstr
% neededSystem
@ -209,12 +213,12 @@ static int main_build_remote(int argc, char * * argv)
for (auto & m : machines)
error
% concatStringsSep<std::vector<std::string>>(", ", m.systemTypes)
% concatStringsSep<StringSet>(", ", m.systemTypes)
% m.maxJobs
% concatStringsSep<StringSet>(", ", m.supportedFeatures)
% concatStringsSep<StringSet>(", ", m.mandatoryFeatures);
printMsg(canBuildLocally ? lvlChatty : lvlWarn, error);
printMsg(couldBuildLocally ? lvlChatty : lvlWarn, error.str());
std::cerr << "# decline\n";
}
@ -230,17 +234,16 @@ static int main_build_remote(int argc, char * * argv)
lock = -1;
try {
storeUri = bestMachine->storeUri.render();
Activity act(*logger, lvlTalkative, actUnknown, fmt("connecting to '%s'", bestMachine->storeUri));
Activity act(*logger, lvlTalkative, actUnknown, fmt("connecting to '%s'", storeUri));
sshStore = bestMachine->openStore();
sshStore->connect();
storeUri = bestMachine->storeUri;
} catch (std::exception & e) {
auto msg = chomp(drainFD(5, false));
printError("cannot build on '%s': %s%s",
bestMachine->storeUri, e.what(),
storeUri, e.what(),
msg.empty() ? "" : ": " + msg);
bestMachine->enabled = false;
continue;
@ -253,12 +256,27 @@ static int main_build_remote(int argc, char * * argv)
connected:
close(5);
assert(sshStore);
std::cerr << "# accept\n" << storeUri << "\n";
auto inputs = readStrings<PathSet>(source);
auto wantedOutputs = readStrings<StringSet>(source);
AutoCloseFD uploadLock = openLockFile(currentLoad + "/" + escapeUri(storeUri) + ".upload-lock", true);
AutoCloseFD uploadLock;
{
auto setUpdateLock = [&](auto && fileName){
uploadLock = openLockFile(currentLoad + "/" + escapeUri(fileName) + ".upload-lock", true);
};
try {
setUpdateLock(storeUri);
} catch (SysError & e) {
if (e.errNo != ENAMETOOLONG) throw;
// Try again hashing the store URL so we have a shorter path
auto h = hashString(HashAlgorithm::MD5, storeUri);
setUpdateLock(h.to_string(HashFormat::Base64, false));
}
}
{
Activity act(*logger, lvlTalkative, actUnknown, fmt("waiting for the upload lock to '%s'", storeUri));
@ -281,33 +299,66 @@ connected:
uploadLock = -1;
auto drv = store->readDerivation(*drvPath);
std::optional<BuildResult> optResult;
// If we don't know whether we are trusted (e.g. `ssh://`
// stores), we assume we are. This is necessary for backwards
// compat.
bool trustedOrLegacy = ({
std::optional trusted = sshStore->isTrustedClient();
!trusted || *trusted;
});
// See the very large comment in `case WorkerProto::Op::BuildDerivation:` in
// `src/libstore/daemon.cc` that explains the trust model here.
//
// This condition mirrors that: that code enforces the "rules" outlined there;
// we do the best we can given those "rules".
if (trustedOrLegacy || drv.type().isCA()) {
// Hijack the inputs paths of the derivation to include all
// the paths that come from the `inputDrvs` set. We dont do
// that for the derivations whose `inputDrvs` is empty
// because:
//
// 1. Its not needed
//
// 2. Changing the `inputSrcs` set changes the associated
// output ids, which break CA derivations
if (!drv.inputDrvs.map.empty())
drv.inputSrcs = store->parseStorePathSet(inputs);
optResult = sshStore->buildDerivation(*drvPath, (const BasicDerivation &) drv);
auto & result = *optResult;
if (!result.success())
throw Error("build of '%s' on '%s' failed: %s", store->printStorePath(*drvPath), storeUri, result.errorMsg);
} else {
copyClosure(*store, *sshStore, StorePathSet {*drvPath}, NoRepair, NoCheckSigs, substitute);
auto res = sshStore->buildPathsWithResults({
DerivedPath::Built {
.drvPath = makeConstantStorePathRef(*drvPath),
.outputs = OutputsSpec::All {},
}
});
// One path to build should produce exactly one build result
assert(res.size() == 1);
optResult = std::move(res[0]);
}
auto outputHashes = staticOutputHashes(*store, drv);
// Hijack the inputs paths of the derivation to include all the paths
// that come from the `inputDrvs` set.
// We dont do that for the derivations whose `inputDrvs` is empty
// because
// 1. Its not needed
// 2. Changing the `inputSrcs` set changes the associated output ids,
// which break CA derivations
if (!drv.inputDrvs.empty())
drv.inputSrcs = store->parseStorePathSet(inputs);
auto result = sshStore->buildDerivation(*drvPath, drv);
if (!result.success())
throw Error("build of '%s' on '%s' failed: %s", store->printStorePath(*drvPath), storeUri, result.errorMsg);
std::set<Realisation> missingRealisations;
StorePathSet missingPaths;
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) && !drv.type().hasKnownOutputPaths()) {
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations) && !drv.type().hasKnownOutputPaths()) {
for (auto & outputName : wantedOutputs) {
auto thisOutputHash = outputHashes.at(outputName);
auto thisOutputId = DrvOutput{ thisOutputHash, outputName };
if (!store->queryRealisation(thisOutputId)) {
debug("missing output %s", outputName);
assert(result.builtOutputs.count(thisOutputId));
auto newRealisation = result.builtOutputs.at(thisOutputId);
assert(optResult);
auto & result = *optResult;
auto i = result.builtOutputs.find(outputName);
assert(i != result.builtOutputs.end());
auto & newRealisation = i->second;
missingRealisations.insert(newRealisation);
missingPaths.insert(newRealisation.outPath);
}
@ -332,7 +383,7 @@ connected:
for (auto & realisation : missingRealisations) {
// Should hold, because if the feature isn't enabled the set
// of missing realisations should be empty
settings.requireExperimentalFeature(Xp::CaDerivations);
experimentalFeatureSettings.require(Xp::CaDerivations);
store->registerDrvOutput(realisation);
}

3
src/external-api-docs/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/doxygen.cfg
/html
/latex

View file

@ -0,0 +1 @@
../../.version

View file

@ -0,0 +1,121 @@
# Getting started
> **Warning** These bindings are **experimental**, which means they can change
> at any time or be removed outright; nevertheless the plan is to provide a
> stable external C API to the Nix language and the Nix store.
The language library allows evaluating Nix expressions and interacting with Nix
language values. The Nix store API is still rudimentary, and only allows
initialising and connecting to a store for the Nix language evaluator to
interact with.
Currently there are two ways to interface with the Nix language evaluator
programmatically:
1. Embedding the evaluator
2. Writing language plug-ins
Embedding means you link the Nix C libraries in your program and use them from
there. Adding a plug-in means you make a library that gets loaded by the Nix
language evaluator, specified through a configuration option.
Many of the components and mechanisms involved are not yet documented, therefore
please refer to the [Nix source code](https://github.com/NixOS/nix/) for
details. Additions to in-code documentation and the reference manual are highly
appreciated.
The following examples, for simplicity, don't include error handling. See the
[Handling errors](@ref errors) section for more information.
# Embedding the Nix Evaluator{#nix_evaluator_example}
In this example we programmatically start the Nix language evaluator with a
dummy store (that has no store paths and cannot be written to), and evaluate the
Nix expression `builtins.nixVersion`.
**main.c:**
```C
#include <nix_api_util.h>
#include <nix_api_expr.h>
#include <nix_api_value.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// NOTE: This example lacks all error handling. Production code must check for
// errors, as some return values will be undefined.
void my_get_string_cb(const char * start, unsigned int n, void * user_data)
{
*((char **) user_data) = strdup(start);
}
int main()
{
nix_libexpr_init(NULL);
Store * store = nix_store_open(NULL, "dummy://", NULL);
EvalState * state = nix_state_create(NULL, NULL, store); // empty search path (NIX_PATH)
Value * value = nix_alloc_value(NULL, state);
nix_expr_eval_from_string(NULL, state, "builtins.nixVersion", ".", value);
nix_value_force(NULL, state, value);
char * version;
nix_get_string(NULL, value, my_get_string_cb, &version);
printf("Nix version: %s\n", version);
free(version);
nix_gc_decref(NULL, value);
nix_state_free(state);
nix_store_free(store);
return 0;
}
```
**Usage:**
```ShellSession
$ gcc main.c $(pkg-config nix-expr-c --libs --cflags) -o main
$ ./main
Nix version: 2.17
```
# Writing a Nix language plug-in
In this example we add a custom primitive operation (_primop_) to `builtins`. It
will increment the argument if it is an integer and throw an error otherwise.
**plugin.c:**
```C
#include <nix_api_util.h>
#include <nix_api_expr.h>
#include <nix_api_value.h>
void increment(void* user_data, nix_c_context* ctx, EvalState* state, Value** args, Value* v) {
nix_value_force(NULL, state, args[0]);
if (nix_get_type(NULL, args[0]) == NIX_TYPE_INT) {
nix_init_int(NULL, v, nix_get_int(NULL, args[0]) + 1);
} else {
nix_set_err_msg(ctx, NIX_ERR_UNKNOWN, "First argument should be an integer.");
}
}
void nix_plugin_entry() {
const char* args[] = {"n", NULL};
PrimOp *p = nix_alloc_primop(NULL, increment, 1, "increment", args, "Example custom built-in function: increments an integer", NULL);
nix_register_primop(NULL, p);
nix_gc_decref(NULL, p);
}
```
**Usage:**
```ShellSession
$ gcc plugin.c $(pkg-config nix-expr-c --libs --cflags) -shared -o plugin.so
$ nix --plugin-files ./plugin.so repl
nix-repl> builtins.increment 1
2
```

View file

@ -0,0 +1,60 @@
# Doxyfile 1.9.5
# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
# double-quotes, unless you are using Doxywizard) that should identify the
# project for which the documentation is generated. This name is used in the
# title of most generated pages and in a few other places.
# The default value is: My Project.
PROJECT_NAME = "Nix"
# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = @PROJECT_NUMBER@
OUTPUT_DIRECTORY = @OUTPUT_DIRECTORY@
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
# quick idea about the purpose of the project. Keep the description short.
PROJECT_BRIEF = "Nix, the purely functional package manager: C API (experimental)"
# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
# The default value is: YES.
GENERATE_LATEX = NO
# The INPUT tag is used to specify the files and/or directories that contain
# documented source files. You may enter file names like myfile.cpp or
# directories like /usr/src/myproject. Separate the files or directories with
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
# FIXME Make this list more maintainable somehow. We could maybe generate this
# in the Makefile, but we would need to change how `.in` files are preprocessed
# so they can expand variables despite configure variables.
INPUT = \
@src@/src/libutil-c \
@src@/src/libexpr-c \
@src@/src/libstore-c \
@src@/doc/external-api/README.md
FILE_PATTERNS = nix_api_*.h *.md
# The INCLUDE_PATH tag can be used to specify one or more directories that
# contain include files that are not input files but should be processed by the
# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of
# RECURSIVE has no effect here.
# This tag requires that the tag SEARCH_INCLUDES is set to YES.
EXCLUDE_PATTERNS = *_internal.h
GENERATE_TREEVIEW = YES
OPTIMIZE_OUTPUT_FOR_C = YES
USE_MDFILE_AS_MAINPAGE = doc/external-api/README.md
QUIET = YES

View file

@ -0,0 +1,31 @@
project('nix-external-api-docs',
version : files('.version'),
meson_version : '>= 1.1',
license : 'LGPL-2.1-or-later',
)
fs = import('fs')
doxygen_cfg = configure_file(
input : 'doxygen.cfg.in',
output : 'doxygen.cfg',
configuration : {
'PROJECT_NUMBER': meson.project_version(),
'OUTPUT_DIRECTORY' : meson.current_build_dir(),
'src' : fs.parent(fs.parent(meson.project_source_root())),
},
)
doxygen = find_program('doxygen', native : true, required : true)
custom_target(
'external-api-docs',
command : [ doxygen , doxygen_cfg ],
input : [
doxygen_cfg,
],
output : 'html',
install : true,
install_dir : get_option('datadir') / 'doc/nix/external-api',
build_always_stale : true,
)

View file

@ -0,0 +1,55 @@
{ lib
, mkMesonDerivation
, doxygen
# Configuration Options
, version
}:
let
inherit (lib) fileset;
in
mkMesonDerivation (finalAttrs: {
pname = "nix-external-api-docs";
inherit version;
workDir = ./.;
fileset =
let
cpp = fileset.fileFilter (file: file.hasExt "cc" || file.hasExt "h");
in
fileset.unions [
./.version
../../.version
./meson.build
./doxygen.cfg.in
./README.md
# Source is not compiled, but still must be available for Doxygen
# to gather comments.
(cpp ../libexpr-c)
(cpp ../libstore-c)
(cpp ../libutil-c)
];
nativeBuildInputs = [
doxygen
];
preConfigure =
''
chmod u+w ./.version
echo ${finalAttrs.version} > ./.version
'';
postInstall = ''
mkdir -p ''${!outputDoc}/nix-support
echo "doc external-api-docs $out/share/doc/nix/external-api/html" >> ''${!outputDoc}/nix-support/hydra-build-products
'';
meta = {
platforms = lib.platforms.all;
};
})

3
src/internal-api-docs/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/doxygen.cfg
/html
/latex

View file

@ -0,0 +1 @@
../../.version

View file

@ -0,0 +1,102 @@
# Doxyfile 1.9.5
# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
# double-quotes, unless you are using Doxywizard) that should identify the
# project for which the documentation is generated. This name is used in the
# title of most generated pages and in a few other places.
# The default value is: My Project.
PROJECT_NAME = "Nix"
# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = @PROJECT_NUMBER@
OUTPUT_DIRECTORY = @OUTPUT_DIRECTORY@
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
# quick idea about the purpose of the project. Keep the description short.
PROJECT_BRIEF = "Nix, the purely functional package manager; unstable internal interfaces"
# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
# The default value is: YES.
GENERATE_LATEX = NO
# The INPUT tag is used to specify the files and/or directories that contain
# documented source files. You may enter file names like myfile.cpp or
# directories like /usr/src/myproject. Separate the files or directories with
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
# FIXME Make this list more maintainable somehow. We could maybe generate this
# in the Makefile, but we would need to change how `.in` files are preprocessed
# so they can expand variables despite configure variables.
INPUT = \
@src@/libcmd \
@src@/libexpr \
@src@/libexpr/flake \
@src@/libexpr-tests \
@src@/libexpr-tests/value \
@src@/libexpr-test-support/test \
@src@/libexpr-test-support/test/value \
@src@/libexpr/value \
@src@/libfetchers \
@src@/libmain \
@src@/libstore \
@src@/libstore/build \
@src@/libstore/builtins \
@src@/libstore-tests \
@src@/libstore-test-support/test \
@src@/libutil \
@src@/libutil-tests \
@src@/libutil-test-support/test \
@src@/nix \
@src@/nix-env \
@src@/nix-store
# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
# in the source code. If set to NO, only conditional compilation will be
# performed. Macro expansion can be done in a controlled way by setting
# EXPAND_ONLY_PREDEF to YES.
# The default value is: NO.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
MACRO_EXPANSION = YES
# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
# the macro expansion is limited to the macros specified with the PREDEFINED and
# EXPAND_AS_DEFINED tags.
# The default value is: NO.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
EXPAND_ONLY_PREDEF = YES
# The INCLUDE_PATH tag can be used to specify one or more directories that
# contain include files that are not input files but should be processed by the
# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of
# RECURSIVE has no effect here.
# This tag requires that the tag SEARCH_INCLUDES is set to YES.
INCLUDE_PATH =
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
# tag can be used to specify a list of macro names that should be expanded. The
# macro definition that is found in the sources will be used. Use the PREDEFINED
# tag if you want to use a different macro definition that overrules the
# definition found in the source code.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
EXPAND_AS_DEFINED = \
DECLARE_COMMON_SERIALISER \
DECLARE_WORKER_SERIALISER \
DECLARE_SERVE_SERIALISER \
LENGTH_PREFIXED_PROTO_HELPER
WARN_IF_UNDOCUMENTED = NO
QUIET = YES

View file

@ -0,0 +1,31 @@
project('nix-internal-api-docs',
version : files('.version'),
meson_version : '>= 1.1',
license : 'LGPL-2.1-or-later',
)
fs = import('fs')
doxygen_cfg = configure_file(
input : 'doxygen.cfg.in',
output : 'doxygen.cfg',
configuration : {
'PROJECT_NUMBER': meson.project_version(),
'OUTPUT_DIRECTORY' : meson.current_build_dir(),
'src' : fs.parent(fs.parent(meson.project_source_root())) / 'src',
},
)
doxygen = find_program('doxygen', native : true, required : true)
custom_target(
'internal-api-docs',
command : [ doxygen , doxygen_cfg ],
input : [
doxygen_cfg,
],
output : 'html',
install : true,
install_dir : get_option('datadir') / 'doc/nix/internal-api',
build_always_stale : true,
)

View file

@ -0,0 +1,50 @@
{ lib
, mkMesonDerivation
, doxygen
# Configuration Options
, version
}:
let
inherit (lib) fileset;
in
mkMesonDerivation (finalAttrs: {
pname = "nix-internal-api-docs";
inherit version;
workDir = ./.;
fileset = let
cpp = fileset.fileFilter (file: file.hasExt "cc" || file.hasExt "hh");
in fileset.unions [
./.version
../../.version
./meson.build
./doxygen.cfg.in
# Source is not compiled, but still must be available for Doxygen
# to gather comments.
(cpp ../.)
];
nativeBuildInputs = [
doxygen
];
preConfigure =
''
chmod u+w ./.version
echo ${finalAttrs.version} > ./.version
'';
postInstall = ''
mkdir -p ''${!outputDoc}/nix-support
echo "doc internal-api-docs $out/share/doc/nix/internal-api/html" >> ''${!outputDoc}/nix-support/hydra-build-products
'';
meta = {
platforms = lib.platforms.all;
};
})

1
src/libcmd/.version Symbolic link
View file

@ -0,0 +1 @@
../../.version

View file

@ -0,0 +1 @@
../../build-utils-meson

144
src/libcmd/built-path.cc Normal file
View file

@ -0,0 +1,144 @@
#include "built-path.hh"
#include "derivations.hh"
#include "store-api.hh"
#include "comparator.hh"
#include <nlohmann/json.hpp>
#include <optional>
namespace nix {
// Custom implementation to avoid `ref` ptr equality
GENERATE_CMP_EXT(
,
std::strong_ordering,
SingleBuiltPathBuilt,
*me->drvPath,
me->output);
// Custom implementation to avoid `ref` ptr equality
// TODO no `GENERATE_CMP_EXT` because no `std::set::operator<=>` on
// Darwin, per header.
GENERATE_EQUAL(
,
BuiltPathBuilt ::,
BuiltPathBuilt,
*me->drvPath,
me->outputs);
StorePath SingleBuiltPath::outPath() const
{
return std::visit(
overloaded{
[](const SingleBuiltPath::Opaque & p) { return p.path; },
[](const SingleBuiltPath::Built & b) { return b.output.second; },
}, raw()
);
}
StorePathSet BuiltPath::outPaths() const
{
return std::visit(
overloaded{
[](const BuiltPath::Opaque & p) { return StorePathSet{p.path}; },
[](const BuiltPath::Built & b) {
StorePathSet res;
for (auto & [_, path] : b.outputs)
res.insert(path);
return res;
},
}, raw()
);
}
SingleDerivedPath::Built SingleBuiltPath::Built::discardOutputPath() const
{
return SingleDerivedPath::Built {
.drvPath = make_ref<SingleDerivedPath>(drvPath->discardOutputPath()),
.output = output.first,
};
}
SingleDerivedPath SingleBuiltPath::discardOutputPath() const
{
return std::visit(
overloaded{
[](const SingleBuiltPath::Opaque & p) -> SingleDerivedPath {
return p;
},
[](const SingleBuiltPath::Built & b) -> SingleDerivedPath {
return b.discardOutputPath();
},
}, raw()
);
}
nlohmann::json BuiltPath::Built::toJSON(const StoreDirConfig & store) const
{
nlohmann::json res;
res["drvPath"] = drvPath->toJSON(store);
for (const auto & [outputName, outputPath] : outputs) {
res["outputs"][outputName] = store.printStorePath(outputPath);
}
return res;
}
nlohmann::json SingleBuiltPath::Built::toJSON(const StoreDirConfig & store) const
{
nlohmann::json res;
res["drvPath"] = drvPath->toJSON(store);
auto & [outputName, outputPath] = output;
res["output"] = outputName;
res["outputPath"] = store.printStorePath(outputPath);
return res;
}
nlohmann::json SingleBuiltPath::toJSON(const StoreDirConfig & store) const
{
return std::visit([&](const auto & buildable) {
return buildable.toJSON(store);
}, raw());
}
nlohmann::json BuiltPath::toJSON(const StoreDirConfig & store) const
{
return std::visit([&](const auto & buildable) {
return buildable.toJSON(store);
}, raw());
}
RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const
{
RealisedPath::Set res;
std::visit(
overloaded{
[&](const BuiltPath::Opaque & p) { res.insert(p.path); },
[&](const BuiltPath::Built & p) {
auto drvHashes =
staticOutputHashes(store, store.readDerivation(p.drvPath->outPath()));
for (auto& [outputName, outputPath] : p.outputs) {
if (experimentalFeatureSettings.isEnabled(
Xp::CaDerivations)) {
auto drvOutput = get(drvHashes, outputName);
if (!drvOutput)
throw Error(
"the derivation '%s' has unrealised output '%s' (derived-path.cc/toRealisedPaths)",
store.printStorePath(p.drvPath->outPath()), outputName);
auto thisRealisation = store.queryRealisation(
DrvOutput{*drvOutput, outputName});
assert(thisRealisation); // Weve built it, so we must
// have the realisation
res.insert(*thisRealisation);
} else {
res.insert(outputPath);
}
}
},
},
raw());
return res;
}
}

107
src/libcmd/built-path.hh Normal file
View file

@ -0,0 +1,107 @@
#pragma once
///@file
#include "derived-path.hh"
#include "realisation.hh"
namespace nix {
struct SingleBuiltPath;
struct SingleBuiltPathBuilt {
ref<SingleBuiltPath> drvPath;
std::pair<std::string, StorePath> output;
SingleDerivedPathBuilt discardOutputPath() const;
std::string to_string(const StoreDirConfig & store) const;
static SingleBuiltPathBuilt parse(const StoreDirConfig & store, std::string_view, std::string_view);
nlohmann::json toJSON(const StoreDirConfig & store) const;
bool operator ==(const SingleBuiltPathBuilt &) const noexcept;
std::strong_ordering operator <=>(const SingleBuiltPathBuilt &) const noexcept;
};
using _SingleBuiltPathRaw = std::variant<
DerivedPathOpaque,
SingleBuiltPathBuilt
>;
struct SingleBuiltPath : _SingleBuiltPathRaw {
using Raw = _SingleBuiltPathRaw;
using Raw::Raw;
using Opaque = DerivedPathOpaque;
using Built = SingleBuiltPathBuilt;
bool operator == (const SingleBuiltPath &) const = default;
auto operator <=> (const SingleBuiltPath &) const = default;
inline const Raw & raw() const {
return static_cast<const Raw &>(*this);
}
StorePath outPath() const;
SingleDerivedPath discardOutputPath() const;
static SingleBuiltPath parse(const StoreDirConfig & store, std::string_view);
nlohmann::json toJSON(const StoreDirConfig & store) const;
};
static inline ref<SingleBuiltPath> staticDrv(StorePath drvPath)
{
return make_ref<SingleBuiltPath>(SingleBuiltPath::Opaque { drvPath });
}
/**
* A built derived path with hints in the form of optional concrete output paths.
*
* See 'BuiltPath' for more an explanation.
*/
struct BuiltPathBuilt {
ref<SingleBuiltPath> drvPath;
std::map<std::string, StorePath> outputs;
bool operator == (const BuiltPathBuilt &) const noexcept;
// TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet.
//std::strong_ordering operator <=> (const BuiltPathBuilt &) const noexcept;
std::string to_string(const StoreDirConfig & store) const;
static BuiltPathBuilt parse(const StoreDirConfig & store, std::string_view, std::string_view);
nlohmann::json toJSON(const StoreDirConfig & store) const;
};
using _BuiltPathRaw = std::variant<
DerivedPath::Opaque,
BuiltPathBuilt
>;
/**
* A built path. Similar to a DerivedPath, but enriched with the corresponding
* output path(s).
*/
struct BuiltPath : _BuiltPathRaw {
using Raw = _BuiltPathRaw;
using Raw::Raw;
using Opaque = DerivedPathOpaque;
using Built = BuiltPathBuilt;
bool operator == (const BuiltPath &) const = default;
// TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet.
//auto operator <=> (const BuiltPath &) const = default;
inline const Raw & raw() const {
return static_cast<const Raw &>(*this);
}
StorePathSet outPaths() const;
RealisedPath::Set toRealisedPaths(Store & store) const;
nlohmann::json toJSON(const StoreDirConfig & store) const;
};
typedef std::vector<BuiltPath> BuiltPaths;
}

View file

@ -0,0 +1,11 @@
#include "command-installable-value.hh"
namespace nix {
void InstallableValueCommand::run(ref<Store> store, ref<Installable> installable)
{
auto installableValue = InstallableValue::require(installable);
run(store, installableValue);
}
}

View file

@ -0,0 +1,23 @@
#pragma once
///@file
#include "installable-value.hh"
#include "command.hh"
namespace nix {
/**
* An InstallableCommand where the single positional argument must be an
* InstallableValue in particular.
*/
struct InstallableValueCommand : InstallableCommand
{
/**
* Entry point to this command
*/
virtual void run(ref<Store> store, ref<InstallableValue> installable) = 0;
void run(ref<Store> store, ref<Installable> installable) override;
};
}

View file

@ -1,13 +1,16 @@
#include <algorithm>
#include <nlohmann/json.hpp>
#include "command.hh"
#include "markdown.hh"
#include "store-api.hh"
#include "local-fs-store.hh"
#include "derivations.hh"
#include "nixexpr.hh"
#include "profiles.hh"
#include <nlohmann/json.hpp>
extern char * * environ __attribute__((weak));
#include "repl.hh"
#include "strings.hh"
#include "environment-variables.hh"
namespace nix {
@ -20,7 +23,8 @@ nix::Commands RegisterCommand::getCommandsFor(const std::vector<std::string> & p
if (name.size() == prefix.size() + 1) {
bool equal = true;
for (size_t i = 0; i < prefix.size(); ++i)
if (name[i] != prefix[i]) equal = false;
if (name[i] != prefix[i])
equal = false;
if (equal)
res.insert_or_assign(name[prefix.size()], command);
}
@ -33,10 +37,23 @@ nlohmann::json NixMultiCommand::toJSON()
return MultiCommand::toJSON();
}
StoreCommand::StoreCommand()
void NixMultiCommand::run()
{
if (!command) {
std::set<std::string> subCommandTextLines;
for (auto & [name, _] : commands)
subCommandTextLines.insert(fmt("- `%s`", name));
std::string markdownError =
fmt("`nix %s` requires a sub-command. Available sub-commands:\n\n%s\n",
commandName,
concatStringsSep("\n", subCommandTextLines));
throw UsageError(renderMarkdownToTerminal(markdownError));
}
command->second->run();
}
StoreCommand::StoreCommand() {}
ref<Store> StoreCommand::getStore()
{
if (!_store)
@ -88,7 +105,8 @@ EvalCommand::EvalCommand()
{
addFlag({
.longName = "debugger",
.description = "start an interactive environment if evaluation fails",
.description = "Start an interactive environment if evaluation fails.",
.category = MixEvalArgs::category,
.handler = {&startReplOnEvalErrors, true},
});
}
@ -96,7 +114,7 @@ EvalCommand::EvalCommand()
EvalCommand::~EvalCommand()
{
if (evalState)
evalState->printStats();
evalState->maybePrintStats();
}
ref<Store> EvalCommand::getEvalStore()
@ -109,23 +127,29 @@ ref<Store> EvalCommand::getEvalStore()
ref<EvalState> EvalCommand::getEvalState()
{
if (!evalState) {
evalState =
#if HAVE_BOEHMGC
std::allocate_shared<EvalState>(traceable_allocator<EvalState>(),
searchPath, getEvalStore(), getStore())
#else
std::make_shared<EvalState>(
searchPath, getEvalStore(), getStore())
#endif
;
evalState = std::allocate_shared<EvalState>(
traceable_allocator<EvalState>(), lookupPath, getEvalStore(), fetchSettings, evalSettings, getStore());
evalState->repair = repair;
if (startReplOnEvalErrors) {
evalState->debugRepl = &runRepl;
evalState->debugRepl = &AbstractNixRepl::runSimple;
};
}
return ref<EvalState>(evalState);
}
MixOperateOnOptions::MixOperateOnOptions()
{
addFlag({
.longName = "derivation",
.description =
"Operate on the [store derivation](@docroot@/glossary.md#gloss-store-derivation) rather than its outputs.",
.category = installablesCategory,
.handler = {&operateOn, OperateOn::Derivation},
});
}
BuiltPathsCommand::BuiltPathsCommand(bool recursive)
: recursive(recursive)
{
@ -153,7 +177,7 @@ BuiltPathsCommand::BuiltPathsCommand(bool recursive)
});
}
void BuiltPathsCommand::run(ref<Store> store)
void BuiltPathsCommand::run(ref<Store> store, Installables && installables)
{
BuiltPaths paths;
if (all) {
@ -161,7 +185,7 @@ void BuiltPathsCommand::run(ref<Store> store)
throw UsageError("'--all' does not expect arguments");
// XXX: Only uses opaque paths, ignores all the realisations
for (auto & p : store->queryAllValidPaths())
paths.push_back(BuiltPath::Opaque{p});
paths.emplace_back(BuiltPath::Opaque{p});
} else {
paths = Installable::toBuiltPaths(getEvalStore(), store, realiseMode, operateOn, installables);
if (recursive) {
@ -174,7 +198,7 @@ void BuiltPathsCommand::run(ref<Store> store)
}
store->computeFSClosure(pathsRoots, pathsClosure);
for (auto & path : pathsClosure)
paths.push_back(BuiltPath::Opaque{path});
paths.emplace_back(BuiltPath::Opaque{path});
}
}
@ -199,7 +223,7 @@ void StorePathsCommand::run(ref<Store> store, BuiltPaths && paths)
run(store, std::move(sorted));
}
void StorePathCommand::run(ref<Store> store, std::vector<StorePath> && storePaths)
void StorePathCommand::run(ref<Store> store, StorePaths && storePaths)
{
if (storePaths.size() != 1)
throw UsageError("this command requires exactly one store path");
@ -207,64 +231,50 @@ void StorePathCommand::run(ref<Store> store, std::vector<StorePath> && storePath
run(store, *storePaths.begin());
}
Strings editorFor(const Path & file, uint32_t line)
{
auto editor = getEnv("EDITOR").value_or("cat");
auto args = tokenizeString<Strings>(editor);
if (line > 0 && (
editor.find("emacs") != std::string::npos ||
editor.find("nano") != std::string::npos ||
editor.find("vim") != std::string::npos ||
editor.find("kak") != std::string::npos))
args.push_back(fmt("+%d", line));
args.push_back(file);
return args;
}
MixProfile::MixProfile()
{
addFlag({
.longName = "profile",
.description = "The profile to update.",
.labels = {"path"},
.handler = {&profile},
.completer = completePath
});
addFlag(
{.longName = "profile",
.description = "The profile to operate on.",
.labels = {"path"},
.handler = {&profile},
.completer = completePath});
}
void MixProfile::updateProfile(const StorePath & storePath)
{
if (!profile) return;
if (!profile)
return;
auto store = getStore().dynamic_pointer_cast<LocalFSStore>();
if (!store) throw Error("'--profile' is not supported for this Nix store");
if (!store)
throw Error("'--profile' is not supported for this Nix store");
auto profile2 = absPath(*profile);
switchLink(profile2,
createGeneration(
ref<LocalFSStore>(store),
profile2, storePath));
switchLink(profile2, createGeneration(*store, profile2, storePath));
}
void MixProfile::updateProfile(const BuiltPaths & buildables)
{
if (!profile) return;
if (!profile)
return;
std::vector<StorePath> result;
StorePaths result;
for (auto & buildable : buildables) {
std::visit(overloaded {
[&](const BuiltPath::Opaque & bo) {
result.push_back(bo.path);
std::visit(
overloaded{
[&](const BuiltPath::Opaque & bo) { result.push_back(bo.path); },
[&](const BuiltPath::Built & bfd) {
for (auto & output : bfd.outputs) {
result.push_back(output.second);
}
},
},
[&](const BuiltPath::Built & bfd) {
for (auto & output : bfd.outputs) {
result.push_back(output.second);
}
},
}, buildable.raw());
buildable.raw());
}
if (result.size() != 1)
throw UsageError("'--profile' requires that the arguments produce a single store path, but there are %d", result.size());
throw UsageError(
"'--profile' requires that the arguments produce a single store path, but there are %d", result.size());
updateProfile(result[0]);
}
@ -274,51 +284,85 @@ MixDefaultProfile::MixDefaultProfile()
profile = getDefaultProfile();
}
MixEnvironment::MixEnvironment() : ignoreEnvironment(false)
MixEnvironment::MixEnvironment()
: ignoreEnvironment(false)
{
addFlag({
.longName = "ignore-environment",
.longName = "ignore-env",
.aliases = {"ignore-environment"},
.shortName = 'i',
.description = "Clear the entire environment (except those specified with `--keep`).",
.description = "Clear the entire environment, except for those specified with `--keep-env-var`.",
.category = environmentVariablesCategory,
.handler = {&ignoreEnvironment, true},
});
addFlag({
.longName = "keep",
.longName = "keep-env-var",
.aliases = {"keep"},
.shortName = 'k',
.description = "Keep the environment variable *name*.",
.description = "Keep the environment variable *name*, when using `--ignore-env`.",
.category = environmentVariablesCategory,
.labels = {"name"},
.handler = {[&](std::string s) { keep.insert(s); }},
.handler = {[&](std::string s) { keepVars.insert(s); }},
});
addFlag({
.longName = "unset",
.longName = "unset-env-var",
.aliases = {"unset"},
.shortName = 'u',
.description = "Unset the environment variable *name*.",
.category = environmentVariablesCategory,
.labels = {"name"},
.handler = {[&](std::string s) { unset.insert(s); }},
.handler = {[&](std::string name) {
if (setVars.contains(name))
throw UsageError("Cannot unset environment variable '%s' that is set with '%s'", name, "--set-env-var");
unsetVars.insert(name);
}},
});
addFlag({
.longName = "set-env-var",
.shortName = 's',
.description = "Sets an environment variable *name* with *value*.",
.category = environmentVariablesCategory,
.labels = {"name", "value"},
.handler = {[&](std::string name, std::string value) {
if (unsetVars.contains(name))
throw UsageError(
"Cannot set environment variable '%s' that is unset with '%s'", name, "--unset-env-var");
if (setVars.contains(name))
throw UsageError(
"Duplicate definition of environment variable '%s' with '%s' is ambiguous", name, "--set-env-var");
setVars.insert_or_assign(name, value);
}},
});
}
void MixEnvironment::setEnviron() {
if (ignoreEnvironment) {
if (!unset.empty())
throw UsageError("--unset does not make sense with --ignore-environment");
void MixEnvironment::setEnviron()
{
if (ignoreEnvironment && !unsetVars.empty())
throw UsageError("--unset-env-var does not make sense with --ignore-env");
for (const auto & var : keep) {
auto val = getenv(var.c_str());
if (val) stringsEnv.emplace_back(fmt("%s=%s", var.c_str(), val));
}
if (!ignoreEnvironment && !keepVars.empty())
throw UsageError("--keep-env-var does not make sense without --ignore-env");
vectorEnv = stringsToCharPtrs(stringsEnv);
environ = vectorEnv.data();
} else {
if (!keep.empty())
throw UsageError("--keep does not make sense without --ignore-environment");
auto env = getEnv();
for (const auto & var : unset)
unsetenv(var.c_str());
}
if (ignoreEnvironment)
std::erase_if(env, [&](const auto & var) { return !keepVars.contains(var.first); });
for (const auto & [name, value] : setVars)
env[name] = value;
if (!unsetVars.empty())
std::erase_if(env, [&](const auto & var) { return unsetVars.contains(var.first); });
replaceEnv(env);
return;
}
}

View file

@ -1,6 +1,7 @@
#pragma once
///@file
#include "installables.hh"
#include "installable-value.hh"
#include "args.hh"
#include "common-eval-args.hh"
#include "path.hh"
@ -12,38 +13,54 @@ namespace nix {
extern std::string programPath;
extern char * * savedArgv;
extern char ** savedArgv;
class EvalState;
struct Pos;
class Store;
static constexpr Command::Category catHelp = -1;
static constexpr Command::Category catSecondary = 100;
static constexpr Command::Category catUtility = 101;
static constexpr Command::Category catNixInstallation = 102;
static constexpr auto installablesCategory = "Options that change the interpretation of installables";
static constexpr auto installablesCategory =
"Options that change the interpretation of [installables](@docroot@/command-ref/new-cli/nix.md#installables)";
struct NixMultiCommand : virtual MultiCommand, virtual Command
struct NixMultiCommand : MultiCommand, virtual Command
{
nlohmann::json toJSON() override;
using MultiCommand::MultiCommand;
virtual void run() override;
};
/* A command that requires a Nix store. */
// For the overloaded run methods
#pragma GCC diagnostic ignored "-Woverloaded-virtual"
/**
* A command that requires a \ref Store "Nix store".
*/
struct StoreCommand : virtual Command
{
StoreCommand();
void run() override;
ref<Store> getStore();
virtual ref<Store> createStore();
/**
* Main entry point, with a `Store` provided
*/
virtual void run(ref<Store>) = 0;
private:
std::shared_ptr<Store> _store;
};
/* A command that copies something between `--from` and `--to`
stores. */
/**
* A command that copies something between `--from` and `--to` \ref
* Store stores.
*/
struct CopyCommand : virtual StoreCommand
{
std::string srcUri, dstUri;
@ -55,6 +72,9 @@ struct CopyCommand : virtual StoreCommand
ref<Store> getDstStore();
};
/**
* A command that needs to evaluate Nix language expressions.
*/
struct EvalCommand : virtual StoreCommand, MixEvalArgs
{
bool startReplOnEvalErrors = false;
@ -74,87 +94,137 @@ private:
std::shared_ptr<EvalState> evalState;
};
/**
* A mixin class for commands that process flakes, adding a few standard
* flake-related options/flags.
*/
struct MixFlakeOptions : virtual Args, EvalCommand
{
flake::LockFlags lockFlags;
std::optional<std::string> needsFlakeInputCompletion = {};
MixFlakeOptions();
virtual std::vector<std::string> getFlakesForCompletion()
{ return {}; }
void completeFlakeInput(std::string_view prefix);
void completionHook() override;
/**
* The completion for some of these flags depends on the flake(s) in
* question.
*
* This method should be implemented to gather all flakerefs the
* command is operating with (presumably specified via some other
* arguments) so that the completions for these flags can use them.
*/
virtual std::vector<FlakeRef> getFlakeRefsForCompletion()
{
return {};
}
};
struct SourceExprCommand : virtual Args, MixFlakeOptions
{
std::optional<Path> file;
std::optional<std::string> expr;
bool readOnlyMode = false;
// FIXME: move this; not all commands (e.g. 'nix run') use it.
OperateOn operateOn = OperateOn::Output;
SourceExprCommand();
SourceExprCommand(bool supportReadOnlyMode = false);
Installables parseInstallables(ref<Store> store, std::vector<std::string> ss);
std::vector<std::shared_ptr<Installable>> parseInstallables(
ref<Store> store, std::vector<std::string> ss);
std::shared_ptr<Installable> parseInstallable(
ref<Store> store, const std::string & installable);
ref<Installable> parseInstallable(ref<Store> store, const std::string & installable);
virtual Strings getDefaultFlakeAttrPaths();
virtual Strings getDefaultFlakeAttrPathPrefixes();
void completeInstallable(std::string_view prefix);
/**
* Complete an installable from the given prefix.
*/
void completeInstallable(AddCompletions & completions, std::string_view prefix);
/**
* Convenience wrapper around the underlying function to make setting the
* callback easier.
*/
CompleterClosure getCompleteInstallable();
};
/* A command that operates on a list of "installables", which can be
store paths, attribute paths, Nix expressions, etc. */
struct InstallablesCommand : virtual Args, SourceExprCommand
/**
* A mixin class for commands that need a read-only flag.
*
* What exactly is "read-only" is unspecified, but it will usually be
* the \ref Store "Nix store".
*/
struct MixReadOnlyOption : virtual Args
{
std::vector<std::shared_ptr<Installable>> installables;
InstallablesCommand();
void prepare() override;
Installables load();
virtual bool useDefaultInstallables() { return true; }
std::vector<std::string> getFlakesForCompletion() override;
protected:
std::vector<std::string> _installables;
MixReadOnlyOption();
};
/* A command that operates on exactly one "installable" */
/**
* Like InstallablesCommand but the installables are not loaded.
*
* This is needed by `CmdRepl` which wants to load (and reload) the
* installables itself.
*/
struct RawInstallablesCommand : virtual Args, SourceExprCommand
{
RawInstallablesCommand();
virtual void run(ref<Store> store, std::vector<std::string> && rawInstallables) = 0;
void run(ref<Store> store) override;
// FIXME make const after `CmdRepl`'s override is fixed up
virtual void applyDefaultInstallables(std::vector<std::string> & rawInstallables);
bool readFromStdIn = false;
std::vector<FlakeRef> getFlakeRefsForCompletion() override;
private:
std::vector<std::string> rawInstallables;
};
/**
* A command that operates on a list of "installables", which can be
* store paths, attribute paths, Nix expressions, etc.
*/
struct InstallablesCommand : RawInstallablesCommand
{
virtual void run(ref<Store> store, Installables && installables) = 0;
void run(ref<Store> store, std::vector<std::string> && rawInstallables) override;
};
/**
* A command that operates on exactly one "installable".
*/
struct InstallableCommand : virtual Args, SourceExprCommand
{
std::shared_ptr<Installable> installable;
InstallableCommand();
InstallableCommand(bool supportReadOnlyMode = false);
virtual void run(ref<Store> store, ref<Installable> installable) = 0;
void prepare() override;
void run(ref<Store> store) override;
std::vector<std::string> getFlakesForCompletion() override
{
return {_installable};
}
std::vector<FlakeRef> getFlakeRefsForCompletion() override;
private:
std::string _installable{"."};
};
/* A command that operates on zero or more store paths. */
struct BuiltPathsCommand : public InstallablesCommand
struct MixOperateOnOptions : virtual Args
{
OperateOn operateOn = OperateOn::Output;
MixOperateOnOptions();
};
/**
* A command that operates on zero or more extant store paths.
*
* If the argument the user passes is a some sort of recipe for a path
* not yet built, it must be built first.
*/
struct BuiltPathsCommand : InstallablesCommand, virtual MixOperateOnOptions
{
private:
@ -169,46 +239,44 @@ public:
BuiltPathsCommand(bool recursive = false);
using StoreCommand::run;
virtual void run(ref<Store> store, BuiltPaths && paths) = 0;
void run(ref<Store> store) override;
void run(ref<Store> store, Installables && installables) override;
bool useDefaultInstallables() override { return !all; }
void applyDefaultInstallables(std::vector<std::string> & rawInstallables) override;
};
struct StorePathsCommand : public BuiltPathsCommand
{
StorePathsCommand(bool recursive = false);
using BuiltPathsCommand::run;
virtual void run(ref<Store> store, std::vector<StorePath> && storePaths) = 0;
virtual void run(ref<Store> store, StorePaths && storePaths) = 0;
void run(ref<Store> store, BuiltPaths && paths) override;
};
/* A command that operates on exactly one store path. */
/**
* A command that operates on exactly one store path.
*/
struct StorePathCommand : public StorePathsCommand
{
using StorePathsCommand::run;
virtual void run(ref<Store> store, const StorePath & storePath) = 0;
void run(ref<Store> store, std::vector<StorePath> && storePaths) override;
void run(ref<Store> store, StorePaths && storePaths) override;
};
/* A helper class for registering commands globally. */
/**
* A helper class for registering \ref Command commands globally.
*/
struct RegisterCommand
{
typedef std::map<std::vector<std::string>, std::function<ref<Command>()>> Commands;
static Commands * commands;
RegisterCommand(std::vector<std::string> && name,
std::function<ref<Command>()> command)
RegisterCommand(std::vector<std::string> && name, std::function<ref<Command>()> command)
{
if (!commands) commands = new Commands;
if (!commands)
commands = new Commands;
commands->emplace(name, command);
}
@ -218,19 +286,15 @@ struct RegisterCommand
template<class T>
static RegisterCommand registerCommand(const std::string & name)
{
return RegisterCommand({name}, [](){ return make_ref<T>(); });
return RegisterCommand({name}, []() { return make_ref<T>(); });
}
template<class T>
static RegisterCommand registerCommand2(std::vector<std::string> && name)
{
return RegisterCommand(std::move(name), [](){ return make_ref<T>(); });
return RegisterCommand(std::move(name), []() { return make_ref<T>(); });
}
/* Helper function to generate args that invoke $EDITOR on
filename:lineno. */
Strings editorFor(const Path & file, uint32_t line);
struct MixProfile : virtual StoreCommand
{
std::optional<Path> profile;
@ -250,22 +314,35 @@ struct MixDefaultProfile : MixProfile
MixDefaultProfile();
};
struct MixEnvironment : virtual Args {
struct MixEnvironment : virtual Args
{
StringSet keep, unset;
Strings stringsEnv;
std::vector<char*> vectorEnv;
StringSet keepVars;
StringSet unsetVars;
std::map<std::string, std::string> setVars;
bool ignoreEnvironment;
MixEnvironment();
/* Modify global environ based on ignoreEnvironment, keep, and unset. It's expected that exec will be called before this class goes out of scope, otherwise environ will become invalid. */
/***
* Modify global environ based on `ignoreEnvironment`, `keep`,
* `unset`, and `added`. It's expected that exec will be called
* before this class goes out of scope, otherwise `environ` will
* become invalid.
*/
void setEnviron();
};
void completeFlakeRef(ref<Store> store, std::string_view prefix);
void completeFlakeInputPath(
AddCompletions & completions,
ref<EvalState> evalState,
const std::vector<FlakeRef> & flakeRefs,
std::string_view prefix);
void completeFlakeRef(AddCompletions & completions, ref<Store> store, std::string_view prefix);
void completeFlakeRefWithFragment(
AddCompletions & completions,
ref<EvalState> evalState,
flake::LockFlags lockFlags,
Strings attrPathPrefixes,
@ -275,13 +352,6 @@ void completeFlakeRefWithFragment(
std::string showVersions(const std::set<std::string> & versions);
void printClosureDiff(
ref<Store> store,
const StorePath & beforePath,
const StorePath & afterPath,
std::string_view indent);
ref<Store> store, const StorePath & beforePath, const StorePath & afterPath, std::string_view indent);
void runRepl(
ref<EvalState> evalState,
const ValMap & extraEnv);
}

View file

@ -1,26 +1,67 @@
#include "fetch-settings.hh"
#include "eval-settings.hh"
#include "common-eval-args.hh"
#include "shared.hh"
#include "config-global.hh"
#include "filetransfer.hh"
#include "util.hh"
#include "eval.hh"
#include "fetchers.hh"
#include "registry.hh"
#include "flake/flakeref.hh"
#include "flake/settings.hh"
#include "store-api.hh"
#include "command.hh"
#include "tarball.hh"
#include "fetch-to-store.hh"
#include "compatibility-settings.hh"
#include "eval-settings.hh"
namespace nix {
namespace fs { using namespace std::filesystem; }
fetchers::Settings fetchSettings;
static GlobalConfig::Register rFetchSettings(&fetchSettings);
EvalSettings evalSettings {
settings.readOnlyMode,
{
{
"flake",
[](ref<Store> store, std::string_view rest) {
experimentalFeatureSettings.require(Xp::Flakes);
// FIXME `parseFlakeRef` should take a `std::string_view`.
auto flakeRef = parseFlakeRef(fetchSettings, 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);
flake::Settings flakeSettings;
static GlobalConfig::Register rFlakeSettings(&flakeSettings);
CompatibilitySettings compatibilitySettings {};
static GlobalConfig::Register rCompatibilitySettings(&compatibilitySettings);
MixEvalArgs::MixEvalArgs()
{
auto category = "Common evaluation options";
addFlag({
.longName = "arg",
.description = "Pass the value *expr* as the argument *name* to Nix functions.",
.category = category,
.labels = {"name", "expr"},
.handler = {[&](std::string name, std::string expr) { autoArgs[name] = 'E' + expr; }}
.handler = {[&](std::string name, std::string expr) { autoArgs.insert_or_assign(name, AutoArg{AutoArgExpr{expr}}); }}
});
addFlag({
@ -28,16 +69,41 @@ MixEvalArgs::MixEvalArgs()
.description = "Pass the string *string* as the argument *name* to Nix functions.",
.category = category,
.labels = {"name", "string"},
.handler = {[&](std::string name, std::string s) { autoArgs[name] = 'S' + s; }},
.handler = {[&](std::string name, std::string s) { autoArgs.insert_or_assign(name, AutoArg{AutoArgString{s}}); }},
});
addFlag({
.longName = "arg-from-file",
.description = "Pass the contents of file *path* as the argument *name* to Nix functions.",
.category = category,
.labels = {"name", "path"},
.handler = {[&](std::string name, std::string path) { autoArgs.insert_or_assign(name, AutoArg{AutoArgFile{path}}); }},
.completer = completePath
});
addFlag({
.longName = "arg-from-stdin",
.description = "Pass the contents of stdin as the argument *name* to Nix functions.",
.category = category,
.labels = {"name"},
.handler = {[&](std::string name) { autoArgs.insert_or_assign(name, AutoArg{AutoArgStdin{}}); }},
});
addFlag({
.longName = "include",
.shortName = 'I',
.description = "Add *path* to the list of locations used to look up `<...>` file names.",
.description = R"(
Add *path* to search path entries used to resolve [lookup paths](@docroot@/language/constructs/lookup-path.md)
This option may be given multiple times.
Paths added through `-I` take precedence over the [`nix-path` configuration setting](@docroot@/command-ref/conf-file.md#conf-nix-path) and the [`NIX_PATH` environment variable](@docroot@/command-ref/env-common.md#env-NIX_PATH).
)",
.category = category,
.labels = {"path"},
.handler = {[&](std::string s) { searchPath.push_back(s); }}
.handler = {[&](std::string s) {
lookupPath.elements.emplace_back(LookupPath::Elem::parse(s));
}}
});
addFlag({
@ -55,20 +121,24 @@ MixEvalArgs::MixEvalArgs()
.category = category,
.labels = {"original-ref", "resolved-ref"},
.handler = {[&](std::string _from, std::string _to) {
auto from = parseFlakeRef(_from, absPath("."));
auto to = parseFlakeRef(_to, absPath("."));
auto from = parseFlakeRef(fetchSettings, _from, fs::current_path().string());
auto to = parseFlakeRef(fetchSettings, _to, fs::current_path().string());
fetchers::Attrs extraAttrs;
if (to.subdir != "") extraAttrs["dir"] = to.subdir;
fetchers::overrideRegistry(from.input, to.input, extraAttrs);
}},
.completer = {[&](size_t, std::string_view prefix) {
completeFlakeRef(openStore(), prefix);
.completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) {
completeFlakeRef(completions, openStore(), prefix);
}}
});
addFlag({
.longName = "eval-store",
.description = "The Nix store to use for evaluations.",
.description =
R"(
The [URL of the Nix store](@docroot@/store/types/index.md#store-url-format)
to use for evaluation, i.e. to store derivations (`.drv` files) and inputs referenced by them.
)",
.category = category,
.labels = {"store-url"},
.handler = {&evalStoreUrl},
@ -78,28 +148,52 @@ MixEvalArgs::MixEvalArgs()
Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
{
auto res = state.buildBindings(autoArgs.size());
for (auto & i : autoArgs) {
for (auto & [name, arg] : autoArgs) {
auto v = state.allocValue();
if (i.second[0] == 'E')
state.mkThunk_(*v, state.parseExprFromString(i.second.substr(1), absPath(".")));
else
v->mkString(((std::string_view) i.second).substr(1));
res.insert(state.symbols.create(i.first), v);
std::visit(overloaded {
[&](const AutoArgExpr & arg) {
state.mkThunk_(*v, state.parseExprFromString(arg.expr, compatibilitySettings.nixShellShebangArgumentsRelativeToScript ? state.rootPath(absPath(getCommandBaseDir())) : state.rootPath(".")));
},
[&](const AutoArgString & arg) {
v->mkString(arg.s);
},
[&](const AutoArgFile & arg) {
v->mkString(readFile(arg.path.string()));
},
[&](const AutoArgStdin & arg) {
v->mkString(readFile(STDIN_FILENO));
}
}, arg);
res.insert(state.symbols.create(name), v);
}
return res.finish();
}
Path lookupFileArg(EvalState & state, std::string_view s)
SourcePath lookupFileArg(EvalState & state, std::string_view s, const Path * baseDir)
{
if (isUri(s)) {
return state.store->toRealPath(
fetchers::downloadTarball(
state.store, resolveUri(s), "source", false).first.storePath);
} else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') {
if (EvalSettings::isPseudoUrl(s)) {
auto accessor = fetchers::downloadTarball(
state.store,
state.fetchSettings,
EvalSettings::resolvePseudoUrl(s));
auto storePath = fetchToStore(*state.store, SourcePath(accessor), FetchMode::Copy);
return state.rootPath(CanonPath(state.store->toRealPath(storePath)));
}
else if (hasPrefix(s, "flake:")) {
experimentalFeatureSettings.require(Xp::Flakes);
auto flakeRef = parseFlakeRef(fetchSettings, std::string(s.substr(6)), {}, true, false);
auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first;
return state.rootPath(CanonPath(state.store->toRealPath(storePath)));
}
else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') {
Path p(s.substr(1, s.size() - 2));
return state.findFile(p);
} else
return absPath(std::string(s));
}
else
return state.rootPath(baseDir ? absPath(s, *baseDir) : absPath(s));
}
}

View file

@ -1,27 +1,73 @@
#pragma once
///@file
#include "args.hh"
#include "canon-path.hh"
#include "common-args.hh"
#include "search-path.hh"
#include <filesystem>
namespace nix {
class Store;
class EvalState;
class Bindings;
struct MixEvalArgs : virtual Args
namespace fetchers { struct Settings; }
class EvalState;
struct EvalSettings;
struct CompatibilitySettings;
class Bindings;
struct SourcePath;
namespace flake { struct Settings; }
/**
* @todo Get rid of global setttings variables
*/
extern fetchers::Settings fetchSettings;
/**
* @todo Get rid of global setttings variables
*/
extern EvalSettings evalSettings;
/**
* @todo Get rid of global setttings variables
*/
extern flake::Settings flakeSettings;
/**
* Settings that control behaviors that have changed since Nix 2.3.
*/
extern CompatibilitySettings compatibilitySettings;
struct MixEvalArgs : virtual Args, virtual MixRepair
{
static constexpr auto category = "Common evaluation options";
MixEvalArgs();
Bindings * getAutoArgs(EvalState & state);
Strings searchPath;
LookupPath lookupPath;
std::optional<std::string> evalStoreUrl;
private:
std::map<std::string, std::string> autoArgs;
struct AutoArgExpr { std::string expr; };
struct AutoArgString { std::string s; };
struct AutoArgFile { std::filesystem::path path; };
struct AutoArgStdin { };
using AutoArg = std::variant<AutoArgExpr, AutoArgString, AutoArgFile, AutoArgStdin>;
std::map<std::string, AutoArg> autoArgs;
};
Path lookupFileArg(EvalState & state, std::string_view s);
/**
* @param baseDir Optional [base directory](https://nixos.org/manual/nix/unstable/glossary#gloss-base-directory)
*/
SourcePath lookupFileArg(EvalState & state, std::string_view s, const Path * baseDir = nullptr);
}

View file

@ -0,0 +1,36 @@
#pragma once
#include "config.hh"
namespace nix {
struct CompatibilitySettings : public Config
{
CompatibilitySettings() = default;
// Added in Nix 2.24, July 2024.
Setting<bool> nixShellAlwaysLooksForShellNix{this, true, "nix-shell-always-looks-for-shell-nix", R"(
Before Nix 2.24, [`nix-shell`](@docroot@/command-ref/nix-shell.md) would only look at `shell.nix` if it was in the working directory - when no file was specified.
Since Nix 2.24, `nix-shell` always looks for a `shell.nix`, whether that's in the working directory, or in a directory that was passed as an argument.
You may set this to `false` to temporarily revert to the behavior of Nix 2.23 and older.
Using this setting is not recommended.
It will be deprecated and removed.
)"};
// Added in Nix 2.24, July 2024.
Setting<bool> nixShellShebangArgumentsRelativeToScript{
this, true, "nix-shell-shebang-arguments-relative-to-script", R"(
Before Nix 2.24, relative file path expressions in arguments in a `nix-shell` shebang were resolved relative to the working directory.
Since Nix 2.24, `nix-shell` resolves these paths in a manner that is relative to the [base directory](@docroot@/glossary.md#gloss-base-directory), defined as the script's directory.
You may set this to `false` to temporarily revert to the behavior of Nix 2.23 and older.
Using this setting is not recommended.
It will be deprecated and removed.
)"};
};
};

24
src/libcmd/editor-for.cc Normal file
View file

@ -0,0 +1,24 @@
#include "editor-for.hh"
#include "environment-variables.hh"
#include "source-path.hh"
namespace nix {
Strings editorFor(const SourcePath & file, uint32_t line)
{
auto path = file.getPhysicalPath();
if (!path)
throw Error("cannot open '%s' in an editor because it has no physical path", file);
auto editor = getEnv("EDITOR").value_or("cat");
auto args = tokenizeString<Strings>(editor);
if (line > 0 && (
editor.find("emacs") != std::string::npos ||
editor.find("nano") != std::string::npos ||
editor.find("vim") != std::string::npos ||
editor.find("kak") != std::string::npos))
args.push_back(fmt("+%d", line));
args.push_back(path->string());
return args;
}
}

15
src/libcmd/editor-for.hh Normal file
View file

@ -0,0 +1,15 @@
#pragma once
///@file
#include "types.hh"
#include "source-path.hh"
namespace nix {
/**
* Helper function to generate args that invoke $EDITOR on
* filename:lineno.
*/
Strings editorFor(const SourcePath & file, uint32_t line);
}

View file

@ -0,0 +1,124 @@
#include "globals.hh"
#include "installable-attr-path.hh"
#include "outputs-spec.hh"
#include "util.hh"
#include "command.hh"
#include "attr-path.hh"
#include "common-eval-args.hh"
#include "derivations.hh"
#include "eval-inline.hh"
#include "eval.hh"
#include "get-drvs.hh"
#include "store-api.hh"
#include "shared.hh"
#include "flake/flake.hh"
#include "eval-cache.hh"
#include "url.hh"
#include "registry.hh"
#include "build-result.hh"
#include <regex>
#include <queue>
#include <nlohmann/json.hpp>
namespace nix {
InstallableAttrPath::InstallableAttrPath(
ref<EvalState> state,
SourceExprCommand & cmd,
Value * v,
const std::string & attrPath,
ExtendedOutputsSpec extendedOutputsSpec)
: InstallableValue(state)
, cmd(cmd)
, v(allocRootValue(v))
, attrPath(attrPath)
, extendedOutputsSpec(std::move(extendedOutputsSpec))
{ }
std::pair<Value *, PosIdx> InstallableAttrPath::toValue(EvalState & state)
{
auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), **v);
state.forceValue(*vRes, pos);
return {vRes, pos};
}
DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths()
{
auto [v, pos] = toValue(*state);
if (std::optional derivedPathWithInfo = trySinglePathToDerivedPaths(
*v,
pos,
fmt("while evaluating the attribute '%s'", attrPath)))
{
return { *derivedPathWithInfo };
}
Bindings & autoArgs = *cmd.getAutoArgs(*state);
PackageInfos packageInfos;
getDerivations(*state, *v, "", autoArgs, packageInfos, false);
// Backward compatibility hack: group results by drvPath. This
// helps keep .all output together.
std::map<StorePath, OutputsSpec> byDrvPath;
for (auto & packageInfo : packageInfos) {
auto drvPath = packageInfo.queryDrvPath();
if (!drvPath)
throw Error("'%s' is not a derivation", what());
auto newOutputs = std::visit(overloaded {
[&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec {
std::set<std::string> outputsToInstall;
for (auto & output : packageInfo.queryOutputs(false, true))
outputsToInstall.insert(output.first);
if (outputsToInstall.empty())
outputsToInstall.insert("out");
return OutputsSpec::Names { std::move(outputsToInstall) };
},
[&](const ExtendedOutputsSpec::Explicit & e) -> OutputsSpec {
return e;
},
}, extendedOutputsSpec.raw);
auto [iter, didInsert] = byDrvPath.emplace(*drvPath, newOutputs);
if (!didInsert)
iter->second = iter->second.union_(newOutputs);
}
DerivedPathsWithInfo res;
for (auto & [drvPath, outputs] : byDrvPath)
res.push_back({
.path = DerivedPath::Built {
.drvPath = makeConstantStorePathRef(drvPath),
.outputs = outputs,
},
.info = make_ref<ExtraPathInfoValue>(ExtraPathInfoValue::Value {
.extendedOutputsSpec = outputs,
/* FIXME: reconsider backwards compatibility above
so we can fill in this info. */
}),
});
return res;
}
InstallableAttrPath InstallableAttrPath::parse(
ref<EvalState> state,
SourceExprCommand & cmd,
Value * v,
std::string_view prefix,
ExtendedOutputsSpec extendedOutputsSpec)
{
return {
state, cmd, v,
prefix == "." ? "" : std::string { prefix },
std::move(extendedOutputsSpec),
};
}
}

View file

@ -0,0 +1,58 @@
#pragma once
///@file
#include "globals.hh"
#include "installable-value.hh"
#include "outputs-spec.hh"
#include "command.hh"
#include "attr-path.hh"
#include "common-eval-args.hh"
#include "derivations.hh"
#include "eval-inline.hh"
#include "eval.hh"
#include "get-drvs.hh"
#include "store-api.hh"
#include "shared.hh"
#include "eval-cache.hh"
#include "url.hh"
#include "registry.hh"
#include "build-result.hh"
#include <regex>
#include <queue>
#include <nlohmann/json.hpp>
namespace nix {
class InstallableAttrPath : public InstallableValue
{
SourceExprCommand & cmd;
RootValue v;
std::string attrPath;
ExtendedOutputsSpec extendedOutputsSpec;
InstallableAttrPath(
ref<EvalState> state,
SourceExprCommand & cmd,
Value * v,
const std::string & attrPath,
ExtendedOutputsSpec extendedOutputsSpec);
std::string what() const override { return attrPath; };
std::pair<Value *, PosIdx> toValue(EvalState & state) override;
DerivedPathsWithInfo toDerivedPaths() override;
public:
static InstallableAttrPath parse(
ref<EvalState> state,
SourceExprCommand & cmd,
Value * v,
std::string_view prefix,
ExtendedOutputsSpec extendedOutputsSpec);
};
}

View file

@ -0,0 +1,65 @@
#include "installable-derived-path.hh"
#include "derivations.hh"
namespace nix {
std::string InstallableDerivedPath::what() const
{
return derivedPath.to_string(*store);
}
DerivedPathsWithInfo InstallableDerivedPath::toDerivedPaths()
{
return {{
.path = derivedPath,
.info = make_ref<ExtraPathInfo>(),
}};
}
std::optional<StorePath> InstallableDerivedPath::getStorePath()
{
return derivedPath.getBaseStorePath();
}
InstallableDerivedPath InstallableDerivedPath::parse(
ref<Store> store,
std::string_view prefix,
ExtendedOutputsSpec extendedOutputsSpec)
{
auto derivedPath = std::visit(overloaded {
// If the user did not use ^, we treat the output more
// liberally: we accept a symlink chain or an actual
// store path.
[&](const ExtendedOutputsSpec::Default &) -> DerivedPath {
auto storePath = store->followLinksToStorePath(prefix);
// Remove this prior to stabilizing the new CLI.
if (storePath.isDerivation()) {
auto oldDerivedPath = DerivedPath::Built {
.drvPath = makeConstantStorePathRef(storePath),
.outputs = OutputsSpec::All { },
};
warn(
"The interpretation of store paths arguments ending in `.drv` recently changed. If this command is now failing try again with '%s'",
oldDerivedPath.to_string(*store));
};
return DerivedPath::Opaque {
.path = std::move(storePath),
};
},
// If the user did use ^, we just do exactly what is written.
[&](const ExtendedOutputsSpec::Explicit & outputSpec) -> DerivedPath {
auto drv = make_ref<SingleDerivedPath>(SingleDerivedPath::parse(*store, prefix));
drvRequireExperiment(*drv);
return DerivedPath::Built {
.drvPath = std::move(drv),
.outputs = outputSpec,
};
},
}, extendedOutputsSpec.raw);
return InstallableDerivedPath {
store,
std::move(derivedPath),
};
}
}

View file

@ -0,0 +1,29 @@
#pragma once
///@file
#include "installables.hh"
namespace nix {
struct InstallableDerivedPath : Installable
{
ref<Store> store;
DerivedPath derivedPath;
InstallableDerivedPath(ref<Store> store, DerivedPath && derivedPath)
: store(store), derivedPath(std::move(derivedPath))
{ }
std::string what() const override;
DerivedPathsWithInfo toDerivedPaths() override;
std::optional<StorePath> getStorePath() override;
static InstallableDerivedPath parse(
ref<Store> store,
std::string_view prefix,
ExtendedOutputsSpec extendedOutputsSpec);
};
}

View file

@ -0,0 +1,219 @@
#include "globals.hh"
#include "installable-flake.hh"
#include "installable-derived-path.hh"
#include "outputs-spec.hh"
#include "util.hh"
#include "command.hh"
#include "attr-path.hh"
#include "common-eval-args.hh"
#include "derivations.hh"
#include "eval-inline.hh"
#include "eval.hh"
#include "get-drvs.hh"
#include "store-api.hh"
#include "shared.hh"
#include "flake/flake.hh"
#include "eval-cache.hh"
#include "url.hh"
#include "registry.hh"
#include "build-result.hh"
#include <regex>
#include <queue>
#include <nlohmann/json.hpp>
namespace nix {
std::vector<std::string> InstallableFlake::getActualAttrPaths()
{
std::vector<std::string> res;
if (attrPaths.size() == 1 && attrPaths.front().starts_with(".")){
attrPaths.front().erase(0,1);
res.push_back(attrPaths.front());
return res;
}
for (auto & prefix : prefixes)
res.push_back(prefix + *attrPaths.begin());
for (auto & s : attrPaths)
res.push_back(s);
return res;
}
static std::string showAttrPaths(const std::vector<std::string> & paths)
{
std::string s;
for (const auto & [n, i] : enumerate(paths)) {
if (n > 0) s += n + 1 == paths.size() ? " or " : ", ";
s += '\''; s += i; s += '\'';
}
return s;
}
InstallableFlake::InstallableFlake(
SourceExprCommand * cmd,
ref<EvalState> state,
FlakeRef && flakeRef,
std::string_view fragment,
ExtendedOutputsSpec extendedOutputsSpec,
Strings attrPaths,
Strings prefixes,
const flake::LockFlags & lockFlags)
: InstallableValue(state),
flakeRef(flakeRef),
attrPaths(fragment == "" ? attrPaths : Strings{(std::string) fragment}),
prefixes(fragment == "" ? Strings{} : prefixes),
extendedOutputsSpec(std::move(extendedOutputsSpec)),
lockFlags(lockFlags)
{
if (cmd && cmd->getAutoArgs(*state)->size())
throw UsageError("'--arg' and '--argstr' are incompatible with flakes");
}
DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
{
Activity act(*logger, lvlTalkative, actEvaluate, fmt("evaluating derivation '%s'", what()));
auto attr = getCursor(*state);
auto attrPath = attr->getAttrPathStr();
if (!attr->isDerivation()) {
// FIXME: use eval cache?
auto v = attr->forceValue();
if (std::optional derivedPathWithInfo = trySinglePathToDerivedPaths(
v,
noPos,
fmt("while evaluating the flake output attribute '%s'", attrPath)))
{
return { *derivedPathWithInfo };
} else {
throw Error(
"expected flake output attribute '%s' to be a derivation or path but found %s: %s",
attrPath,
showType(v),
ValuePrinter(*this->state, v, errorPrintOptions)
);
}
}
auto drvPath = attr->forceDerivation();
std::optional<NixInt::Inner> priority;
if (attr->maybeGetAttr(state->sOutputSpecified)) {
} else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) {
if (auto aPriority = aMeta->maybeGetAttr("priority"))
priority = aPriority->getInt().value;
}
return {{
.path = DerivedPath::Built {
.drvPath = makeConstantStorePathRef(std::move(drvPath)),
.outputs = std::visit(overloaded {
[&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec {
std::set<std::string> outputsToInstall;
if (auto aOutputSpecified = attr->maybeGetAttr(state->sOutputSpecified)) {
if (aOutputSpecified->getBool()) {
if (auto aOutputName = attr->maybeGetAttr("outputName"))
outputsToInstall = { aOutputName->getString() };
}
} else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) {
if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall"))
for (auto & s : aOutputsToInstall->getListOfStrings())
outputsToInstall.insert(s);
}
if (outputsToInstall.empty())
outputsToInstall.insert("out");
return OutputsSpec::Names { std::move(outputsToInstall) };
},
[&](const ExtendedOutputsSpec::Explicit & e) -> OutputsSpec {
return e;
},
}, extendedOutputsSpec.raw),
},
.info = make_ref<ExtraPathInfoFlake>(
ExtraPathInfoValue::Value {
.priority = priority,
.attrPath = attrPath,
.extendedOutputsSpec = extendedOutputsSpec,
},
ExtraPathInfoFlake::Flake {
.originalRef = flakeRef,
.lockedRef = getLockedFlake()->flake.lockedRef,
}),
}};
}
std::pair<Value *, PosIdx> InstallableFlake::toValue(EvalState & state)
{
return {&getCursor(state)->forceValue(), noPos};
}
std::vector<ref<eval_cache::AttrCursor>>
InstallableFlake::getCursors(EvalState & state)
{
auto evalCache = openEvalCache(state, getLockedFlake());
auto root = evalCache->getRoot();
std::vector<ref<eval_cache::AttrCursor>> res;
Suggestions suggestions;
auto attrPaths = getActualAttrPaths();
for (auto & attrPath : attrPaths) {
debug("trying flake output attribute '%s'", attrPath);
auto attr = root->findAlongAttrPath(parseAttrPath(state, attrPath));
if (attr) {
res.push_back(ref(*attr));
} else {
suggestions += attr.getSuggestions();
}
}
if (res.size() == 0)
throw Error(
suggestions,
"flake '%s' does not provide attribute %s",
flakeRef,
showAttrPaths(attrPaths));
return res;
}
std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const
{
if (!_lockedFlake) {
flake::LockFlags lockFlagsApplyConfig = lockFlags;
// FIXME why this side effect?
lockFlagsApplyConfig.applyNixConfig = true;
_lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(
flakeSettings, *state, flakeRef, lockFlagsApplyConfig));
}
return _lockedFlake;
}
FlakeRef InstallableFlake::nixpkgsFlakeRef() const
{
auto lockedFlake = getLockedFlake();
if (auto nixpkgsInput = lockedFlake->lockFile.findInput({"nixpkgs"})) {
if (auto lockedNode = std::dynamic_pointer_cast<const flake::LockedNode>(nixpkgsInput)) {
debug("using nixpkgs flake '%s'", lockedNode->lockedRef);
return std::move(lockedNode->lockedRef);
}
}
return defaultNixpkgsFlakeRef();
}
}

View file

@ -0,0 +1,89 @@
#pragma once
///@file
#include "common-eval-args.hh"
#include "installable-value.hh"
namespace nix {
/**
* Extra info about a \ref DerivedPath "derived path" that ultimately
* come from a Flake.
*
* Invariant: every ExtraPathInfo gotten from an InstallableFlake should
* be possible to downcast to an ExtraPathInfoFlake.
*/
struct ExtraPathInfoFlake : ExtraPathInfoValue
{
/**
* Extra struct to get around C++ designated initializer limitations
*/
struct Flake {
FlakeRef originalRef;
FlakeRef lockedRef;
};
Flake flake;
ExtraPathInfoFlake(Value && v, Flake && f)
: ExtraPathInfoValue(std::move(v)), flake(f)
{ }
};
struct InstallableFlake : InstallableValue
{
FlakeRef flakeRef;
Strings attrPaths;
Strings prefixes;
ExtendedOutputsSpec extendedOutputsSpec;
const flake::LockFlags & lockFlags;
mutable std::shared_ptr<flake::LockedFlake> _lockedFlake;
InstallableFlake(
SourceExprCommand * cmd,
ref<EvalState> state,
FlakeRef && flakeRef,
std::string_view fragment,
ExtendedOutputsSpec extendedOutputsSpec,
Strings attrPaths,
Strings prefixes,
const flake::LockFlags & lockFlags);
std::string what() const override { return flakeRef.to_string() + "#" + *attrPaths.begin(); }
std::vector<std::string> getActualAttrPaths();
DerivedPathsWithInfo toDerivedPaths() override;
std::pair<Value *, PosIdx> toValue(EvalState & state) override;
/**
* Get a cursor to every attrpath in getActualAttrPaths() that
* exists. However if none exists, throw an exception.
*/
std::vector<ref<eval_cache::AttrCursor>>
getCursors(EvalState & state) override;
std::shared_ptr<flake::LockedFlake> getLockedFlake() const;
FlakeRef nixpkgsFlakeRef() const;
};
/**
* Default flake ref for referring to Nixpkgs. For flakes that don't
* have their own Nixpkgs input, or other installables.
*
* It is a layer violation for Nix to know about Nixpkgs; currently just
* `nix develop` does. Be wary of using this /
* `InstallableFlake::nixpkgsFlakeRef` more places.
*/
static inline FlakeRef defaultNixpkgsFlakeRef()
{
return FlakeRef::fromAttrs(fetchSettings, {{"type","indirect"}, {"id", "nixpkgs"}});
}
ref<eval_cache::EvalCache> openEvalCache(
EvalState & state,
std::shared_ptr<flake::LockedFlake> lockedFlake);
}

View file

@ -0,0 +1,68 @@
#include "installable-value.hh"
#include "eval-cache.hh"
#include "fetch-to-store.hh"
namespace nix {
std::vector<ref<eval_cache::AttrCursor>>
InstallableValue::getCursors(EvalState & state)
{
auto evalCache =
std::make_shared<nix::eval_cache::EvalCache>(std::nullopt, state,
[&]() { return toValue(state).first; });
return {evalCache->getRoot()};
}
ref<eval_cache::AttrCursor>
InstallableValue::getCursor(EvalState & state)
{
/* Although getCursors should return at least one element, in case it doesn't,
bound check to avoid an undefined behavior for vector[0] */
return getCursors(state).at(0);
}
static UsageError nonValueInstallable(Installable & installable)
{
return UsageError("installable '%s' does not correspond to a Nix language value", installable.what());
}
InstallableValue & InstallableValue::require(Installable & installable)
{
auto * castedInstallable = dynamic_cast<InstallableValue *>(&installable);
if (!castedInstallable)
throw nonValueInstallable(installable);
return *castedInstallable;
}
ref<InstallableValue> InstallableValue::require(ref<Installable> installable)
{
auto castedInstallable = installable.dynamic_pointer_cast<InstallableValue>();
if (!castedInstallable)
throw nonValueInstallable(*installable);
return ref { castedInstallable };
}
std::optional<DerivedPathWithInfo> InstallableValue::trySinglePathToDerivedPaths(Value & v, const PosIdx pos, std::string_view errorCtx)
{
if (v.type() == nPath) {
auto storePath = fetchToStore(*state->store, v.path(), FetchMode::Copy);
return {{
.path = DerivedPath::Opaque {
.path = std::move(storePath),
},
.info = make_ref<ExtraPathInfo>(),
}};
}
else if (v.type() == nString) {
return {{
.path = DerivedPath::fromSingle(
state->coerceToSingleDerivedPath(pos, v, errorCtx)),
.info = make_ref<ExtraPathInfo>(),
}};
}
else return std::nullopt;
}
}

View file

@ -0,0 +1,121 @@
#pragma once
///@file
#include "installables.hh"
#include "flake/flake.hh"
namespace nix {
struct PackageInfo;
struct SourceExprCommand;
namespace eval_cache { class EvalCache; class AttrCursor; }
struct App
{
std::vector<DerivedPath> context;
Path program;
// FIXME: add args, sandbox settings, metadata, ...
};
struct UnresolvedApp
{
App unresolved;
App resolve(ref<Store> evalStore, ref<Store> store);
};
/**
* Extra info about a \ref DerivedPath "derived path" that ultimately
* come from a Nix language value.
*
* Invariant: every ExtraPathInfo gotten from an InstallableValue should
* be possible to downcast to an ExtraPathInfoValue.
*/
struct ExtraPathInfoValue : ExtraPathInfo
{
/**
* Extra struct to get around C++ designated initializer limitations
*/
struct Value {
/**
* An optional priority for use with "build envs". See Package
*/
std::optional<NixInt::Inner> priority;
/**
* The attribute path associated with this value. The idea is
* that an installable referring to a value typically refers to
* a larger value, from which we project a smaller value out
* with this.
*/
std::string attrPath;
/**
* \todo merge with DerivedPath's 'outputs' field?
*/
ExtendedOutputsSpec extendedOutputsSpec;
};
Value value;
ExtraPathInfoValue(Value && v)
: value(v)
{ }
virtual ~ExtraPathInfoValue() = default;
};
/**
* An Installable which corresponds a Nix language value, in addition to
* a collection of \ref DerivedPath "derived paths".
*/
struct InstallableValue : Installable
{
ref<EvalState> state;
InstallableValue(ref<EvalState> state) : state(state) {}
virtual ~InstallableValue() { }
virtual std::pair<Value *, PosIdx> toValue(EvalState & state) = 0;
/**
* Get a cursor to each value this Installable could refer to.
* However if none exists, throw exception instead of returning
* empty vector.
*/
virtual std::vector<ref<eval_cache::AttrCursor>>
getCursors(EvalState & state);
/**
* Get the first and most preferred cursor this Installable could
* refer to, or throw an exception if none exists.
*/
virtual ref<eval_cache::AttrCursor>
getCursor(EvalState & state);
UnresolvedApp toApp(EvalState & state);
static InstallableValue & require(Installable & installable);
static ref<InstallableValue> require(ref<Installable> installable);
protected:
/**
* Handles either a plain path, or a string with a single string
* context elem in the right format. The latter case is handled by
* `EvalState::coerceToDerivedPath()`; see it for details.
*
* @param v Value that is hopefully a string or path per the above.
*
* @param pos Position of value to aid with diagnostics.
*
* @param errorCtx Arbitrary message for use in potential error message when something is wrong with `v`.
*
* @result A derived path (with empty info, for now) if the value
* matched the above criteria.
*/
std::optional<DerivedPathWithInfo> trySinglePathToDerivedPaths(Value & v, const PosIdx pos, std::string_view errorCtx);
};
}

File diff suppressed because it is too large Load diff

View file

@ -1,127 +1,194 @@
#pragma once
///@file
#include "util.hh"
#include "path.hh"
#include "path-with-outputs.hh"
#include "outputs-spec.hh"
#include "derived-path.hh"
#include "eval.hh"
#include "built-path.hh"
#include "store-api.hh"
#include "flake/flake.hh"
#include "build-result.hh"
#include <optional>
namespace nix {
struct DrvInfo;
struct SourceExprCommand;
namespace eval_cache { class EvalCache; class AttrCursor; }
struct App
{
std::vector<StorePathWithOutputs> context;
Path program;
// FIXME: add args, sandbox settings, metadata, ...
};
struct UnresolvedApp
{
App unresolved;
App resolve(ref<Store> evalStore, ref<Store> store);
};
struct PackageInfo;
enum class Realise {
/* Build the derivation. Postcondition: the
derivation outputs exist. */
/**
* Build the derivation.
*
* Postcondition: the derivation outputs exist.
*/
Outputs,
/* Don't build the derivation. Postcondition: the store derivation
exists. */
/**
* Don't build the derivation.
*
* Postcondition: the store derivation exists.
*/
Derivation,
/* Evaluate in dry-run mode. Postcondition: nothing. */
// FIXME: currently unused, but could be revived if we can
// evaluate derivations in-memory.
/**
* Evaluate in dry-run mode.
*
* Postcondition: nothing.
*
* \todo currently unused, but could be revived if we can evaluate
* derivations in-memory.
*/
Nothing
};
/* How to handle derivations in commands that operate on store paths. */
/**
* How to handle derivations in commands that operate on store paths.
*/
enum class OperateOn {
/* Operate on the output path. */
/**
* Operate on the output path.
*/
Output,
/* Operate on the .drv path. */
/**
* Operate on the .drv path.
*/
Derivation
};
/**
* Extra info about a DerivedPath
*
* Yes, this is empty, but that is intended. It will be sub-classed by
* the subclasses of Installable to allow those to provide more info.
* Certain commands will make use of this info.
*/
struct ExtraPathInfo
{
virtual ~ExtraPathInfo() = default;
};
/**
* A DerivedPath with \ref ExtraPathInfo "any additional info" that
* commands might need from the derivation.
*/
struct DerivedPathWithInfo
{
DerivedPath path;
ref<ExtraPathInfo> info;
};
/**
* Like DerivedPathWithInfo but extending BuiltPath with \ref
* ExtraPathInfo "extra info" and also possibly the \ref BuildResult
* "result of building".
*/
struct BuiltPathWithResult
{
BuiltPath path;
ref<ExtraPathInfo> info;
std::optional<BuildResult> result;
};
/**
* Shorthand, for less typing and helping us keep the choice of
* collection in sync.
*/
typedef std::vector<DerivedPathWithInfo> DerivedPathsWithInfo;
struct Installable;
/**
* Shorthand, for less typing and helping us keep the choice of
* collection in sync.
*/
typedef std::vector<ref<Installable>> Installables;
/**
* Installables are the main positional arguments for the Nix
* Command-line.
*
* This base class is very flexible, and just assumes and the
* Installable refers to a collection of \ref DerivedPath "derived paths" with
* \ref ExtraPathInfo "extra info".
*/
struct Installable
{
virtual ~Installable() { }
/**
* What Installable is this?
*
* Prints back valid CLI syntax that would result in this same
* installable. It doesn't need to be exactly what the user wrote,
* just something that means the same thing.
*/
virtual std::string what() const = 0;
virtual DerivedPaths toDerivedPaths() = 0;
/**
* Get the collection of \ref DerivedPathWithInfo "derived paths
* with info" that this \ref Installable instalallable denotes.
*
* This is the main method of this class
*/
virtual DerivedPathsWithInfo toDerivedPaths() = 0;
virtual StorePathSet toDrvPaths(ref<Store> store)
{
throw Error("'%s' cannot be converted to a derivation path", what());
}
/**
* A convenience wrapper of the above for when we expect an
* installable to produce a single \ref DerivedPath "derived path"
* only.
*
* If no or multiple \ref DerivedPath "derived paths" are produced,
* and error is raised.
*/
DerivedPathWithInfo toDerivedPath();
DerivedPath toDerivedPath();
UnresolvedApp toApp(EvalState & state);
virtual std::pair<Value *, PosIdx> toValue(EvalState & state)
{
throw Error("argument '%s' cannot be evaluated", what());
}
/* Return a value only if this installable is a store path or a
symlink to it. */
/**
* Return a value only if this installable is a store path or a
* symlink to it.
*
* \todo should we move this to InstallableDerivedPath? It is only
* supposed to work there anyways. Can always downcast.
*/
virtual std::optional<StorePath> getStorePath()
{
return {};
}
virtual std::vector<ref<eval_cache::AttrCursor>>
getCursors(EvalState & state);
virtual ref<eval_cache::AttrCursor>
getCursor(EvalState & state);
virtual FlakeRef nixpkgsFlakeRef() const
{
return FlakeRef::fromAttrs({{"type","indirect"}, {"id", "nixpkgs"}});
}
static BuiltPaths build(
static std::vector<BuiltPathWithResult> build(
ref<Store> evalStore,
ref<Store> store,
Realise mode,
const std::vector<std::shared_ptr<Installable>> & installables,
const Installables & installables,
BuildMode bMode = bmNormal);
static std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> build2(
static std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> build2(
ref<Store> evalStore,
ref<Store> store,
Realise mode,
const std::vector<std::shared_ptr<Installable>> & installables,
const Installables & installables,
BuildMode bMode = bmNormal);
static std::set<StorePath> toStorePaths(
static std::set<StorePath> toStorePathSet(
ref<Store> evalStore,
ref<Store> store,
Realise mode,
OperateOn operateOn,
const std::vector<std::shared_ptr<Installable>> & installables);
const Installables & installables);
static std::vector<StorePath> toStorePaths(
ref<Store> evalStore,
ref<Store> store,
Realise mode,
OperateOn operateOn,
const Installables & installables);
static StorePath toStorePath(
ref<Store> evalStore,
ref<Store> store,
Realise mode,
OperateOn operateOn,
std::shared_ptr<Installable> installable);
ref<Installable> installable);
static std::set<StorePath> toDerivations(
ref<Store> store,
const std::vector<std::shared_ptr<Installable>> & installables,
const Installables & installables,
bool useDeriver = false);
static BuiltPaths toBuiltPaths(
@ -129,78 +196,7 @@ struct Installable
ref<Store> store,
Realise mode,
OperateOn operateOn,
const std::vector<std::shared_ptr<Installable>> & installables);
const Installables & installables);
};
typedef std::vector<std::shared_ptr<Installable>> Installables;
struct InstallableValue : Installable
{
ref<EvalState> state;
InstallableValue(ref<EvalState> state) : state(state) {}
struct DerivationInfo
{
StorePath drvPath;
std::set<std::string> outputsToInstall;
std::optional<NixInt> priority;
};
virtual std::vector<DerivationInfo> toDerivations() = 0;
DerivedPaths toDerivedPaths() override;
StorePathSet toDrvPaths(ref<Store> store) override;
};
struct InstallableFlake : InstallableValue
{
FlakeRef flakeRef;
Strings attrPaths;
Strings prefixes;
OutputsSpec outputsSpec;
const flake::LockFlags & lockFlags;
mutable std::shared_ptr<flake::LockedFlake> _lockedFlake;
InstallableFlake(
SourceExprCommand * cmd,
ref<EvalState> state,
FlakeRef && flakeRef,
std::string_view fragment,
OutputsSpec outputsSpec,
Strings attrPaths,
Strings prefixes,
const flake::LockFlags & lockFlags);
std::string what() const override { return flakeRef.to_string() + "#" + *attrPaths.begin(); }
std::vector<std::string> getActualAttrPaths();
Value * getFlakeOutputs(EvalState & state, const flake::LockedFlake & lockedFlake);
std::tuple<std::string, FlakeRef, DerivationInfo> toDerivation();
std::vector<DerivationInfo> toDerivations() override;
std::pair<Value *, PosIdx> toValue(EvalState & state) override;
/* Get a cursor to every attrpath in getActualAttrPaths() that
exists. */
std::vector<ref<eval_cache::AttrCursor>>
getCursors(EvalState & state) override;
/* Get a cursor to the first attrpath in getActualAttrPaths() that
exists, or throw an exception with suggestions if none exists. */
ref<eval_cache::AttrCursor> getCursor(EvalState & state) override;
std::shared_ptr<flake::LockedFlake> getLockedFlake() const;
FlakeRef nixpkgsFlakeRef() const override;
};
ref<eval_cache::EvalCache> openEvalCache(
EvalState & state,
std::shared_ptr<flake::LockedFlake> lockedFlake);
}

View file

@ -1,4 +1,5 @@
#pragma once
///@file
#include <functional>
#include <map>

View file

@ -6,10 +6,10 @@ libcmd_DIR := $(d)
libcmd_SOURCES := $(wildcard $(d)/*.cc)
libcmd_CXXFLAGS += -I src/libutil -I src/libstore -I src/libexpr -I src/libmain -I src/libfetchers -I src/nix
libcmd_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libfetchers) $(INCLUDE_libexpr) $(INCLUDE_libflake) $(INCLUDE_libmain)
libcmd_LDFLAGS = $(EDITLINE_LIBS) -llowdown -pthread
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, $(d)/nix-cmd.pc, $(libdir)/pkgconfig, 0644))
$(eval $(call install-file-in, $(buildprefix)$(d)/nix-cmd.pc, $(libdir)/pkgconfig, 0644))

View file

@ -1,24 +1,30 @@
#include "markdown.hh"
#include "util.hh"
#include "environment-variables.hh"
#include "error.hh"
#include "finally.hh"
#include "terminal.hh"
#include <sys/queue.h>
#include <lowdown.h>
#if HAVE_LOWDOWN
# include <sys/queue.h>
# include <lowdown.h>
#endif
namespace nix {
std::string renderMarkdownToTerminal(std::string_view markdown)
#if HAVE_LOWDOWN
static std::string doRenderMarkdownToTerminal(std::string_view markdown)
{
int windowWidth = getWindowSize().second;
struct lowdown_opts opts {
struct lowdown_opts opts
{
.type = LOWDOWN_TERM,
.maxdepth = 20,
.cols = (size_t) std::max(windowWidth - 5, 60),
.hmargin = 0,
.vmargin = 0,
.feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES,
.oflags = 0,
.oflags = LOWDOWN_TERM_NOLINK,
};
auto doc = lowdown_doc_new(&opts);
@ -46,7 +52,22 @@ std::string renderMarkdownToTerminal(std::string_view markdown)
if (!rndr_res)
throw Error("allocation error while rendering Markdown");
return filterANSIEscapes(std::string(buf->data, buf->size), !shouldANSI());
return filterANSIEscapes(std::string(buf->data, buf->size), !isTTY());
}
std::string renderMarkdownToTerminal(std::string_view markdown)
{
if (auto e = getEnv("_NIX_TEST_RAW_MARKDOWN"); e && *e == "1")
return std::string(markdown);
else
return doRenderMarkdownToTerminal(markdown);
}
#else
std::string renderMarkdownToTerminal(std::string_view markdown)
{
return std::string(markdown);
}
#endif
} // namespace nix

View file

@ -1,7 +1,17 @@
#include "types.hh"
#pragma once
///@file
#include <string_view>
namespace nix {
/**
* Render the given Markdown text to the terminal.
*
* If Nix is compiled without Markdown support, this function will return the input text as-is.
*
* The renderer takes into account the terminal width, and wraps text accordingly.
*/
std::string renderMarkdownToTerminal(std::string_view markdown);
}

130
src/libcmd/meson.build Normal file
View file

@ -0,0 +1,130 @@
project('nix-cmd', 'cpp',
version : files('.version'),
default_options : [
'cpp_std=c++2a',
# TODO(Qyriad): increase the warning level
'warning_level=1',
'debug=true',
'optimization=2',
'errorlogs=true', # Please print logs for tests that fail
],
meson_version : '>= 1.1',
license : 'LGPL-2.1-or-later',
)
cxx = meson.get_compiler('cpp')
subdir('build-utils-meson/deps-lists')
configdata = configuration_data()
deps_private_maybe_subproject = [
]
deps_public_maybe_subproject = [
dependency('nix-util'),
dependency('nix-store'),
dependency('nix-fetchers'),
dependency('nix-expr'),
dependency('nix-flake'),
dependency('nix-main'),
]
subdir('build-utils-meson/subprojects')
subdir('build-utils-meson/threads')
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
deps_public += nlohmann_json
lowdown = dependency('lowdown', version : '>= 0.9.0', required : get_option('markdown'))
deps_private += lowdown
configdata.set('HAVE_LOWDOWN', lowdown.found().to_int())
readline_flavor = get_option('readline-flavor')
if readline_flavor == 'editline'
editline = dependency('libeditline', 'editline', version : '>=1.14')
deps_private += editline
elif readline_flavor == 'readline'
readline = dependency('readline')
deps_private += readline
configdata.set(
'USE_READLINE',
1,
description: 'Use readline instead of editline',
)
else
error('illegal editline flavor', readline_flavor)
endif
config_h = configure_file(
configuration : configdata,
output : 'config-cmd.hh',
)
add_project_arguments(
# TODO(Qyriad): Yes this is how the autoconf+Make system did it.
# It would be nice for our headers to be idempotent instead.
'-include', 'config-util.hh',
'-include', 'config-store.hh',
# '-include', 'config-fetchers.h',
'-include', 'config-expr.hh',
'-include', 'config-main.hh',
'-include', 'config-cmd.hh',
language : 'cpp',
)
subdir('build-utils-meson/diagnostics')
sources = files(
'built-path.cc',
'command-installable-value.cc',
'command.cc',
'common-eval-args.cc',
'editor-for.cc',
'installable-attr-path.cc',
'installable-derived-path.cc',
'installable-flake.cc',
'installable-value.cc',
'installables.cc',
'legacy.cc',
'markdown.cc',
'misc-store-flags.cc',
'network-proxy.cc',
'repl-interacter.cc',
'repl.cc',
)
include_dirs = [include_directories('.')]
headers = [config_h] + files(
'built-path.hh',
'command-installable-value.hh',
'command.hh',
'common-eval-args.hh',
'compatibility-settings.hh',
'editor-for.hh',
'installable-attr-path.hh',
'installable-derived-path.hh',
'installable-flake.hh',
'installable-value.hh',
'installables.hh',
'legacy.hh',
'markdown.hh',
'misc-store-flags.hh',
'network-proxy.hh',
'repl-interacter.hh',
'repl.hh',
)
this_library = library(
'nixcmd',
sources,
dependencies : deps_public + deps_private + deps_other,
prelink : true, # For C++ static initializers
install : true,
)
install_headers(headers, subdir : 'nix', preserve_path : true)
libraries_private = []
subdir('build-utils-meson/export')

15
src/libcmd/meson.options Normal file
View file

@ -0,0 +1,15 @@
# vim: filetype=meson
option(
'markdown',
type: 'feature',
description: 'Enable Markdown rendering in the Nix binary (requires lowdown)',
)
option(
'readline-flavor',
type : 'combo',
choices : ['editline', 'readline'],
value : 'editline',
description : 'Which library to use for nice line editing with the Nix language REPL',
)

View file

@ -0,0 +1,135 @@
#include "misc-store-flags.hh"
namespace nix::flag
{
static void hashFormatCompleter(AddCompletions & completions, size_t index, std::string_view prefix)
{
for (auto & format : hashFormats) {
if (hasPrefix(format, prefix)) {
completions.add(format);
}
}
}
Args::Flag hashFormatWithDefault(std::string && longName, HashFormat * hf)
{
assert(*hf == nix::HashFormat::SRI);
return Args::Flag {
.longName = std::move(longName),
.description = "Hash format (`base16`, `nix32`, `base64`, `sri`). Default: `sri`.",
.labels = {"hash-format"},
.handler = {[hf](std::string s) {
*hf = parseHashFormat(s);
}},
.completer = hashFormatCompleter,
};
}
Args::Flag hashFormatOpt(std::string && longName, std::optional<HashFormat> * ohf)
{
return Args::Flag {
.longName = std::move(longName),
.description = "Hash format (`base16`, `nix32`, `base64`, `sri`).",
.labels = {"hash-format"},
.handler = {[ohf](std::string s) {
*ohf = std::optional<HashFormat>{parseHashFormat(s)};
}},
.completer = hashFormatCompleter,
};
}
static void hashAlgoCompleter(AddCompletions & completions, size_t index, std::string_view prefix)
{
for (auto & algo : hashAlgorithms)
if (hasPrefix(algo, prefix))
completions.add(algo);
}
Args::Flag hashAlgo(std::string && longName, HashAlgorithm * ha)
{
return Args::Flag {
.longName = std::move(longName),
.description = "Hash algorithm (`md5`, `sha1`, `sha256`, or `sha512`).",
.labels = {"hash-algo"},
.handler = {[ha](std::string s) {
*ha = parseHashAlgo(s);
}},
.completer = hashAlgoCompleter,
};
}
Args::Flag hashAlgoOpt(std::string && longName, std::optional<HashAlgorithm> * oha)
{
return Args::Flag {
.longName = std::move(longName),
.description = "Hash algorithm (`md5`, `sha1`, `sha256`, or `sha512`). Can be omitted for SRI hashes.",
.labels = {"hash-algo"},
.handler = {[oha](std::string s) {
*oha = std::optional<HashAlgorithm>{parseHashAlgo(s)};
}},
.completer = hashAlgoCompleter,
};
}
Args::Flag fileIngestionMethod(FileIngestionMethod * method)
{
return Args::Flag {
.longName = "mode",
// FIXME indentation carefully made for context, this is messed up.
.description = R"(
How to compute the hash of the input.
One of:
- `nar` (the default):
Serialises the input as a
[Nix Archive](@docroot@/store/file-system-object/content-address.md#serial-nix-archive)
and passes that to the hash function.
- `flat`:
Assumes that the input is a single file and
[directly passes](@docroot@/store/file-system-object/content-address.md#serial-flat)
it to the hash function.
)",
.labels = {"file-ingestion-method"},
.handler = {[method](std::string s) {
*method = parseFileIngestionMethod(s);
}},
};
}
Args::Flag contentAddressMethod(ContentAddressMethod * method)
{
return Args::Flag {
.longName = "mode",
// FIXME indentation carefully made for context, this is messed up.
.description = R"(
How to compute the content-address of the store object.
One of:
- [`nar`](@docroot@/store/store-object/content-address.md#method-nix-archive)
(the default):
Serialises the input as a
[Nix Archive](@docroot@/store/file-system-object/content-address.md#serial-nix-archive)
and passes that to the hash function.
- [`flat`](@docroot@/store/store-object/content-address.md#method-flat):
Assumes that the input is a single file and
[directly passes](@docroot@/store/file-system-object/content-address.md#serial-flat)
it to the hash function.
- [`text`](@docroot@/store/store-object/content-address.md#method-text):
Like `flat`, but used for
[derivations](@docroot@/glossary.md#store-derivation) serialized in store object and
[`builtins.toFile`](@docroot@/language/builtins.html#builtins-toFile).
For advanced use-cases only;
for regular usage prefer `nar` and `flat`.
)",
.labels = {"content-address-method"},
.handler = {[method](std::string s) {
*method = ContentAddressMethod::parse(s);
}},
};
}
}

View file

@ -0,0 +1,21 @@
#include "args.hh"
#include "content-address.hh"
namespace nix::flag {
Args::Flag hashAlgo(std::string && longName, HashAlgorithm * ha);
static inline Args::Flag hashAlgo(HashAlgorithm * ha)
{
return hashAlgo("hash-algo", ha);
}
Args::Flag hashAlgoOpt(std::string && longName, std::optional<HashAlgorithm> * oha);
Args::Flag hashFormatWithDefault(std::string && longName, HashFormat * hf);
Args::Flag hashFormatOpt(std::string && longName, std::optional<HashFormat> * ohf);
static inline Args::Flag hashAlgoOpt(std::optional<HashAlgorithm> * oha)
{
return hashAlgoOpt("hash-algo", oha);
}
Args::Flag fileIngestionMethod(FileIngestionMethod * method);
Args::Flag contentAddressMethod(ContentAddressMethod * method);
}

View file

@ -0,0 +1,50 @@
#include "network-proxy.hh"
#include <algorithm>
#include "environment-variables.hh"
namespace nix {
static const StringSet lowercaseVariables{"http_proxy", "https_proxy", "ftp_proxy", "all_proxy", "no_proxy"};
static StringSet getAllVariables()
{
StringSet variables = lowercaseVariables;
for (const auto & variable : lowercaseVariables) {
std::string upperVariable;
std::transform(
variable.begin(), variable.end(), upperVariable.begin(), [](unsigned char c) { return std::toupper(c); });
variables.insert(std::move(upperVariable));
}
return variables;
}
const StringSet networkProxyVariables = getAllVariables();
static StringSet getExcludingNoProxyVariables()
{
static const StringSet excludeVariables{"no_proxy", "NO_PROXY"};
StringSet variables;
std::set_difference(
networkProxyVariables.begin(),
networkProxyVariables.end(),
excludeVariables.begin(),
excludeVariables.end(),
std::inserter(variables, variables.begin()));
return variables;
}
static const StringSet excludingNoProxyVariables = getExcludingNoProxyVariables();
bool haveNetworkProxyConnection()
{
for (const auto & variable : excludingNoProxyVariables) {
if (getEnv(variable).has_value()) {
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,22 @@
#pragma once
///@file
#include "types.hh"
namespace nix {
/**
* Environment variables relating to network proxying. These are used by
* a few misc commands.
*
* See the Environment section of https://curl.se/docs/manpage.html for details.
*/
extern const StringSet networkProxyVariables;
/**
* Heuristically check if there is a proxy connection by checking for defined
* proxy variables.
*/
bool haveNetworkProxyConnection();
}

View file

@ -6,4 +6,4 @@ Name: Nix
Description: Nix Package Manager
Version: @PACKAGE_VERSION@
Libs: -L${libdir} -lnixcmd
Cflags: -I${includedir}/nix -std=c++17
Cflags: -I${includedir}/nix -std=c++2a

87
src/libcmd/package.nix Normal file
View file

@ -0,0 +1,87 @@
{ lib
, stdenv
, mkMesonLibrary
, nix-util
, nix-store
, nix-fetchers
, nix-expr
, nix-flake
, nix-main
, editline
, readline
, lowdown
, nlohmann_json
# Configuration Options
, version
# Whether to enable Markdown rendering in the Nix binary.
, enableMarkdown ? !stdenv.hostPlatform.isWindows
# Which interactive line editor library to use for Nix's repl.
#
# Currently supported choices are:
#
# - editline (default)
# - readline
, readlineFlavor ? if stdenv.hostPlatform.isWindows then "readline" else "editline"
}:
let
inherit (lib) fileset;
in
mkMesonLibrary (finalAttrs: {
pname = "nix-cmd";
inherit version;
workDir = ./.;
fileset = fileset.unions [
../../build-utils-meson
./build-utils-meson
../../.version
./.version
./meson.build
./meson.options
(fileset.fileFilter (file: file.hasExt "cc") ./.)
(fileset.fileFilter (file: file.hasExt "hh") ./.)
];
buildInputs = [
({ inherit editline readline; }.${readlineFlavor})
] ++ lib.optional enableMarkdown lowdown;
propagatedBuildInputs = [
nix-util
nix-store
nix-fetchers
nix-expr
nix-flake
nix-main
nlohmann_json
];
preConfigure =
# "Inline" .version so it's not a symlink, and includes the suffix.
# Do the meson utils, without modification.
''
chmod u+w ./.version
echo ${version} > ../../.version
'';
mesonFlags = [
(lib.mesonEnable "markdown" enableMarkdown)
(lib.mesonOption "readline-flavor" readlineFlavor)
];
env = lib.optionalAttrs (stdenv.isLinux && !(stdenv.hostPlatform.isStatic && stdenv.system == "aarch64-linux")) {
LDFLAGS = "-fuse-ld=gold";
};
meta = {
platforms = lib.platforms.unix ++ lib.platforms.windows;
};
})

View file

@ -0,0 +1,206 @@
#include <cstdio>
#ifdef USE_READLINE
#include <readline/history.h>
#include <readline/readline.h>
#else
// editline < 1.15.2 don't wrap their API for C++ usage
// (added in https://github.com/troglobit/editline/commit/91398ceb3427b730995357e9d120539fb9bb7461).
// This results in linker errors due to to name-mangling of editline C symbols.
// For compatibility with these versions, we wrap the API here
// (wrapping multiple times on newer versions is no problem).
extern "C" {
#include <editline.h>
}
#endif
#include "signals.hh"
#include "finally.hh"
#include "repl-interacter.hh"
#include "file-system.hh"
#include "repl.hh"
#include "environment-variables.hh"
namespace nix {
namespace {
// Used to communicate to NixRepl::getLine whether a signal occurred in ::readline.
volatile sig_atomic_t g_signal_received = 0;
void sigintHandler(int signo)
{
g_signal_received = signo;
}
};
static detail::ReplCompleterMixin * curRepl; // ugly
#ifndef USE_READLINE
static char * completionCallback(char * s, int * match)
{
auto possible = curRepl->completePrefix(s);
if (possible.size() == 1) {
*match = 1;
auto * res = strdup(possible.begin()->c_str() + strlen(s));
if (!res)
throw Error("allocation failure");
return res;
} else if (possible.size() > 1) {
auto checkAllHaveSameAt = [&](size_t pos) {
auto & first = *possible.begin();
for (auto & p : possible) {
if (p.size() <= pos || p[pos] != first[pos])
return false;
}
return true;
};
size_t start = strlen(s);
size_t len = 0;
while (checkAllHaveSameAt(start + len))
++len;
if (len > 0) {
*match = 1;
auto * res = strdup(std::string(*possible.begin(), start, len).c_str());
if (!res)
throw Error("allocation failure");
return res;
}
}
*match = 0;
return nullptr;
}
static int listPossibleCallback(char * s, char *** avp)
{
auto possible = curRepl->completePrefix(s);
if (possible.size() > (std::numeric_limits<int>::max() / sizeof(char *)))
throw Error("too many completions");
int ac = 0;
char ** vp = nullptr;
auto check = [&](auto * p) {
if (!p) {
if (vp) {
while (--ac >= 0)
free(vp[ac]);
free(vp);
}
throw Error("allocation failure");
}
return p;
};
vp = check((char **) malloc(possible.size() * sizeof(char *)));
for (auto & p : possible)
vp[ac++] = check(strdup(p.c_str()));
*avp = vp;
return ac;
}
#endif
ReadlineLikeInteracter::Guard ReadlineLikeInteracter::init(detail::ReplCompleterMixin * repl)
{
// Allow nix-repl specific settings in .inputrc
rl_readline_name = "nix-repl";
try {
createDirs(dirOf(historyFile));
} catch (SystemError & e) {
logWarning(e.info());
}
#ifndef USE_READLINE
el_hist_size = 1000;
#endif
read_history(historyFile.c_str());
auto oldRepl = curRepl;
curRepl = repl;
Guard restoreRepl([oldRepl] { curRepl = oldRepl; });
#ifndef USE_READLINE
rl_set_complete_func(completionCallback);
rl_set_list_possib_func(listPossibleCallback);
#endif
return restoreRepl;
}
static constexpr const char * promptForType(ReplPromptType promptType)
{
switch (promptType) {
case ReplPromptType::ReplPrompt:
return "nix-repl> ";
case ReplPromptType::ContinuationPrompt:
return " ";
}
assert(false);
}
bool ReadlineLikeInteracter::getLine(std::string & input, ReplPromptType promptType)
{
#ifndef _WIN32 // TODO use more signals.hh for this
struct sigaction act, old;
sigset_t savedSignalMask, set;
auto setupSignals = [&]() {
act.sa_handler = sigintHandler;
sigfillset(&act.sa_mask);
act.sa_flags = 0;
if (sigaction(SIGINT, &act, &old))
throw SysError("installing handler for SIGINT");
sigemptyset(&set);
sigaddset(&set, SIGINT);
if (sigprocmask(SIG_UNBLOCK, &set, &savedSignalMask))
throw SysError("unblocking SIGINT");
};
auto restoreSignals = [&]() {
if (sigprocmask(SIG_SETMASK, &savedSignalMask, nullptr))
throw SysError("restoring signals");
if (sigaction(SIGINT, &old, 0))
throw SysError("restoring handler for SIGINT");
};
setupSignals();
#endif
char * s = readline(promptForType(promptType));
Finally doFree([&]() { free(s); });
#ifndef _WIN32 // TODO use more signals.hh for this
restoreSignals();
#endif
if (g_signal_received) {
g_signal_received = 0;
input.clear();
return true;
}
// editline doesn't echo the input to the output when non-interactive, unlike readline
// this results in a different behavior when running tests. The echoing is
// quite useful for reading the test output, so we add it here.
if (auto e = getEnv("_NIX_TEST_REPL_ECHO"); s && e && *e == "1")
{
#ifndef USE_READLINE
// This is probably not right for multi-line input, but we don't use that
// in the characterisation tests, so it's fine.
std::cout << promptForType(promptType) << s << std::endl;
#endif
}
if (!s)
return false;
input += s;
input += '\n';
return true;
}
ReadlineLikeInteracter::~ReadlineLikeInteracter()
{
write_history(historyFile.c_str());
}
};

View file

@ -0,0 +1,48 @@
#pragma once
/// @file
#include "finally.hh"
#include "types.hh"
#include <functional>
#include <string>
namespace nix {
namespace detail {
/** Provides the completion hooks for the repl, without exposing its complete
* internals. */
struct ReplCompleterMixin {
virtual StringSet completePrefix(const std::string & prefix) = 0;
};
};
enum class ReplPromptType {
ReplPrompt,
ContinuationPrompt,
};
class ReplInteracter
{
public:
using Guard = Finally<std::function<void()>>;
virtual Guard init(detail::ReplCompleterMixin * repl) = 0;
/** Returns a boolean of whether the interacter got EOF */
virtual bool getLine(std::string & input, ReplPromptType promptType) = 0;
virtual ~ReplInteracter(){};
};
class ReadlineLikeInteracter : public virtual ReplInteracter
{
std::string historyFile;
public:
ReadlineLikeInteracter(std::string historyFile)
: historyFile(historyFile)
{
}
virtual Guard init(detail::ReplCompleterMixin * repl) override;
virtual bool getLine(std::string & input, ReplPromptType promptType) override;
virtual ~ReadlineLikeInteracter() override;
};
};

File diff suppressed because it is too large Load diff

45
src/libcmd/repl.hh Normal file
View file

@ -0,0 +1,45 @@
#pragma once
///@file
#include "eval.hh"
namespace nix {
struct AbstractNixRepl
{
ref<EvalState> state;
Bindings * autoArgs;
AbstractNixRepl(ref<EvalState> state)
: state(state)
{ }
virtual ~AbstractNixRepl()
{ }
typedef std::vector<std::pair<Value*,std::string>> AnnotatedValues;
using RunNix = void(Path program, const Strings & args, const std::optional<std::string> & input);
/**
* @param runNix Function to run the nix CLI to support various
* `:<something>` commands. Optional; if not provided,
* everything else will still work fine, but those commands won't.
*/
static std::unique_ptr<AbstractNixRepl> create(
const LookupPath & lookupPath,
nix::ref<Store> store,
ref<EvalState> state,
std::function<AnnotatedValues()> getValues,
RunNix * runNix = nullptr);
static ReplExitStatus runSimple(
ref<EvalState> evalState,
const ValMap & extraEnv);
virtual void initEnv() = 0;
virtual ReplExitStatus mainLoop() = 0;
};
}

1
src/libexpr-c/.version Symbolic link
View file

@ -0,0 +1 @@
../../.version

View file

@ -0,0 +1 @@
../../build-utils-meson

25
src/libexpr-c/local.mk Normal file
View file

@ -0,0 +1,25 @@
libraries += libexprc
libexprc_NAME = libnixexprc
libexprc_DIR := $(d)
libexprc_SOURCES := \
$(wildcard $(d)/*.cc) \
# Not just for this library itself, but also for downstream libraries using this library
INCLUDE_libexprc := -I $(d)
libexprc_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libutilc) \
$(INCLUDE_libfetchers) \
$(INCLUDE_libstore) $(INCLUDE_libstorec) \
$(INCLUDE_libexpr) $(INCLUDE_libexprc)
libexprc_LIBS = libutil libutilc libstore libstorec libfetchers libexpr
libexprc_LDFLAGS += $(THREAD_LDFLAGS)
$(eval $(call install-file-in, $(d)/nix-expr-c.pc, $(libdir)/pkgconfig, 0644))
libexprc_FORCE_INSTALL := 1

94
src/libexpr-c/meson.build Normal file
View file

@ -0,0 +1,94 @@
project('nix-expr-c', 'cpp',
version : files('.version'),
default_options : [
'cpp_std=c++2a',
# TODO(Qyriad): increase the warning level
'warning_level=1',
'debug=true',
'optimization=2',
'errorlogs=true', # Please print logs for tests that fail
],
meson_version : '>= 1.1',
license : 'LGPL-2.1-or-later',
)
cxx = meson.get_compiler('cpp')
subdir('build-utils-meson/deps-lists')
configdata = configuration_data()
deps_private_maybe_subproject = [
dependency('nix-util'),
dependency('nix-store'),
dependency('nix-expr'),
]
deps_public_maybe_subproject = [
dependency('nix-util-c'),
dependency('nix-store-c'),
]
subdir('build-utils-meson/subprojects')
subdir('build-utils-meson/threads')
# TODO rename, because it will conflict with downstream projects
configdata.set_quoted('PACKAGE_VERSION', meson.project_version())
config_h = configure_file(
configuration : configdata,
output : 'config-expr.h',
)
add_project_arguments(
# TODO(Qyriad): Yes this is how the autoconf+Make system did it.
# It would be nice for our headers to be idempotent instead.
# From C++ libraries, only for internals
'-include', 'config-util.hh',
'-include', 'config-store.hh',
'-include', 'config-expr.hh',
# From C libraries, for our public, installed headers too
'-include', 'config-util.h',
'-include', 'config-store.h',
'-include', 'config-expr.h',
language : 'cpp',
)
subdir('build-utils-meson/diagnostics')
sources = files(
'nix_api_expr.cc',
'nix_api_external.cc',
'nix_api_value.cc',
)
include_dirs = [include_directories('.')]
headers = [config_h] + files(
'nix_api_expr.h',
'nix_api_external.h',
'nix_api_value.h',
)
# TODO move this header to libexpr, maybe don't use it in tests?
headers += files('nix_api_expr_internal.h')
subdir('build-utils-meson/export-all-symbols')
subdir('build-utils-meson/windows-version')
this_library = library(
'nixexprc',
sources,
dependencies : deps_public + deps_private + deps_other,
include_directories : include_dirs,
link_args: linker_export_flags,
prelink : true, # For C++ static initializers
install : true,
)
install_headers(headers, subdir : 'nix', preserve_path : true)
libraries_private = []
subdir('build-utils-meson/export')

View file

@ -0,0 +1,10 @@
prefix=@prefix@
libdir=@libdir@
includedir=@includedir@
Name: Nix
Description: Nix Language Evaluator - C API
Version: @PACKAGE_VERSION@
Requires: nix-store-c
Libs: -L${libdir} -lnixexprc
Cflags: -I${includedir}/nix

View file

@ -0,0 +1,211 @@
#include <cstring>
#include <stdexcept>
#include <string>
#include "eval.hh"
#include "eval-gc.hh"
#include "globals.hh"
#include "eval-settings.hh"
#include "nix_api_expr.h"
#include "nix_api_expr_internal.h"
#include "nix_api_store.h"
#include "nix_api_store_internal.h"
#include "nix_api_util.h"
#include "nix_api_util_internal.h"
#if HAVE_BOEHMGC
# include <mutex>
#endif
nix_err nix_libexpr_init(nix_c_context * context)
{
if (context)
context->last_err_code = NIX_OK;
{
auto ret = nix_libutil_init(context);
if (ret != NIX_OK)
return ret;
}
{
auto ret = nix_libstore_init(context);
if (ret != NIX_OK)
return ret;
}
try {
nix::initGC();
}
NIXC_CATCH_ERRS
}
nix_err nix_expr_eval_from_string(
nix_c_context * context, EvalState * state, const char * expr, const char * path, nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
nix::Expr * parsedExpr = state->state.parseExprFromString(expr, state->state.rootPath(nix::CanonPath(path)));
state->state.eval(parsedExpr, value->value);
state->state.forceValue(value->value, nix::noPos);
}
NIXC_CATCH_ERRS
}
nix_err nix_value_call(nix_c_context * context, EvalState * state, Value * fn, nix_value * arg, nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
state->state.callFunction(fn->value, arg->value, value->value, nix::noPos);
state->state.forceValue(value->value, nix::noPos);
}
NIXC_CATCH_ERRS
}
nix_err nix_value_call_multi(nix_c_context * context, EvalState * state, nix_value * fn, size_t nargs, nix_value ** args, nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
state->state.callFunction(fn->value, nargs, (nix::Value * *)args, value->value, nix::noPos);
state->state.forceValue(value->value, nix::noPos);
}
NIXC_CATCH_ERRS
}
nix_err nix_value_force(nix_c_context * context, EvalState * state, nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
state->state.forceValue(value->value, nix::noPos);
}
NIXC_CATCH_ERRS
}
nix_err nix_value_force_deep(nix_c_context * context, EvalState * state, nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
state->state.forceValueDeep(value->value);
}
NIXC_CATCH_ERRS
}
EvalState * nix_state_create(nix_c_context * context, const char ** lookupPath_c, Store * store)
{
if (context)
context->last_err_code = NIX_OK;
try {
nix::Strings lookupPath;
if (lookupPath_c != nullptr)
for (size_t i = 0; lookupPath_c[i] != nullptr; i++)
lookupPath.push_back(lookupPath_c[i]);
void * p = ::operator new(
sizeof(EvalState),
static_cast<std::align_val_t>(alignof(EvalState)));
auto * p2 = static_cast<EvalState *>(p);
new (p) EvalState {
.fetchSettings = nix::fetchers::Settings{},
.settings = nix::EvalSettings{
nix::settings.readOnlyMode,
},
.state = nix::EvalState(
nix::LookupPath::parse(lookupPath),
store->ptr,
p2->fetchSettings,
p2->settings),
};
loadConfFile(p2->settings);
return p2;
}
NIXC_CATCH_ERRS_NULL
}
void nix_state_free(EvalState * state)
{
delete state;
}
#if HAVE_BOEHMGC
std::unordered_map<
const void *,
unsigned int,
std::hash<const void *>,
std::equal_to<const void *>,
traceable_allocator<std::pair<const void * const, unsigned int>>>
nix_refcounts;
std::mutex nix_refcount_lock;
nix_err nix_gc_incref(nix_c_context * context, const void * p)
{
if (context)
context->last_err_code = NIX_OK;
try {
std::scoped_lock lock(nix_refcount_lock);
auto f = nix_refcounts.find(p);
if (f != nix_refcounts.end()) {
f->second++;
} else {
nix_refcounts[p] = 1;
}
}
NIXC_CATCH_ERRS
}
nix_err nix_gc_decref(nix_c_context * context, const void * p)
{
if (context)
context->last_err_code = NIX_OK;
try {
std::scoped_lock lock(nix_refcount_lock);
auto f = nix_refcounts.find(p);
if (f != nix_refcounts.end()) {
if (--f->second == 0)
nix_refcounts.erase(f);
} else
throw std::runtime_error("nix_gc_decref: object was not referenced");
}
NIXC_CATCH_ERRS
}
void nix_gc_now()
{
GC_gcollect();
}
#else
nix_err nix_gc_incref(nix_c_context * context, const void *)
{
if (context)
context->last_err_code = NIX_OK;
return NIX_OK;
}
nix_err nix_gc_decref(nix_c_context * context, const void *)
{
if (context)
context->last_err_code = NIX_OK;
return NIX_OK;
}
void nix_gc_now() {}
#endif
nix_err nix_value_incref(nix_c_context * context, nix_value *x)
{
return nix_gc_incref(context, (const void *) x);
}
nix_err nix_value_decref(nix_c_context * context, nix_value *x)
{
return nix_gc_decref(context, (const void *) x);
}
void nix_gc_register_finalizer(void * obj, void * cd, void (*finalizer)(void * obj, void * cd))
{
#if HAVE_BOEHMGC
GC_REGISTER_FINALIZER(obj, finalizer, cd, 0, 0);
#endif
}

View file

@ -0,0 +1,257 @@
#ifndef NIX_API_EXPR_H
#define NIX_API_EXPR_H
/** @defgroup libexpr libexpr
* @brief Bindings to the Nix language evaluator
*
* See *[Embedding the Nix Evaluator](@ref nix_evaluator_example)* for an example.
* @{
*/
/** @file
* @brief Main entry for the libexpr C bindings
*/
#include "nix_api_store.h"
#include "nix_api_util.h"
#include <stddef.h>
#ifndef __has_c_attribute
# define __has_c_attribute(x) 0
#endif
#if __has_c_attribute(deprecated)
# define NIX_DEPRECATED(msg) [[deprecated(msg)]]
#else
# define NIX_DEPRECATED(msg)
#endif
#ifdef __cplusplus
extern "C" {
#endif
// cffi start
// Type definitions
/**
* @brief Represents a state of the Nix language evaluator.
*
* Multiple states can be created for multi-threaded
* operation.
* @struct EvalState
* @see nix_state_create
*/
typedef struct EvalState EvalState; // nix::EvalState
/** @brief A Nix language value, or thunk that may evaluate to a value.
*
* Values are the primary objects manipulated in the Nix language.
* They are considered to be immutable from a user's perspective, but the process of evaluating a value changes its
* ValueType if it was a thunk. After a value has been evaluated, its ValueType does not change.
*
* Evaluation in this context refers to the process of evaluating a single value object, also called "forcing" the
* value; see `nix_value_force`.
*
* The evaluator manages its own memory, but your use of the C API must follow the reference counting rules.
*
* @see value_manip
* @see nix_value_incref, nix_value_decref
*/
typedef struct nix_value nix_value;
NIX_DEPRECATED("use nix_value instead") typedef nix_value Value;
// Function prototypes
/**
* @brief Initialize the Nix language evaluator.
*
* This function must be called at least once,
* at some point before constructing a EvalState for the first time.
* This function can be called multiple times, and is idempotent.
*
* @param[out] context Optional, stores error information
* @return NIX_OK if the initialization was successful, an error code otherwise.
*/
nix_err nix_libexpr_init(nix_c_context * context);
/**
* @brief Parses and evaluates a Nix expression from a string.
*
* @param[out] context Optional, stores error information
* @param[in] state The state of the evaluation.
* @param[in] expr The Nix expression to parse.
* @param[in] path The file path to associate with the expression.
* This is required for expressions that contain relative paths (such as `./.`) that are resolved relative to the given
* directory.
* @param[out] value The result of the evaluation. You must allocate this
* yourself.
* @return NIX_OK if the evaluation was successful, an error code otherwise.
*/
nix_err nix_expr_eval_from_string(
nix_c_context * context, EvalState * state, const char * expr, const char * path, nix_value * value);
/**
* @brief Calls a Nix function with an argument.
*
* @param[out] context Optional, stores error information
* @param[in] state The state of the evaluation.
* @param[in] fn The Nix function to call.
* @param[in] arg The argument to pass to the function.
* @param[out] value The result of the function call.
* @return NIX_OK if the function call was successful, an error code otherwise.
* @see nix_init_apply() for a similar function that does not performs the call immediately, but stores it as a thunk.
* Note the different argument order.
*/
nix_err nix_value_call(nix_c_context * context, EvalState * state, nix_value * fn, nix_value * arg, nix_value * value);
/**
* @brief Calls a Nix function with multiple arguments.
*
* Technically these are functions that return functions. It is common for Nix
* functions to be curried, so this function is useful for calling them.
*
* @param[out] context Optional, stores error information
* @param[in] state The state of the evaluation.
* @param[in] fn The Nix function to call.
* @param[in] nargs The number of arguments.
* @param[in] args The arguments to pass to the function.
* @param[out] value The result of the function call.
*
* @see nix_value_call For the single argument primitive.
* @see NIX_VALUE_CALL For a macro that wraps this function for convenience.
*/
nix_err nix_value_call_multi(
nix_c_context * context, EvalState * state, nix_value * fn, size_t nargs, nix_value ** args, nix_value * value);
/**
* @brief Calls a Nix function with multiple arguments.
*
* Technically these are functions that return functions. It is common for Nix
* functions to be curried, so this function is useful for calling them.
*
* @param[out] context Optional, stores error information
* @param[in] state The state of the evaluation.
* @param[out] value The result of the function call.
* @param[in] fn The Nix function to call.
* @param[in] args The arguments to pass to the function.
*
* @see nix_value_call_multi
*/
#define NIX_VALUE_CALL(context, state, value, fn, ...) \
do { \
nix_value * args_array[] = {__VA_ARGS__}; \
size_t nargs = sizeof(args_array) / sizeof(args_array[0]); \
nix_value_call_multi(context, state, fn, nargs, args_array, value); \
} while (0)
/**
* @brief Forces the evaluation of a Nix value.
*
* The Nix interpreter is lazy, and not-yet-evaluated values can be
* of type NIX_TYPE_THUNK instead of their actual value.
*
* This function mutates such a `nix_value`, so that, if successful, it has its final type.
*
* @param[out] context Optional, stores error information
* @param[in] state The state of the evaluation.
* @param[in,out] value The Nix value to force.
* @post value is not of type NIX_TYPE_THUNK
* @return NIX_OK if the force operation was successful, an error code
* otherwise.
*/
nix_err nix_value_force(nix_c_context * context, EvalState * state, nix_value * value);
/**
* @brief Forces the deep evaluation of a Nix value.
*
* Recursively calls nix_value_force
*
* @see nix_value_force
* @warning Calling this function on a recursive data structure will cause a
* stack overflow.
* @param[out] context Optional, stores error information
* @param[in] state The state of the evaluation.
* @param[in,out] value The Nix value to force.
* @return NIX_OK if the deep force operation was successful, an error code
* otherwise.
*/
nix_err nix_value_force_deep(nix_c_context * context, EvalState * state, nix_value * value);
/**
* @brief Create a new Nix language evaluator state.
*
* @param[out] context Optional, stores error information
* @param[in] lookupPath Null-terminated array of strings corresponding to entries in NIX_PATH.
* @param[in] store The Nix store to use.
* @return A new Nix state or NULL on failure.
*/
EvalState * nix_state_create(nix_c_context * context, const char ** lookupPath, Store * store);
/**
* @brief Frees a Nix state.
*
* Does not fail.
*
* @param[in] state The state to free.
*/
void nix_state_free(EvalState * state);
/** @addtogroup GC
* @brief Reference counting and garbage collector operations
*
* The Nix language evaluator uses a garbage collector. To ease C interop, we implement
* a reference counting scheme, where objects will be deallocated
* when there are no references from the Nix side, and the reference count kept
* by the C API reaches `0`.
*
* Functions returning a garbage-collected object will automatically increase
* the refcount for you. You should make sure to call `nix_gc_decref` when
* you're done with a value returned by the evaluator.
* @{
*/
// TODO: Deprecate nix_gc_incref in favor of the type-specific reference counting functions?
// e.g. nix_value_incref.
// It gives implementors more flexibility, and adds safety, so that generated
// bindings can be used without fighting the host type system (where applicable).
/**
* @brief Increment the garbage collector reference counter for the given object.
*
* The Nix language evaluator C API keeps track of alive objects by reference counting.
* When you're done with a refcounted pointer, call nix_gc_decref().
*
* @param[out] context Optional, stores error information
* @param[in] object The object to keep alive
*/
nix_err nix_gc_incref(nix_c_context * context, const void * object);
/**
* @brief Decrement the garbage collector reference counter for the given object
*
* @param[out] context Optional, stores error information
* @param[in] object The object to stop referencing
*/
nix_err nix_gc_decref(nix_c_context * context, const void * object);
/**
* @brief Trigger the garbage collector manually
*
* You should not need to do this, but it can be useful for debugging.
*/
void nix_gc_now();
/**
* @brief Register a callback that gets called when the object is garbage
* collected.
* @note Objects can only have a single finalizer. This function overwrites existing values
* silently.
* @param[in] obj the object to watch
* @param[in] cd the data to pass to the finalizer
* @param[in] finalizer the callback function, called with obj and cd
*/
void nix_gc_register_finalizer(void * obj, void * cd, void (*finalizer)(void * obj, void * cd));
/** @} */
// cffi end
#ifdef __cplusplus
}
#endif
/** @} */
#endif // NIX_API_EXPR_H

View file

@ -0,0 +1,53 @@
#ifndef NIX_API_EXPR_INTERNAL_H
#define NIX_API_EXPR_INTERNAL_H
#include "fetch-settings.hh"
#include "eval.hh"
#include "eval-settings.hh"
#include "attr-set.hh"
#include "nix_api_value.h"
struct EvalState
{
nix::fetchers::Settings fetchSettings;
nix::EvalSettings settings;
nix::EvalState state;
};
struct BindingsBuilder
{
nix::BindingsBuilder builder;
};
struct ListBuilder
{
nix::ListBuilder builder;
};
struct nix_value
{
nix::Value value;
};
struct nix_string_return
{
std::string str;
};
struct nix_printer
{
std::ostream & s;
};
struct nix_string_context
{
nix::NixStringContext & ctx;
};
struct nix_realised_string
{
std::string str;
std::vector<StorePath> storePaths;
};
#endif // NIX_API_EXPR_INTERNAL_H

View file

@ -0,0 +1,192 @@
#include "attr-set.hh"
#include "config.hh"
#include "eval.hh"
#include "globals.hh"
#include "value.hh"
#include "nix_api_expr.h"
#include "nix_api_expr_internal.h"
#include "nix_api_external.h"
#include "nix_api_util.h"
#include "nix_api_util_internal.h"
#include "nix_api_value.h"
#include "value/context.hh"
#include <nlohmann/json.hpp>
void nix_set_string_return(nix_string_return * str, const char * c)
{
str->str = c;
}
nix_err nix_external_print(nix_c_context * context, nix_printer * printer, const char * c)
{
if (context)
context->last_err_code = NIX_OK;
try {
printer->s << c;
}
NIXC_CATCH_ERRS
}
nix_err nix_external_add_string_context(nix_c_context * context, nix_string_context * ctx, const char * c)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto r = nix::NixStringContextElem::parse(c);
ctx->ctx.insert(r);
}
NIXC_CATCH_ERRS
}
class NixCExternalValue : public nix::ExternalValueBase
{
NixCExternalValueDesc & desc;
void * v;
public:
NixCExternalValue(NixCExternalValueDesc & desc, void * v)
: desc(desc)
, v(v){};
void * get_ptr()
{
return v;
}
/**
* Print out the value
*/
virtual std::ostream & print(std::ostream & str) const override
{
nix_printer p{str};
desc.print(v, &p);
return str;
}
/**
* Return a simple string describing the type
*/
virtual std::string showType() const override
{
nix_string_return res;
desc.showType(v, &res);
return std::move(res.str);
}
/**
* Return a string to be used in builtins.typeOf
*/
virtual std::string typeOf() const override
{
nix_string_return res;
desc.typeOf(v, &res);
return std::move(res.str);
}
/**
* Coerce the value to a string.
*/
virtual std::string coerceToString(
nix::EvalState & state,
const nix::PosIdx & pos,
nix::NixStringContext & context,
bool copyMore,
bool copyToStore) const override
{
if (!desc.coerceToString) {
return nix::ExternalValueBase::coerceToString(state, pos, context, copyMore, copyToStore);
}
nix_string_context ctx{context};
nix_string_return res{""};
// todo: pos, errors
desc.coerceToString(v, &ctx, copyMore, copyToStore, &res);
if (res.str.empty()) {
return nix::ExternalValueBase::coerceToString(state, pos, context, copyMore, copyToStore);
}
return std::move(res.str);
}
/**
* Compare to another value of the same type.
*/
virtual bool operator==(const ExternalValueBase & b) const noexcept override
{
if (!desc.equal) {
return false;
}
auto r = dynamic_cast<const NixCExternalValue *>(&b);
if (!r)
return false;
return desc.equal(v, r->v);
}
/**
* Print the value as JSON.
*/
virtual nlohmann::json printValueAsJSON(
nix::EvalState & state, bool strict, nix::NixStringContext & context, bool copyToStore = true) const override
{
if (!desc.printValueAsJSON) {
return nix::ExternalValueBase::printValueAsJSON(state, strict, context, copyToStore);
}
nix_string_context ctx{context};
nix_string_return res{""};
desc.printValueAsJSON(v, (EvalState *) &state, strict, &ctx, copyToStore, &res);
if (res.str.empty()) {
return nix::ExternalValueBase::printValueAsJSON(state, strict, context, copyToStore);
}
return nlohmann::json::parse(res.str);
}
/**
* Print the value as XML.
*/
virtual void printValueAsXML(
nix::EvalState & state,
bool strict,
bool location,
nix::XMLWriter & doc,
nix::NixStringContext & context,
nix::PathSet & drvsSeen,
const nix::PosIdx pos) const override
{
if (!desc.printValueAsXML) {
return nix::ExternalValueBase::printValueAsXML(state, strict, location, doc, context, drvsSeen, pos);
}
nix_string_context ctx{context};
desc.printValueAsXML(
v, (EvalState *) &state, strict, location, &doc, &ctx, &drvsSeen,
*reinterpret_cast<const uint32_t *>(&pos));
}
virtual ~NixCExternalValue() override{};
};
ExternalValue * nix_create_external_value(nix_c_context * context, NixCExternalValueDesc * desc, void * v)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto ret = new
#if HAVE_BOEHMGC
(GC)
#endif
NixCExternalValue(*desc, v);
nix_gc_incref(nullptr, ret);
return (ExternalValue *) ret;
}
NIXC_CATCH_ERRS_NULL
}
void * nix_get_external_value_content(nix_c_context * context, ExternalValue * b)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto r = dynamic_cast<NixCExternalValue *>((nix::ExternalValueBase *) b);
if (r)
return r->get_ptr();
return nullptr;
}
NIXC_CATCH_ERRS_NULL
}

View file

@ -0,0 +1,196 @@
#ifndef NIX_API_EXTERNAL_H
#define NIX_API_EXTERNAL_H
/** @ingroup libexpr
* @addtogroup Externals
* @brief Deal with external values
* @{
*/
/** @file
* @brief libexpr C bindings dealing with external values
*/
#include "nix_api_expr.h"
#include "nix_api_util.h"
#include "nix_api_value.h"
#include "stdbool.h"
#include "stddef.h"
#include "stdint.h"
#ifdef __cplusplus
extern "C" {
#endif
// cffi start
/**
* @brief Represents a string owned by the Nix language evaluator.
* @see nix_set_owned_string
*/
typedef struct nix_string_return nix_string_return;
/**
* @brief Wraps a stream that can output multiple string pieces.
*/
typedef struct nix_printer nix_printer;
/**
* @brief A list of string context items
*/
typedef struct nix_string_context nix_string_context;
/**
* @brief Sets the contents of a nix_string_return
*
* Copies the passed string.
* @param[out] str the nix_string_return to write to
* @param[in] c The string to copy
*/
void nix_set_string_return(nix_string_return * str, const char * c);
/**
* Print to the nix_printer
*
* @param[out] context Optional, stores error information
* @param[out] printer The nix_printer to print to
* @param[in] str The string to print
* @returns NIX_OK if everything worked
*/
nix_err nix_external_print(nix_c_context * context, nix_printer * printer, const char * str);
/**
* Add string context to the nix_string_context object
* @param[out] context Optional, stores error information
* @param[out] string_context The nix_string_context to add to
* @param[in] c The context string to add
* @returns NIX_OK if everything worked
*/
nix_err nix_external_add_string_context(nix_c_context * context, nix_string_context * string_context, const char * c);
/**
* @brief Definition for a class of external values
*
* Create and implement one of these, then pass it to nix_create_external_value
* Make sure to keep it alive while the external value lives.
*
* Optional functions can be set to NULL
*
* @see nix_create_external_value
*/
typedef struct NixCExternalValueDesc
{
/**
* @brief Called when printing the external value
*
* @param[in] self the void* passed to nix_create_external_value
* @param[out] printer The printer to print to, pass to nix_external_print
*/
void (*print)(void * self, nix_printer * printer);
/**
* @brief Called on :t
* @param[in] self the void* passed to nix_create_external_value
* @param[out] res the return value
*/
void (*showType)(void * self, nix_string_return * res);
/**
* @brief Called on `builtins.typeOf`
* @param self the void* passed to nix_create_external_value
* @param[out] res the return value
*/
void (*typeOf)(void * self, nix_string_return * res);
/**
* @brief Called on "${str}" and builtins.toString.
*
* The latter with coerceMore=true
* Optional, the default is to throw an error.
* @param[in] self the void* passed to nix_create_external_value
* @param[out] c writable string context for the resulting string
* @param[in] coerceMore boolean, try to coerce to strings in more cases
* instead of throwing an error
* @param[in] copyToStore boolean, whether to copy referenced paths to store
* or keep them as-is
* @param[out] res the return value. Not touching this, or setting it to the
* empty string, will make the conversion throw an error.
*/
void (*coerceToString)(
void * self, nix_string_context * c, int coerceMore, int copyToStore, nix_string_return * res);
/**
* @brief Try to compare two external values
*
* Optional, the default is always false.
* If the other object was not a Nix C external value, this comparison will
* also return false
* @param[in] self the void* passed to nix_create_external_value
* @param[in] other the void* passed to the other object's
* nix_create_external_value
* @returns true if the objects are deemed to be equal
*/
int (*equal)(void * self, void * other);
/**
* @brief Convert the external value to json
*
* Optional, the default is to throw an error
* @param[in] self the void* passed to nix_create_external_value
* @param[in] state The evaluator state
* @param[in] strict boolean Whether to force the value before printing
* @param[out] c writable string context for the resulting string
* @param[in] copyToStore whether to copy referenced paths to store or keep
* them as-is
* @param[out] res the return value. Gets parsed as JSON. Not touching this,
* or setting it to the empty string, will make the conversion throw an error.
*/
void (*printValueAsJSON)(
void * self, EvalState * state, bool strict, nix_string_context * c, bool copyToStore, nix_string_return * res);
/**
* @brief Convert the external value to XML
*
* Optional, the default is to throw an error
* @todo The mechanisms for this call are incomplete. There are no C
* bindings to work with XML, pathsets and positions.
* @param[in] self the void* passed to nix_create_external_value
* @param[in] state The evaluator state
* @param[in] strict boolean Whether to force the value before printing
* @param[in] location boolean Whether to include position information in the
* xml
* @param[out] doc XML document to output to
* @param[out] c writable string context for the resulting string
* @param[in,out] drvsSeen a path set to avoid duplicating derivations
* @param[in] pos The position of the call.
*/
void (*printValueAsXML)(
void * self,
EvalState * state,
int strict,
int location,
void * doc,
nix_string_context * c,
void * drvsSeen,
int pos);
} NixCExternalValueDesc;
/**
* @brief Create an external value, that can be given to nix_init_external
*
* Owned by the GC. Use nix_gc_decref when you're done with the pointer.
*
* @param[out] context Optional, stores error information
* @param[in] desc a NixCExternalValueDesc, you should keep this alive as long
* as the ExternalValue lives
* @param[in] v the value to store
* @returns external value, owned by the garbage collector
* @see nix_init_external
*/
ExternalValue * nix_create_external_value(nix_c_context * context, NixCExternalValueDesc * desc, void * v);
/**
* @brief Extract the pointer from a nix c external value.
* @param[out] context Optional, stores error information
* @param[in] b The external value
* @returns The pointer, or null if the external value was not from nix c.
* @see nix_get_external
*/
void * nix_get_external_value_content(nix_c_context * context, ExternalValue * b);
// cffi end
#ifdef __cplusplus
}
#endif
/** @} */
#endif // NIX_API_EXTERNAL_H

View file

@ -0,0 +1,657 @@
#include "attr-set.hh"
#include "config.hh"
#include "eval.hh"
#include "globals.hh"
#include "path.hh"
#include "primops.hh"
#include "value.hh"
#include "nix_api_expr.h"
#include "nix_api_expr_internal.h"
#include "nix_api_util.h"
#include "nix_api_util_internal.h"
#include "nix_api_store_internal.h"
#include "nix_api_value.h"
#include "value/context.hh"
// Internal helper functions to check [in] and [out] `Value *` parameters
static const nix::Value & check_value_not_null(const nix_value * value)
{
if (!value) {
throw std::runtime_error("nix_value is null");
}
return *((const nix::Value *) value);
}
static nix::Value & check_value_not_null(nix_value * value)
{
if (!value) {
throw std::runtime_error("nix_value is null");
}
return value->value;
}
static const nix::Value & check_value_in(const nix_value * value)
{
auto & v = check_value_not_null(value);
if (!v.isValid()) {
throw std::runtime_error("Uninitialized nix_value");
}
return v;
}
static nix::Value & check_value_in(nix_value * value)
{
auto & v = check_value_not_null(value);
if (!v.isValid()) {
throw std::runtime_error("Uninitialized nix_value");
}
return v;
}
static nix::Value & check_value_out(nix_value * value)
{
auto & v = check_value_not_null(value);
if (v.isValid()) {
throw std::runtime_error("nix_value already initialized. Variables are immutable");
}
return v;
}
static inline nix_value * as_nix_value_ptr(nix::Value * v)
{
return reinterpret_cast<nix_value *>(v);
}
/**
* Helper function to convert calls from nix into C API.
*
* Deals with errors and converts arguments from C++ into C types.
*/
static void nix_c_primop_wrapper(
PrimOpFun f, void * userdata, nix::EvalState & state, const nix::PosIdx pos, nix::Value ** args, nix::Value & v)
{
nix_c_context ctx;
// v currently has a thunk, but the C API initializers require an uninitialized value.
//
// We can't destroy the thunk, because that makes it impossible to retry,
// which is needed for tryEval and for evaluation drivers that evaluate more
// than one value (e.g. an attrset with two derivations, both of which
// reference v).
//
// Instead we create a temporary value, and then assign the result to v.
// This does not give the primop definition access to the thunk, but that's
// ok because we don't see a need for this yet (e.g. inspecting thunks,
// or maybe something to make blackholes work better; we don't know).
nix::Value vTmp;
f(userdata, &ctx, (EvalState *) &state, (nix_value **) args, (nix_value *) &vTmp);
if (ctx.last_err_code != NIX_OK) {
/* TODO: Throw different errors depending on the error code */
state.error<nix::EvalError>("Error from custom function: %s", *ctx.last_err).atPos(pos).debugThrow();
}
if (!vTmp.isValid()) {
state.error<nix::EvalError>("Implementation error in custom function: return value was not initialized")
.atPos(pos)
.debugThrow();
}
if (vTmp.type() == nix::nThunk) {
// We might allow this in the future if it makes sense for the evaluator
// e.g. implementing tail recursion by returning a thunk to the next
// "iteration". Until then, this is most likely a mistake or misunderstanding.
state.error<nix::EvalError>("Implementation error in custom function: return value must not be a thunk")
.atPos(pos)
.debugThrow();
}
v = vTmp;
}
PrimOp * nix_alloc_primop(
nix_c_context * context,
PrimOpFun fun,
int arity,
const char * name,
const char ** args,
const char * doc,
void * user_data)
{
if (context)
context->last_err_code = NIX_OK;
try {
using namespace std::placeholders;
auto p = new
#if HAVE_BOEHMGC
(GC)
#endif
nix::PrimOp{
.name = name,
.args = {},
.arity = (size_t) arity,
.doc = doc,
.fun = std::bind(nix_c_primop_wrapper, fun, user_data, _1, _2, _3, _4)};
if (args)
for (size_t i = 0; args[i]; i++)
p->args.emplace_back(*args);
nix_gc_incref(nullptr, p);
return (PrimOp *) p;
}
NIXC_CATCH_ERRS_NULL
}
nix_err nix_register_primop(nix_c_context * context, PrimOp * primOp)
{
if (context)
context->last_err_code = NIX_OK;
try {
nix::RegisterPrimOp r(std::move(*((nix::PrimOp *) primOp)));
}
NIXC_CATCH_ERRS
}
nix_value * nix_alloc_value(nix_c_context * context, EvalState * state)
{
if (context)
context->last_err_code = NIX_OK;
try {
nix_value * res = as_nix_value_ptr(state->state.allocValue());
nix_gc_incref(nullptr, res);
return res;
}
NIXC_CATCH_ERRS_NULL
}
ValueType nix_get_type(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
using namespace nix;
switch (v.type()) {
case nThunk:
return NIX_TYPE_THUNK;
case nInt:
return NIX_TYPE_INT;
case nFloat:
return NIX_TYPE_FLOAT;
case nBool:
return NIX_TYPE_BOOL;
case nString:
return NIX_TYPE_STRING;
case nPath:
return NIX_TYPE_PATH;
case nNull:
return NIX_TYPE_NULL;
case nAttrs:
return NIX_TYPE_ATTRS;
case nList:
return NIX_TYPE_LIST;
case nFunction:
return NIX_TYPE_FUNCTION;
case nExternal:
return NIX_TYPE_EXTERNAL;
}
return NIX_TYPE_NULL;
}
NIXC_CATCH_ERRS_RES(NIX_TYPE_NULL);
}
const char * nix_get_typename(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
auto s = nix::showType(v);
return strdup(s.c_str());
}
NIXC_CATCH_ERRS_NULL
}
bool nix_get_bool(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nBool);
return v.boolean();
}
NIXC_CATCH_ERRS_RES(false);
}
nix_err
nix_get_string(nix_c_context * context, const nix_value * value, nix_get_string_callback callback, void * user_data)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nString);
call_nix_get_string_callback(v.c_str(), callback, user_data);
}
NIXC_CATCH_ERRS
}
const char * nix_get_path_string(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nPath);
// NOTE (from @yorickvP)
// v._path.path should work but may not be how Eelco intended it.
// Long-term this function should be rewritten to copy some data into a
// user-allocated string.
// We could use v.path().to_string().c_str(), but I'm concerned this
// crashes. Looks like .path() allocates a CanonPath with a copy of the
// string, then it gets the underlying data from that.
return v.payload.path.path;
}
NIXC_CATCH_ERRS_NULL
}
unsigned int nix_get_list_size(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nList);
return v.listSize();
}
NIXC_CATCH_ERRS_RES(0);
}
unsigned int nix_get_attrs_size(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nAttrs);
return v.attrs()->size();
}
NIXC_CATCH_ERRS_RES(0);
}
double nix_get_float(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nFloat);
return v.fpoint();
}
NIXC_CATCH_ERRS_RES(0.0);
}
int64_t nix_get_int(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nInt);
return v.integer().value;
}
NIXC_CATCH_ERRS_RES(0);
}
ExternalValue * nix_get_external(nix_c_context * context, nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
assert(v.type() == nix::nExternal);
return (ExternalValue *) v.external();
}
NIXC_CATCH_ERRS_NULL;
}
nix_value * nix_get_list_byidx(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int ix)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nList);
auto * p = v.listElems()[ix];
nix_gc_incref(nullptr, p);
if (p != nullptr)
state->state.forceValue(*p, nix::noPos);
return as_nix_value_ptr(p);
}
NIXC_CATCH_ERRS_NULL
}
nix_value * nix_get_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nAttrs);
nix::Symbol s = state->state.symbols.create(name);
auto attr = v.attrs()->get(s);
if (attr) {
nix_gc_incref(nullptr, attr->value);
state->state.forceValue(*attr->value, nix::noPos);
return as_nix_value_ptr(attr->value);
}
nix_set_err_msg(context, NIX_ERR_KEY, "missing attribute");
return nullptr;
}
NIXC_CATCH_ERRS_NULL
}
bool nix_has_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nAttrs);
nix::Symbol s = state->state.symbols.create(name);
auto attr = v.attrs()->get(s);
if (attr)
return true;
return false;
}
NIXC_CATCH_ERRS_RES(false);
}
nix_value * nix_get_attr_byidx(
nix_c_context * context, const nix_value * value, EvalState * state, unsigned int i, const char ** name)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
const nix::Attr & a = (*v.attrs())[i];
*name = state->state.symbols[a.name].c_str();
nix_gc_incref(nullptr, a.value);
state->state.forceValue(*a.value, nix::noPos);
return as_nix_value_ptr(a.value);
}
NIXC_CATCH_ERRS_NULL
}
const char *
nix_get_attr_name_byidx(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int i)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
const nix::Attr & a = (*v.attrs())[i];
return state->state.symbols[a.name].c_str();
}
NIXC_CATCH_ERRS_NULL
}
nix_err nix_init_bool(nix_c_context * context, nix_value * value, bool b)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
v.mkBool(b);
}
NIXC_CATCH_ERRS
}
// todo string context
nix_err nix_init_string(nix_c_context * context, nix_value * value, const char * str)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
v.mkString(std::string_view(str));
}
NIXC_CATCH_ERRS
}
nix_err nix_init_path_string(nix_c_context * context, EvalState * s, nix_value * value, const char * str)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
v.mkPath(s->state.rootPath(nix::CanonPath(str)));
}
NIXC_CATCH_ERRS
}
nix_err nix_init_float(nix_c_context * context, nix_value * value, double d)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
v.mkFloat(d);
}
NIXC_CATCH_ERRS
}
nix_err nix_init_int(nix_c_context * context, nix_value * value, int64_t i)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
v.mkInt(i);
}
NIXC_CATCH_ERRS
}
nix_err nix_init_null(nix_c_context * context, nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
v.mkNull();
}
NIXC_CATCH_ERRS
}
nix_err nix_init_apply(nix_c_context * context, nix_value * value, nix_value * fn, nix_value * arg)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
auto & f = check_value_not_null(fn);
auto & a = check_value_not_null(arg);
v.mkApp(&f, &a);
}
NIXC_CATCH_ERRS
}
nix_err nix_init_external(nix_c_context * context, nix_value * value, ExternalValue * val)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
auto r = (nix::ExternalValueBase *) val;
v.mkExternal(r);
}
NIXC_CATCH_ERRS
}
ListBuilder * nix_make_list_builder(nix_c_context * context, EvalState * state, size_t capacity)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto builder = state->state.buildList(capacity);
return new
#if HAVE_BOEHMGC
(NoGC)
#endif
ListBuilder{std::move(builder)};
}
NIXC_CATCH_ERRS_NULL
}
nix_err
nix_list_builder_insert(nix_c_context * context, ListBuilder * list_builder, unsigned int index, nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & e = check_value_not_null(value);
list_builder->builder[index] = &e;
}
NIXC_CATCH_ERRS
}
void nix_list_builder_free(ListBuilder * list_builder)
{
#if HAVE_BOEHMGC
GC_FREE(list_builder);
#else
delete list_builder;
#endif
}
nix_err nix_make_list(nix_c_context * context, ListBuilder * list_builder, nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
v.mkList(list_builder->builder);
}
NIXC_CATCH_ERRS
}
nix_err nix_init_primop(nix_c_context * context, nix_value * value, PrimOp * p)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
v.mkPrimOp((nix::PrimOp *) p);
}
NIXC_CATCH_ERRS
}
nix_err nix_copy_value(nix_c_context * context, nix_value * value, const nix_value * source)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
auto & s = check_value_in(source);
v = s;
}
NIXC_CATCH_ERRS
}
nix_err nix_make_attrs(nix_c_context * context, nix_value * value, BindingsBuilder * b)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
v.mkAttrs(b->builder);
}
NIXC_CATCH_ERRS
}
BindingsBuilder * nix_make_bindings_builder(nix_c_context * context, EvalState * state, size_t capacity)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto bb = state->state.buildBindings(capacity);
return new
#if HAVE_BOEHMGC
(NoGC)
#endif
BindingsBuilder{std::move(bb)};
}
NIXC_CATCH_ERRS_NULL
}
nix_err nix_bindings_builder_insert(nix_c_context * context, BindingsBuilder * bb, const char * name, nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
nix::Symbol s = bb->builder.state.symbols.create(name);
bb->builder.insert(s, &v);
}
NIXC_CATCH_ERRS
}
void nix_bindings_builder_free(BindingsBuilder * bb)
{
#if HAVE_BOEHMGC
GC_FREE((nix::BindingsBuilder *) bb);
#else
delete (nix::BindingsBuilder *) bb;
#endif
}
nix_realised_string * nix_string_realise(nix_c_context * context, EvalState * state, nix_value * value, bool isIFD)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
nix::NixStringContext stringContext;
auto rawStr = state->state.coerceToString(nix::noPos, v, stringContext, "while realising a string").toOwned();
nix::StorePathSet storePaths;
auto rewrites = state->state.realiseContext(stringContext, &storePaths);
auto s = nix::rewriteStrings(rawStr, rewrites);
// Convert to the C API StorePath type and convert to vector for index-based access
std::vector<StorePath> vec;
for (auto & sp : storePaths) {
vec.push_back(StorePath{sp});
}
return new nix_realised_string{.str = s, .storePaths = vec};
}
NIXC_CATCH_ERRS_NULL
}
void nix_realised_string_free(nix_realised_string * s)
{
delete s;
}
size_t nix_realised_string_get_buffer_size(nix_realised_string * s)
{
return s->str.size();
}
const char * nix_realised_string_get_buffer_start(nix_realised_string * s)
{
return s->str.data();
}
size_t nix_realised_string_get_store_path_count(nix_realised_string * s)
{
return s->storePaths.size();
}
const StorePath * nix_realised_string_get_store_path(nix_realised_string * s, size_t i)
{
return &s->storePaths[i];
}

View file

@ -0,0 +1,537 @@
#ifndef NIX_API_VALUE_H
#define NIX_API_VALUE_H
/** @addtogroup libexpr
* @{
*/
/** @file
* @brief libexpr C bindings dealing with values
*/
#include "nix_api_util.h"
#include "nix_api_store.h"
#include "stdbool.h"
#include "stddef.h"
#include "stdint.h"
#ifdef __cplusplus
extern "C" {
#endif
// cffi start
// Type definitions
typedef enum {
NIX_TYPE_THUNK,
NIX_TYPE_INT,
NIX_TYPE_FLOAT,
NIX_TYPE_BOOL,
NIX_TYPE_STRING,
NIX_TYPE_PATH,
NIX_TYPE_NULL,
NIX_TYPE_ATTRS,
NIX_TYPE_LIST,
NIX_TYPE_FUNCTION,
NIX_TYPE_EXTERNAL
} ValueType;
// forward declarations
typedef struct nix_value nix_value;
typedef struct EvalState EvalState;
[[deprecated("use nix_value instead")]] typedef nix_value Value;
// type defs
/** @brief Stores an under-construction set of bindings
* @ingroup value_manip
*
* Do not reuse.
* @see nix_make_bindings_builder, nix_bindings_builder_free, nix_make_attrs
* @see nix_bindings_builder_insert
*/
typedef struct BindingsBuilder BindingsBuilder;
/** @brief Stores an under-construction list
* @ingroup value_manip
*
* Do not reuse.
* @see nix_make_list_builder, nix_list_builder_free, nix_make_list
* @see nix_list_builder_insert
*/
typedef struct ListBuilder ListBuilder;
/** @brief PrimOp function
* @ingroup primops
*
* Owned by the GC
* @see nix_alloc_primop, nix_init_primop
*/
typedef struct PrimOp PrimOp;
/** @brief External Value
* @ingroup Externals
*
* Owned by the GC
*/
typedef struct ExternalValue ExternalValue;
/** @brief String without placeholders, and realised store paths
*/
typedef struct nix_realised_string nix_realised_string;
/** @defgroup primops
* @brief Create your own primops
* @{
*/
/** @brief Function pointer for primops
*
* When you want to return an error, call nix_set_err_msg(context, NIX_ERR_UNKNOWN, "your error message here").
*
* @param[in] user_data Arbitrary data that was initially supplied to nix_alloc_primop
* @param[out] context Stores error information.
* @param[in] state Evaluator state
* @param[in] args list of arguments. Note that these can be thunks and should be forced using nix_value_force before
* use.
* @param[out] ret return value
* @see nix_alloc_primop, nix_init_primop
*/
typedef void (*PrimOpFun)(
void * user_data, nix_c_context * context, EvalState * state, nix_value ** args, nix_value * ret);
/** @brief Allocate a PrimOp
*
* Owned by the garbage collector.
* Use nix_gc_decref() when you're done with the returned PrimOp.
*
* @param[out] context Optional, stores error information
* @param[in] fun callback
* @param[in] arity expected number of function arguments
* @param[in] name function name
* @param[in] args array of argument names, NULL-terminated
* @param[in] doc optional, documentation for this primop
* @param[in] user_data optional, arbitrary data, passed to the callback when it's called
* @return primop, or null in case of errors
* @see nix_init_primop
*/
PrimOp * nix_alloc_primop(
nix_c_context * context,
PrimOpFun fun,
int arity,
const char * name,
const char ** args,
const char * doc,
void * user_data);
/** @brief add a primop to the `builtins` attribute set
*
* Only applies to States created after this call.
*
* Moves your PrimOp content into the global evaluator
* registry, meaning your input PrimOp pointer is no longer usable.
* You are free to remove your references to it,
* after which it will be garbage collected.
*
* @param[out] context Optional, stores error information
* @return primop, or null in case of errors
*
*/
nix_err nix_register_primop(nix_c_context * context, PrimOp * primOp);
/** @} */
// Function prototypes
/** @brief Allocate a Nix value
*
* Owned by the GC. Use nix_gc_decref() when you're done with the pointer
* @param[out] context Optional, stores error information
* @param[in] state nix evaluator state
* @return value, or null in case of errors
*
*/
nix_value * nix_alloc_value(nix_c_context * context, EvalState * state);
/**
* @brief Increment the garbage collector reference counter for the given `nix_value`.
*
* The Nix language evaluator C API keeps track of alive objects by reference counting.
* When you're done with a refcounted pointer, call nix_value_decref().
*
* @param[out] context Optional, stores error information
* @param[in] value The object to keep alive
*/
nix_err nix_value_incref(nix_c_context * context, nix_value * value);
/**
* @brief Decrement the garbage collector reference counter for the given object
*
* @param[out] context Optional, stores error information
* @param[in] value The object to stop referencing
*/
nix_err nix_value_decref(nix_c_context * context, nix_value * value);
/** @addtogroup value_manip Manipulating values
* @brief Functions to inspect and change Nix language values, represented by nix_value.
* @{
*/
/** @anchor getters
* @name Getters
*/
/**@{*/
/** @brief Get value type
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @return type of nix value
*/
ValueType nix_get_type(nix_c_context * context, const nix_value * value);
/** @brief Get type name of value as defined in the evaluator
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @return type name, owned string
* @todo way to free the result
*/
const char * nix_get_typename(nix_c_context * context, const nix_value * value);
/** @brief Get boolean value
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @return true or false, error info via context
*/
bool nix_get_bool(nix_c_context * context, const nix_value * value);
/** @brief Get the raw string
*
* This may contain placeholders.
*
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @param[in] callback Called with the string value.
* @param[in] user_data optional, arbitrary data, passed to the callback when it's called.
* @return string
* @return error code, NIX_OK on success.
*/
nix_err
nix_get_string(nix_c_context * context, const nix_value * value, nix_get_string_callback callback, void * user_data);
/** @brief Get path as string
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @return string
* @return NULL in case of error.
*/
const char * nix_get_path_string(nix_c_context * context, const nix_value * value);
/** @brief Get the length of a list
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @return length of list, error info via context
*/
unsigned int nix_get_list_size(nix_c_context * context, const nix_value * value);
/** @brief Get the element count of an attrset
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @return attrset element count, error info via context
*/
unsigned int nix_get_attrs_size(nix_c_context * context, const nix_value * value);
/** @brief Get float value in 64 bits
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @return float contents, error info via context
*/
double nix_get_float(nix_c_context * context, const nix_value * value);
/** @brief Get int value
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @return int contents, error info via context
*/
int64_t nix_get_int(nix_c_context * context, const nix_value * value);
/** @brief Get external reference
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @return reference to external, NULL in case of error
*/
ExternalValue * nix_get_external(nix_c_context * context, nix_value *);
/** @brief Get the ix'th element of a list
*
* Owned by the GC. Use nix_gc_decref when you're done with the pointer
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @param[in] state nix evaluator state
* @param[in] ix list element to get
* @return value, NULL in case of errors
*/
nix_value * nix_get_list_byidx(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int ix);
/** @brief Get an attr by name
*
* Owned by the GC. Use nix_gc_decref when you're done with the pointer
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @param[in] state nix evaluator state
* @param[in] name attribute name
* @return value, NULL in case of errors
*/
nix_value * nix_get_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name);
/** @brief Check if an attribute name exists on a value
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @param[in] state nix evaluator state
* @param[in] name attribute name
* @return value, error info via context
*/
bool nix_has_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name);
/** @brief Get an attribute by index in the sorted bindings
*
* Also gives you the name.
*
* Owned by the GC. Use nix_gc_decref when you're done with the pointer
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @param[in] state nix evaluator state
* @param[in] i attribute index
* @param[out] name will store a pointer to the attribute name
* @return value, NULL in case of errors
*/
nix_value * nix_get_attr_byidx(
nix_c_context * context, const nix_value * value, EvalState * state, unsigned int i, const char ** name);
/** @brief Get an attribute name by index in the sorted bindings
*
* Useful when you want the name but want to avoid evaluation.
*
* Owned by the nix EvalState
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @param[in] state nix evaluator state
* @param[in] i attribute index
* @return name, NULL in case of errors
*/
const char *
nix_get_attr_name_byidx(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int i);
/**@}*/
/** @name Initializers
*
* Values are typically "returned" by initializing already allocated memory that serves as the return value.
* For this reason, the construction of values is not tied their allocation.
* Nix is a language with immutable values. Respect this property by only initializing Values once; and only initialize
* Values that are meant to be initialized by you. Failing to adhere to these rules may lead to undefined behavior.
*/
/**@{*/
/** @brief Set boolean value
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @param[in] b the boolean value
* @return error code, NIX_OK on success.
*/
nix_err nix_init_bool(nix_c_context * context, nix_value * value, bool b);
/** @brief Set a string
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @param[in] str the string, copied
* @return error code, NIX_OK on success.
*/
nix_err nix_init_string(nix_c_context * context, nix_value * value, const char * str);
/** @brief Set a path
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @param[in] str the path string, copied
* @return error code, NIX_OK on success.
*/
nix_err nix_init_path_string(nix_c_context * context, EvalState * s, nix_value * value, const char * str);
/** @brief Set a float
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @param[in] d the float, 64-bits
* @return error code, NIX_OK on success.
*/
nix_err nix_init_float(nix_c_context * context, nix_value * value, double d);
/** @brief Set an int
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @param[in] i the int
* @return error code, NIX_OK on success.
*/
nix_err nix_init_int(nix_c_context * context, nix_value * value, int64_t i);
/** @brief Set null
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @return error code, NIX_OK on success.
*/
nix_err nix_init_null(nix_c_context * context, nix_value * value);
/** @brief Set the value to a thunk that will perform a function application when needed.
*
* Thunks may be put into attribute sets and lists to perform some computation lazily; on demand.
* However, note that in some places, a thunk must not be returned, such as in the return value of a PrimOp.
* In such cases, you may use nix_value_call() instead (but note the different argument order).
*
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @param[in] fn function to call
* @param[in] arg argument to pass
* @return error code, NIX_OK on successful initialization.
* @see nix_value_call() for a similar function that performs the call immediately and only stores the return value.
* Note the different argument order.
*/
nix_err nix_init_apply(nix_c_context * context, nix_value * value, nix_value * fn, nix_value * arg);
/** @brief Set an external value
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @param[in] val the external value to set. Will be GC-referenced by the value.
* @return error code, NIX_OK on success.
*/
nix_err nix_init_external(nix_c_context * context, nix_value * value, ExternalValue * val);
/** @brief Create a list from a list builder
* @param[out] context Optional, stores error information
* @param[in] list_builder list builder to use. Make sure to unref this afterwards.
* @param[out] value Nix value to modify
* @return error code, NIX_OK on success.
*/
nix_err nix_make_list(nix_c_context * context, ListBuilder * list_builder, nix_value * value);
/** @brief Create a list builder
* @param[out] context Optional, stores error information
* @param[in] state nix evaluator state
* @param[in] capacity how many bindings you'll add. Don't exceed.
* @return owned reference to a list builder. Make sure to unref when you're done.
*/
ListBuilder * nix_make_list_builder(nix_c_context * context, EvalState * state, size_t capacity);
/** @brief Insert bindings into a builder
* @param[out] context Optional, stores error information
* @param[in] list_builder ListBuilder to insert into
* @param[in] index index to manipulate
* @param[in] value value to insert
* @return error code, NIX_OK on success.
*/
nix_err
nix_list_builder_insert(nix_c_context * context, ListBuilder * list_builder, unsigned int index, nix_value * value);
/** @brief Free a list builder
*
* Does not fail.
* @param[in] builder the builder to free
*/
void nix_list_builder_free(ListBuilder * list_builder);
/** @brief Create an attribute set from a bindings builder
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @param[in] b bindings builder to use. Make sure to unref this afterwards.
* @return error code, NIX_OK on success.
*/
nix_err nix_make_attrs(nix_c_context * context, nix_value * value, BindingsBuilder * b);
/** @brief Set primop
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @param[in] op primop, will be gc-referenced by the value
* @see nix_alloc_primop
* @return error code, NIX_OK on success.
*/
nix_err nix_init_primop(nix_c_context * context, nix_value * value, PrimOp * op);
/** @brief Copy from another value
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @param[in] source value to copy from
* @return error code, NIX_OK on success.
*/
nix_err nix_copy_value(nix_c_context * context, nix_value * value, const nix_value * source);
/**@}*/
/** @brief Create a bindings builder
* @param[out] context Optional, stores error information
* @param[in] state nix evaluator state
* @param[in] capacity how many bindings you'll add. Don't exceed.
* @return owned reference to a bindings builder. Make sure to unref when you're
done.
*/
BindingsBuilder * nix_make_bindings_builder(nix_c_context * context, EvalState * state, size_t capacity);
/** @brief Insert bindings into a builder
* @param[out] context Optional, stores error information
* @param[in] builder BindingsBuilder to insert into
* @param[in] name attribute name, only used for the duration of the call.
* @param[in] value value to give the binding
* @return error code, NIX_OK on success.
*/
nix_err
nix_bindings_builder_insert(nix_c_context * context, BindingsBuilder * builder, const char * name, nix_value * value);
/** @brief Free a bindings builder
*
* Does not fail.
* @param[in] builder the builder to free
*/
void nix_bindings_builder_free(BindingsBuilder * builder);
/**@}*/
/** @brief Realise a string context.
*
* This will
* - realise the store paths referenced by the string's context, and
* - perform the replacement of placeholders.
* - create temporary garbage collection roots for the store paths, for
* the lifetime of the current process.
* - log to stderr
*
* @param[out] context Optional, stores error information
* @param[in] value Nix value, which must be a string
* @param[in] state Nix evaluator state
* @param[in] isIFD If true, disallow derivation outputs if setting `allow-import-from-derivation` is false.
You should set this to true when this call is part of a primop.
You should set this to false when building for your application's purpose.
* @return NULL if failed, are a new nix_realised_string, which must be freed with nix_realised_string_free
*/
nix_realised_string * nix_string_realise(nix_c_context * context, EvalState * state, nix_value * value, bool isIFD);
/** @brief Start of the string
* @param[in] realised_string
* @return pointer to the start of the string. It may not be null-terminated.
*/
const char * nix_realised_string_get_buffer_start(nix_realised_string * realised_string);
/** @brief Length of the string
* @param[in] realised_string
* @return length of the string in bytes
*/
size_t nix_realised_string_get_buffer_size(nix_realised_string * realised_string);
/** @brief Number of realised store paths
* @param[in] realised_string
* @return number of realised store paths that were referenced by the string via its context
*/
size_t nix_realised_string_get_store_path_count(nix_realised_string * realised_string);
/** @brief Get a store path. The store paths are stored in an arbitrary order.
* @param[in] realised_string
* @param[in] index index of the store path, must be less than the count
* @return store path
*/
const StorePath * nix_realised_string_get_store_path(nix_realised_string * realised_string, size_t index);
/** @brief Free a realised string
* @param[in] realised_string
*/
void nix_realised_string_free(nix_realised_string * realised_string);
// cffi end
#ifdef __cplusplus
}
#endif
/** @} */
#endif // NIX_API_VALUE_H

58
src/libexpr-c/package.nix Normal file
View file

@ -0,0 +1,58 @@
{ lib
, stdenv
, mkMesonLibrary
, nix-store-c
, nix-expr
# Configuration Options
, version
}:
let
inherit (lib) fileset;
in
mkMesonLibrary (finalAttrs: {
pname = "nix-expr-c";
inherit version;
workDir = ./.;
fileset = fileset.unions [
../../build-utils-meson
./build-utils-meson
../../.version
./.version
./meson.build
# ./meson.options
(fileset.fileFilter (file: file.hasExt "cc") ./.)
(fileset.fileFilter (file: file.hasExt "hh") ./.)
(fileset.fileFilter (file: file.hasExt "h") ./.)
];
propagatedBuildInputs = [
nix-store-c
nix-expr
];
preConfigure =
# "Inline" .version so it's not a symlink, and includes the suffix.
# Do the meson utils, without modification.
''
chmod u+w ./.version
echo ${version} > ../../.version
'';
mesonFlags = [
];
env = lib.optionalAttrs (stdenv.isLinux && !(stdenv.hostPlatform.isStatic && stdenv.system == "aarch64-linux")) {
LDFLAGS = "-fuse-ld=gold";
};
meta = {
platforms = lib.platforms.unix ++ lib.platforms.windows;
};
})

View file

@ -0,0 +1 @@
../../.version

View file

@ -0,0 +1 @@
../../build-utils-meson

View file

@ -0,0 +1,23 @@
libraries += libexpr-test-support
libexpr-test-support_NAME = libnixexpr-test-support
libexpr-test-support_DIR := $(d)
ifeq ($(INSTALL_UNIT_TESTS), yes)
libexpr-test-support_INSTALL_DIR := $(checklibdir)
else
libexpr-test-support_INSTALL_DIR :=
endif
libexpr-test-support_SOURCES := \
$(wildcard $(d)/tests/*.cc) \
$(wildcard $(d)/tests/value/*.cc)
libexpr-test-support_CXXFLAGS += $(libexpr-tests_EXTRA_INCLUDES)
libexpr-test-support_LIBS = \
libstore-test-support libutil-test-support \
libexpr libstore libutil
libexpr-test-support_LDFLAGS := $(THREAD_LDFLAGS) -lrapidcheck

View file

@ -0,0 +1,77 @@
project('nix-expr-test-support', 'cpp',
version : files('.version'),
default_options : [
'cpp_std=c++2a',
# TODO(Qyriad): increase the warning level
'warning_level=1',
'debug=true',
'optimization=2',
'errorlogs=true', # Please print logs for tests that fail
],
meson_version : '>= 1.1',
license : 'LGPL-2.1-or-later',
)
cxx = meson.get_compiler('cpp')
subdir('build-utils-meson/deps-lists')
deps_private_maybe_subproject = [
]
deps_public_maybe_subproject = [
dependency('nix-util'),
dependency('nix-util-test-support'),
dependency('nix-store'),
dependency('nix-store-test-support'),
dependency('nix-expr'),
]
subdir('build-utils-meson/subprojects')
subdir('build-utils-meson/threads')
rapidcheck = dependency('rapidcheck')
deps_public += rapidcheck
add_project_arguments(
# TODO(Qyriad): Yes this is how the autoconf+Make system did it.
# It would be nice for our headers to be idempotent instead.
'-include', 'config-util.hh',
'-include', 'config-store.hh',
'-include', 'config-expr.hh',
language : 'cpp',
)
subdir('build-utils-meson/diagnostics')
sources = files(
'tests/value/context.cc',
)
include_dirs = [include_directories('.')]
headers = files(
'tests/libexpr.hh',
'tests/nix_api_expr.hh',
'tests/value/context.hh',
)
subdir('build-utils-meson/export-all-symbols')
subdir('build-utils-meson/windows-version')
this_library = library(
'nix-expr-test-support',
sources,
dependencies : deps_public + deps_private + deps_other,
include_directories : include_dirs,
# TODO: Remove `-lrapidcheck` when https://github.com/emil-e/rapidcheck/pull/326
# is available. See also ../libutil/build.meson
link_args: linker_export_flags + ['-lrapidcheck'],
prelink : true, # For C++ static initializers
install : true,
)
install_headers(headers, subdir : 'nix', preserve_path : true)
libraries_private = []
subdir('build-utils-meson/export')

View file

@ -0,0 +1,60 @@
{ lib
, stdenv
, mkMesonLibrary
, nix-store-test-support
, nix-expr
, rapidcheck
# Configuration Options
, version
}:
let
inherit (lib) fileset;
in
mkMesonLibrary (finalAttrs: {
pname = "nix-util-test-support";
inherit version;
workDir = ./.;
fileset = fileset.unions [
../../build-utils-meson
./build-utils-meson
../../.version
./.version
./meson.build
# ./meson.options
(fileset.fileFilter (file: file.hasExt "cc") ./.)
(fileset.fileFilter (file: file.hasExt "hh") ./.)
];
propagatedBuildInputs = [
nix-store-test-support
nix-expr
rapidcheck
];
preConfigure =
# "Inline" .version so it's not a symlink, and includes the suffix.
# Do the meson utils, without modification.
''
chmod u+w ./.version
echo ${version} > ../../.version
'';
mesonFlags = [
];
env = lib.optionalAttrs (stdenv.isLinux && !(stdenv.hostPlatform.isStatic && stdenv.system == "aarch64-linux")) {
LDFLAGS = "-fuse-ld=gold";
};
meta = {
platforms = lib.platforms.unix ++ lib.platforms.windows;
};
})

View file

@ -1,29 +1,38 @@
#pragma once
///@file
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "fetch-settings.hh"
#include "value.hh"
#include "nixexpr.hh"
#include "nixexpr.hh"
#include "eval.hh"
#include "eval-gc.hh"
#include "eval-inline.hh"
#include "store-api.hh"
#include "eval-settings.hh"
#include "tests/libstore.hh"
namespace nix {
class LibExprTest : public ::testing::Test {
class LibExprTest : public LibStoreTest {
public:
static void SetUpTestSuite() {
LibStoreTest::SetUpTestSuite();
initGC();
}
protected:
LibExprTest()
: store(openStore("dummy://"))
, state({}, store)
: LibStoreTest()
, state({}, store, fetchSettings, evalSettings, nullptr)
{
evalSettings.nixPath = {};
}
Value eval(std::string input, bool forceValue = true) {
Value v;
Expr * e = state.parseExprFromString(input, "");
Expr * e = state.parseExprFromString(input, state.rootPath(CanonPath::root));
assert(e);
state.eval(e, v);
if (forceValue)
@ -35,7 +44,9 @@ namespace nix {
return state.symbols.create(value);
}
ref<Store> store;
bool readOnlyMode = true;
fetchers::Settings fetchSettings{};
EvalSettings evalSettings{readOnlyMode};
EvalState state;
};
@ -67,46 +78,49 @@ namespace nix {
if (arg.type() != nString) {
return false;
}
return std::string_view(arg.string.s) == s;
return std::string_view(arg.c_str()) == s;
}
MATCHER_P(IsIntEq, v, fmt("The string is equal to \"%1%\"", v)) {
if (arg.type() != nInt) {
return false;
}
return arg.integer == v;
return arg.integer().value == v;
}
MATCHER_P(IsFloatEq, v, fmt("The float is equal to \"%1%\"", v)) {
if (arg.type() != nFloat) {
return false;
}
return arg.fpoint == v;
return arg.fpoint() == v;
}
MATCHER(IsTrue, "") {
if (arg.type() != nBool) {
return false;
}
return arg.boolean == true;
return arg.boolean() == true;
}
MATCHER(IsFalse, "") {
if (arg.type() != nBool) {
return false;
}
return arg.boolean == false;
return arg.boolean() == false;
}
MATCHER_P(IsPathEq, p, fmt("Is a path equal to \"%1%\"", p)) {
if (arg.type() != nPath) {
*result_listener << "Expected a path got " << arg.type();
return false;
} else if (std::string_view(arg.string.s) != p) {
*result_listener << "Expected a path that equals \"" << p << "\" but got: " << arg.string.s;
if (arg.type() != nPath) {
*result_listener << "Expected a path got " << arg.type();
return false;
} else {
auto path = arg.path();
if (path.path != CanonPath(p)) {
*result_listener << "Expected a path that equals \"" << p << "\" but got: " << path.path;
return false;
}
return true;
}
return true;
}
@ -123,10 +137,10 @@ namespace nix {
MATCHER_P(IsAttrsOfSize, n, fmt("Is a set of size [%1%]", n)) {
if (arg.type() != nAttrs) {
*result_listener << "Expexted set got " << arg.type();
*result_listener << "Expected set got " << arg.type();
return false;
} else if (arg.attrs->size() != (size_t)n) {
*result_listener << "Expected a set with " << n << " attributes but got " << arg.attrs->size();
} else if (arg.attrs()->size() != (size_t) n) {
*result_listener << "Expected a set with " << n << " attributes but got " << arg.attrs()->size();
return false;
}
return true;

View file

@ -0,0 +1,31 @@
#pragma once
///@file
#include "nix_api_expr.h"
#include "nix_api_value.h"
#include "tests/nix_api_store.hh"
#include <gtest/gtest.h>
namespace nixC {
class nix_api_expr_test : public nix_api_store_test
{
protected:
nix_api_expr_test()
{
nix_libexpr_init(ctx);
state = nix_state_create(nullptr, nullptr, store);
value = nix_alloc_value(nullptr, state);
}
~nix_api_expr_test()
{
nix_gc_decref(nullptr, value);
nix_state_free(state);
}
EvalState * state;
nix_value * value;
};
}

View file

@ -0,0 +1,30 @@
#include <rapidcheck.h>
#include "tests/path.hh"
#include "tests/value/context.hh"
namespace rc {
using namespace nix;
Gen<NixStringContextElem::DrvDeep> Arbitrary<NixStringContextElem::DrvDeep>::arbitrary()
{
return gen::just(NixStringContextElem::DrvDeep {
.drvPath = *gen::arbitrary<StorePath>(),
});
}
Gen<NixStringContextElem> Arbitrary<NixStringContextElem>::arbitrary()
{
switch (*gen::inRange<uint8_t>(0, std::variant_size_v<NixStringContextElem::Raw>)) {
case 0:
return gen::just<NixStringContextElem>(*gen::arbitrary<NixStringContextElem::Opaque>());
case 1:
return gen::just<NixStringContextElem>(*gen::arbitrary<NixStringContextElem::DrvDeep>());
case 2:
return gen::just<NixStringContextElem>(*gen::arbitrary<NixStringContextElem::Built>());
default:
assert(false);
}
}
}

View file

@ -0,0 +1,31 @@
#pragma once
///@file
#include <rapidcheck/gen/Arbitrary.h>
#include "value/context.hh"
namespace rc {
using namespace nix;
template<>
struct Arbitrary<NixStringContextElem::Opaque> {
static Gen<NixStringContextElem::Opaque> arbitrary();
};
template<>
struct Arbitrary<NixStringContextElem::Built> {
static Gen<NixStringContextElem::Built> arbitrary();
};
template<>
struct Arbitrary<NixStringContextElem::DrvDeep> {
static Gen<NixStringContextElem::DrvDeep> arbitrary();
};
template<>
struct Arbitrary<NixStringContextElem> {
static Gen<NixStringContextElem> arbitrary();
};
}

1
src/libexpr-tests/.version Symbolic link
View file

@ -0,0 +1 @@
../../.version

View file

@ -0,0 +1 @@
../../build-utils-meson

View file

View file

@ -0,0 +1,68 @@
#include <nlohmann/json.hpp>
#include <gtest/gtest.h>
#include <rapidcheck/gtest.h>
#include "tests/derived-path.hh"
#include "tests/libexpr.hh"
namespace nix {
// Testing of trivial expressions
class DerivedPathExpressionTest : public LibExprTest {};
// FIXME: `RC_GTEST_FIXTURE_PROP` isn't calling `SetUpTestSuite` because it is
// no a real fixture.
//
// See https://github.com/emil-e/rapidcheck/blob/master/doc/gtest.md#rc_gtest_fixture_propfixture-name-args
TEST_F(DerivedPathExpressionTest, force_init)
{
}
#ifndef COVERAGE
RC_GTEST_FIXTURE_PROP(
DerivedPathExpressionTest,
prop_opaque_path_round_trip,
(const SingleDerivedPath::Opaque & o))
{
auto * v = state.allocValue();
state.mkStorePathString(o.path, *v);
auto d = state.coerceToSingleDerivedPath(noPos, *v, "");
RC_ASSERT(SingleDerivedPath { o } == d);
}
// TODO use DerivedPath::Built for parameter once it supports a single output
// path only.
RC_GTEST_FIXTURE_PROP(
DerivedPathExpressionTest,
prop_derived_path_built_placeholder_round_trip,
(const SingleDerivedPath::Built & b))
{
/**
* We set these in tests rather than the regular globals so we don't have
* to worry about race conditions if the tests run concurrently.
*/
ExperimentalFeatureSettings mockXpSettings;
mockXpSettings.set("experimental-features", "ca-derivations");
auto * v = state.allocValue();
state.mkOutputString(*v, b, std::nullopt, mockXpSettings);
auto [d, _] = state.coerceToSingleDerivedPathUnchecked(noPos, *v, "");
RC_ASSERT(SingleDerivedPath { b } == d);
}
RC_GTEST_FIXTURE_PROP(
DerivedPathExpressionTest,
prop_derived_path_built_out_path_round_trip,
(const SingleDerivedPath::Built & b, const StorePath & outPath))
{
auto * v = state.allocValue();
state.mkOutputString(*v, b, outPath);
auto [d, _] = state.coerceToSingleDerivedPathUnchecked(noPos, *v, "");
RC_ASSERT(SingleDerivedPath { b } == d);
}
#endif
} /* namespace nix */

File diff suppressed because it is too large Load diff

141
src/libexpr-tests/eval.cc Normal file
View file

@ -0,0 +1,141 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "eval.hh"
#include "tests/libexpr.hh"
namespace nix {
TEST(nix_isAllowedURI, http_example_com) {
Strings allowed;
allowed.push_back("http://example.com");
ASSERT_TRUE(isAllowedURI("http://example.com", allowed));
ASSERT_TRUE(isAllowedURI("http://example.com/foo", allowed));
ASSERT_TRUE(isAllowedURI("http://example.com/foo/", allowed));
ASSERT_FALSE(isAllowedURI("/", allowed));
ASSERT_FALSE(isAllowedURI("http://example.co", allowed));
ASSERT_FALSE(isAllowedURI("http://example.como", allowed));
ASSERT_FALSE(isAllowedURI("http://example.org", allowed));
ASSERT_FALSE(isAllowedURI("http://example.org/foo", allowed));
}
TEST(nix_isAllowedURI, http_example_com_foo) {
Strings allowed;
allowed.push_back("http://example.com/foo");
ASSERT_TRUE(isAllowedURI("http://example.com/foo", allowed));
ASSERT_TRUE(isAllowedURI("http://example.com/foo/", allowed));
ASSERT_FALSE(isAllowedURI("/foo", allowed));
ASSERT_FALSE(isAllowedURI("http://example.com", allowed));
ASSERT_FALSE(isAllowedURI("http://example.como", allowed));
ASSERT_FALSE(isAllowedURI("http://example.org/foo", allowed));
// Broken?
// ASSERT_TRUE(isAllowedURI("http://example.com/foo?ok=1", allowed));
}
TEST(nix_isAllowedURI, http) {
Strings allowed;
allowed.push_back("http://");
ASSERT_TRUE(isAllowedURI("http://", allowed));
ASSERT_TRUE(isAllowedURI("http://example.com", allowed));
ASSERT_TRUE(isAllowedURI("http://example.com/foo", allowed));
ASSERT_TRUE(isAllowedURI("http://example.com/foo/", allowed));
ASSERT_TRUE(isAllowedURI("http://example.com", allowed));
ASSERT_FALSE(isAllowedURI("/", allowed));
ASSERT_FALSE(isAllowedURI("https://", allowed));
ASSERT_FALSE(isAllowedURI("http:foo", allowed));
}
TEST(nix_isAllowedURI, https) {
Strings allowed;
allowed.push_back("https://");
ASSERT_TRUE(isAllowedURI("https://example.com", allowed));
ASSERT_TRUE(isAllowedURI("https://example.com/foo", allowed));
ASSERT_FALSE(isAllowedURI("http://example.com", allowed));
ASSERT_FALSE(isAllowedURI("http://example.com/https:", allowed));
}
TEST(nix_isAllowedURI, absolute_path) {
Strings allowed;
allowed.push_back("/var/evil"); // bad idea
ASSERT_TRUE(isAllowedURI("/var/evil", allowed));
ASSERT_TRUE(isAllowedURI("/var/evil/", allowed));
ASSERT_TRUE(isAllowedURI("/var/evil/foo", allowed));
ASSERT_TRUE(isAllowedURI("/var/evil/foo/", allowed));
ASSERT_FALSE(isAllowedURI("/", allowed));
ASSERT_FALSE(isAllowedURI("/var/evi", allowed));
ASSERT_FALSE(isAllowedURI("/var/evilo", allowed));
ASSERT_FALSE(isAllowedURI("/var/evilo/", allowed));
ASSERT_FALSE(isAllowedURI("/var/evilo/foo", allowed));
ASSERT_FALSE(isAllowedURI("http://example.com/var/evil", allowed));
ASSERT_FALSE(isAllowedURI("http://example.com//var/evil", allowed));
ASSERT_FALSE(isAllowedURI("http://example.com//var/evil/foo", allowed));
}
TEST(nix_isAllowedURI, file_url) {
Strings allowed;
allowed.push_back("file:///var/evil"); // bad idea
ASSERT_TRUE(isAllowedURI("file:///var/evil", allowed));
ASSERT_TRUE(isAllowedURI("file:///var/evil/", allowed));
ASSERT_TRUE(isAllowedURI("file:///var/evil/foo", allowed));
ASSERT_TRUE(isAllowedURI("file:///var/evil/foo/", allowed));
ASSERT_FALSE(isAllowedURI("/", allowed));
ASSERT_FALSE(isAllowedURI("/var/evi", allowed));
ASSERT_FALSE(isAllowedURI("/var/evilo", allowed));
ASSERT_FALSE(isAllowedURI("/var/evilo/", allowed));
ASSERT_FALSE(isAllowedURI("/var/evilo/foo", allowed));
ASSERT_FALSE(isAllowedURI("http://example.com/var/evil", allowed));
ASSERT_FALSE(isAllowedURI("http://example.com//var/evil", allowed));
ASSERT_FALSE(isAllowedURI("http://example.com//var/evil/foo", allowed));
ASSERT_FALSE(isAllowedURI("http://var/evil", allowed));
ASSERT_FALSE(isAllowedURI("http:///var/evil", allowed));
ASSERT_FALSE(isAllowedURI("http://var/evil/", allowed));
ASSERT_FALSE(isAllowedURI("file:///var/evi", allowed));
ASSERT_FALSE(isAllowedURI("file:///var/evilo", allowed));
ASSERT_FALSE(isAllowedURI("file:///var/evilo/", allowed));
ASSERT_FALSE(isAllowedURI("file:///var/evilo/foo", allowed));
ASSERT_FALSE(isAllowedURI("file:///", allowed));
ASSERT_FALSE(isAllowedURI("file://", allowed));
}
TEST(nix_isAllowedURI, github_all) {
Strings allowed;
allowed.push_back("github:");
ASSERT_TRUE(isAllowedURI("github:", allowed));
ASSERT_TRUE(isAllowedURI("github:foo/bar", allowed));
ASSERT_TRUE(isAllowedURI("github:foo/bar/feat-multi-bar", allowed));
ASSERT_TRUE(isAllowedURI("github:foo/bar?ref=refs/heads/feat-multi-bar", allowed));
ASSERT_TRUE(isAllowedURI("github://foo/bar", allowed));
ASSERT_FALSE(isAllowedURI("https://github:443/foo/bar/archive/master.tar.gz", allowed));
ASSERT_FALSE(isAllowedURI("file://github:foo/bar/archive/master.tar.gz", allowed));
ASSERT_FALSE(isAllowedURI("file:///github:foo/bar/archive/master.tar.gz", allowed));
ASSERT_FALSE(isAllowedURI("github", allowed));
}
TEST(nix_isAllowedURI, github_org) {
Strings allowed;
allowed.push_back("github:foo");
ASSERT_FALSE(isAllowedURI("github:", allowed));
ASSERT_TRUE(isAllowedURI("github:foo/bar", allowed));
ASSERT_TRUE(isAllowedURI("github:foo/bar/feat-multi-bar", allowed));
ASSERT_TRUE(isAllowedURI("github:foo/bar?ref=refs/heads/feat-multi-bar", allowed));
ASSERT_FALSE(isAllowedURI("github://foo/bar", allowed));
ASSERT_FALSE(isAllowedURI("https://github:443/foo/bar/archive/master.tar.gz", allowed));
ASSERT_FALSE(isAllowedURI("file://github:foo/bar/archive/master.tar.gz", allowed));
ASSERT_FALSE(isAllowedURI("file:///github:foo/bar/archive/master.tar.gz", allowed));
}
TEST(nix_isAllowedURI, non_scheme_colon) {
Strings allowed;
allowed.push_back("https://foo/bar:");
ASSERT_TRUE(isAllowedURI("https://foo/bar:", allowed));
ASSERT_TRUE(isAllowedURI("https://foo/bar:/baz", allowed));
ASSERT_FALSE(isAllowedURI("https://foo/bar:baz", allowed));
}
} // namespace nix

View file

@ -1,4 +1,4 @@
#include "libexprtests.hh"
#include "tests/libexpr.hh"
#include "value-to-json.hh"
namespace nix {
@ -8,7 +8,7 @@ namespace nix {
protected:
std::string getJSONValue(Value& value) {
std::stringstream ss;
PathSet ps;
NixStringContext ps;
printValueAsJSON(state, true, value, noPos, ss, ps);
return ss.str();
}
@ -62,7 +62,7 @@ namespace nix {
// not supported by store 'dummy'" thrown in the test body.
TEST_F(JSONValueTest, DISABLED_Path) {
Value v;
v.mkPath("test");
v.mkPath(state.rootPath(CanonPath("/test")));
ASSERT_EQ(getJSONValue(v), "\"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x\"");
}
} /* namespace nix */

View file

@ -0,0 +1,45 @@
check: libexpr-tests_RUN
programs += libexpr-tests
libexpr-tests_NAME := libnixexpr-tests
libexpr-tests_ENV := _NIX_TEST_UNIT_DATA=$(d)/data GTEST_OUTPUT=xml:$$testresults/libexpr-tests.xml
libexpr-tests_DIR := $(d)
ifeq ($(INSTALL_UNIT_TESTS), yes)
libexpr-tests_INSTALL_DIR := $(checkbindir)
else
libexpr-tests_INSTALL_DIR :=
endif
libexpr-tests_SOURCES := \
$(wildcard $(d)/*.cc) \
$(wildcard $(d)/value/*.cc) \
$(wildcard $(d)/flake/*.cc)
libexpr-tests_EXTRA_INCLUDES = \
-I src/libexpr-test-support \
-I src/libstore-test-support \
-I src/libutil-test-support \
$(INCLUDE_libexpr) \
$(INCLUDE_libexprc) \
$(INCLUDE_libfetchers) \
$(INCLUDE_libstore) \
$(INCLUDE_libstorec) \
$(INCLUDE_libutil) \
$(INCLUDE_libutilc)
libexpr-tests_CXXFLAGS += $(libexpr-tests_EXTRA_INCLUDES)
libexpr-tests_LIBS = \
libexpr-test-support libstore-test-support libutil-test-support \
libexpr libexprc libfetchers libstore libstorec libutil libutilc
libexpr-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
libexpr-tests_LDFLAGS += -Wl,--stack,$(shell echo $$((65 * 1024 * 1024)))
endif

42
src/libexpr-tests/main.cc Normal file
View file

@ -0,0 +1,42 @@
#include <gtest/gtest.h>
#include <cstdlib>
#include "globals.hh"
#include "logging.hh"
using namespace nix;
int main (int argc, char **argv) {
if (argc > 1 && std::string_view(argv[1]) == "__build-remote") {
printError("test-build-remote: not supported in libexpr unit tests");
return 1;
}
// Disable build hook. We won't be testing remote builds in these unit tests. If we do, fix the above build hook.
settings.buildHook = {};
#if __linux__ // should match the conditional around sandboxBuildDir declaration.
// When building and testing nix within the host's Nix sandbox, our store dir will be located in the host's sandboxBuildDir, e.g.:
// Host
// storeDir = /nix/store
// sandboxBuildDir = /build
// This process
// storeDir = /build/foo/bar/store
// sandboxBuildDir = /build
// However, we have a rule that the store dir must not be inside the storeDir, so we need to pick a different sandboxBuildDir.
settings.sandboxBuildDir = "/test-build-dir-instead-of-usual-build-dir";
#endif
#if __APPLE__
// Avoid this error, when already running in a sandbox:
// sandbox-exec: sandbox_apply: Operation not permitted
settings.sandboxMode = smDisabled;
setEnv("_NIX_TEST_NO_SANDBOX", "1");
#endif
// For pipe operator tests in trivial.cc
experimentalFeatureSettings.set("experimental-features", "pipe-operators");
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View file

@ -0,0 +1,93 @@
project('nix-expr-tests', 'cpp',
version : files('.version'),
default_options : [
'cpp_std=c++2a',
# TODO(Qyriad): increase the warning level
'warning_level=1',
'debug=true',
'optimization=2',
'errorlogs=true', # Please print logs for tests that fail
],
meson_version : '>= 1.1',
license : 'LGPL-2.1-or-later',
)
cxx = meson.get_compiler('cpp')
subdir('build-utils-meson/deps-lists')
deps_private_maybe_subproject = [
dependency('nix-expr'),
dependency('nix-expr-c'),
dependency('nix-expr-test-support'),
]
deps_public_maybe_subproject = [
]
subdir('build-utils-meson/subprojects')
subdir('build-utils-meson/threads')
subdir('build-utils-meson/export-all-symbols')
subdir('build-utils-meson/windows-version')
rapidcheck = dependency('rapidcheck')
deps_private += rapidcheck
gtest = dependency('gtest')
deps_private += gtest
gtest = dependency('gmock')
deps_private += gtest
add_project_arguments(
# TODO(Qyriad): Yes this is how the autoconf+Make system did it.
# It would be nice for our headers to be idempotent instead.
'-include', 'config-util.hh',
'-include', 'config-store.hh',
'-include', 'config-expr.hh',
'-include', 'config-util.h',
'-include', 'config-store.h',
'-include', 'config-expr.h',
language : 'cpp',
)
subdir('build-utils-meson/diagnostics')
sources = files(
'derived-path.cc',
'error_traces.cc',
'eval.cc',
'json.cc',
'main.cc',
'nix_api_expr.cc',
'nix_api_external.cc',
'nix_api_value.cc',
'primops.cc',
'search-path.cc',
'trivial.cc',
'value/context.cc',
'value/print.cc',
'value/value.cc',
)
include_dirs = [include_directories('.')]
this_exe = executable(
meson.project_name(),
sources,
dependencies : deps_private_subproject + deps_private + deps_other,
include_directories : include_dirs,
# TODO: -lrapidcheck, see ../libutil-support/build.meson
link_args: linker_export_flags + ['-lrapidcheck'],
install : true,
)
test(
meson.project_name(),
this_exe,
env : {
'_NIX_TEST_UNIT_DATA': meson.current_source_dir() / 'data',
},
protocol : 'gtest',
)

View file

@ -0,0 +1,404 @@
#include "nix_api_store.h"
#include "nix_api_store_internal.h"
#include "nix_api_util.h"
#include "nix_api_util_internal.h"
#include "nix_api_expr.h"
#include "nix_api_value.h"
#include "tests/nix_api_expr.hh"
#include "tests/string_callback.hh"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace nixC {
TEST_F(nix_api_expr_test, nix_expr_eval_from_string)
{
nix_expr_eval_from_string(nullptr, state, "builtins.nixVersion", ".", value);
nix_value_force(nullptr, state, value);
std::string result;
nix_get_string(nullptr, value, OBSERVE_STRING(result));
ASSERT_STREQ(PACKAGE_VERSION, result.c_str());
}
TEST_F(nix_api_expr_test, nix_expr_eval_add_numbers)
{
nix_expr_eval_from_string(nullptr, state, "1 + 1", ".", value);
nix_value_force(nullptr, state, value);
auto result = nix_get_int(nullptr, value);
ASSERT_EQ(2, result);
}
TEST_F(nix_api_expr_test, nix_expr_eval_drv)
{
auto expr = R"(derivation { name = "myname"; builder = "mybuilder"; system = "mysystem"; })";
nix_expr_eval_from_string(nullptr, state, expr, ".", value);
ASSERT_EQ(NIX_TYPE_ATTRS, nix_get_type(nullptr, value));
EvalState * stateFn = nix_state_create(nullptr, nullptr, store);
nix_value * valueFn = nix_alloc_value(nullptr, state);
nix_expr_eval_from_string(nullptr, stateFn, "builtins.toString", ".", valueFn);
ASSERT_EQ(NIX_TYPE_FUNCTION, nix_get_type(nullptr, valueFn));
EvalState * stateResult = nix_state_create(nullptr, nullptr, store);
nix_value * valueResult = nix_alloc_value(nullptr, stateResult);
nix_value_call(ctx, stateResult, valueFn, value, valueResult);
ASSERT_EQ(NIX_TYPE_STRING, nix_get_type(nullptr, valueResult));
std::string p;
nix_get_string(nullptr, valueResult, OBSERVE_STRING(p));
std::string pEnd = "-myname";
ASSERT_EQ(pEnd, p.substr(p.size() - pEnd.size()));
// Clean up
nix_gc_decref(nullptr, valueFn);
nix_state_free(stateFn);
nix_gc_decref(nullptr, valueResult);
nix_state_free(stateResult);
}
TEST_F(nix_api_expr_test, nix_build_drv)
{
auto expr = R"(derivation { name = "myname";
system = builtins.currentSystem;
builder = "/bin/sh";
args = [ "-c" "echo foo > $out" ];
})";
nix_expr_eval_from_string(nullptr, state, expr, ".", value);
nix_value * drvPathValue = nix_get_attr_byname(nullptr, value, state, "drvPath");
std::string drvPath;
nix_get_string(nullptr, drvPathValue, OBSERVE_STRING(drvPath));
std::string p = drvPath;
std::string pEnd = "-myname.drv";
ASSERT_EQ(pEnd, p.substr(p.size() - pEnd.size()));
// NOTE: .drvPath should be usually be ignored. Output paths are more versatile.
// See https://github.com/NixOS/nix/issues/6507
// Use e.g. nix_string_realise to realise the output.
StorePath * drvStorePath = nix_store_parse_path(ctx, store, drvPath.c_str());
ASSERT_EQ(true, nix_store_is_valid_path(ctx, store, drvStorePath));
nix_value * outPathValue = nix_get_attr_byname(ctx, value, state, "outPath");
std::string outPath;
nix_get_string(ctx, outPathValue, OBSERVE_STRING(outPath));
p = outPath;
pEnd = "-myname";
ASSERT_EQ(pEnd, p.substr(p.size() - pEnd.size()));
ASSERT_EQ(true, drvStorePath->path.isDerivation());
StorePath * outStorePath = nix_store_parse_path(ctx, store, outPath.c_str());
ASSERT_EQ(false, nix_store_is_valid_path(ctx, store, outStorePath));
nix_store_realise(ctx, store, drvStorePath, nullptr, nullptr);
auto is_valid_path = nix_store_is_valid_path(ctx, store, outStorePath);
ASSERT_EQ(true, is_valid_path);
// Clean up
nix_store_path_free(drvStorePath);
nix_store_path_free(outStorePath);
}
TEST_F(nix_api_expr_test, nix_expr_realise_context_bad_value)
{
auto expr = "true";
nix_expr_eval_from_string(ctx, state, expr, ".", value);
assert_ctx_ok();
auto r = nix_string_realise(ctx, state, value, false);
ASSERT_EQ(nullptr, r);
ASSERT_EQ(ctx->last_err_code, NIX_ERR_NIX_ERROR);
ASSERT_THAT(ctx->last_err, testing::Optional(testing::HasSubstr("cannot coerce")));
}
TEST_F(nix_api_expr_test, nix_expr_realise_context_bad_build)
{
auto expr = R"(
derivation { name = "letsbuild";
system = builtins.currentSystem;
builder = "/bin/sh";
args = [ "-c" "echo failing a build for testing purposes; exit 1;" ];
}
)";
nix_expr_eval_from_string(ctx, state, expr, ".", value);
assert_ctx_ok();
auto r = nix_string_realise(ctx, state, value, false);
ASSERT_EQ(nullptr, r);
ASSERT_EQ(ctx->last_err_code, NIX_ERR_NIX_ERROR);
ASSERT_THAT(ctx->last_err, testing::Optional(testing::HasSubstr("failed with exit code 1")));
}
TEST_F(nix_api_expr_test, nix_expr_realise_context)
{
// TODO (ca-derivations): add a content-addressed derivation output, which produces a placeholder
auto expr = R"(
''
a derivation output: ${
derivation { name = "letsbuild";
system = builtins.currentSystem;
builder = "/bin/sh";
args = [ "-c" "echo foo > $out" ];
}}
a path: ${builtins.toFile "just-a-file" "ooh file good"}
a derivation path by itself: ${
builtins.unsafeDiscardOutputDependency
(derivation {
name = "not-actually-built-yet";
system = builtins.currentSystem;
builder = "/bin/sh";
args = [ "-c" "echo foo > $out" ];
}).drvPath}
''
)";
nix_expr_eval_from_string(ctx, state, expr, ".", value);
assert_ctx_ok();
auto r = nix_string_realise(ctx, state, value, false);
assert_ctx_ok();
ASSERT_NE(nullptr, r);
auto s = std::string(nix_realised_string_get_buffer_start(r), nix_realised_string_get_buffer_size(r));
EXPECT_THAT(s, testing::StartsWith("a derivation output:"));
EXPECT_THAT(s, testing::HasSubstr("-letsbuild\n"));
EXPECT_THAT(s, testing::Not(testing::HasSubstr("-letsbuild.drv")));
EXPECT_THAT(s, testing::HasSubstr("a path:"));
EXPECT_THAT(s, testing::HasSubstr("-just-a-file"));
EXPECT_THAT(s, testing::Not(testing::HasSubstr("-just-a-file.drv")));
EXPECT_THAT(s, testing::Not(testing::HasSubstr("ooh file good")));
EXPECT_THAT(s, testing::HasSubstr("a derivation path by itself:"));
EXPECT_THAT(s, testing::EndsWith("-not-actually-built-yet.drv\n"));
std::vector<std::string> names;
size_t n = nix_realised_string_get_store_path_count(r);
for (size_t i = 0; i < n; ++i) {
const StorePath * p = nix_realised_string_get_store_path(r, i);
ASSERT_NE(nullptr, p);
std::string name;
nix_store_path_name(p, OBSERVE_STRING(name));
names.push_back(name);
}
std::sort(names.begin(), names.end());
ASSERT_EQ(3, names.size());
EXPECT_THAT(names[0], testing::StrEq("just-a-file"));
EXPECT_THAT(names[1], testing::StrEq("letsbuild"));
EXPECT_THAT(names[2], testing::StrEq("not-actually-built-yet.drv"));
nix_realised_string_free(r);
}
const char * SAMPLE_USER_DATA = "whatever";
static void
primop_square(void * user_data, nix_c_context * context, EvalState * state, nix_value ** args, nix_value * ret)
{
assert(context);
assert(state);
assert(user_data == SAMPLE_USER_DATA);
auto i = nix_get_int(context, args[0]);
nix_init_int(context, ret, i * i);
}
TEST_F(nix_api_expr_test, nix_expr_primop)
{
PrimOp * primop =
nix_alloc_primop(ctx, primop_square, 1, "square", nullptr, "square an integer", (void *) SAMPLE_USER_DATA);
assert_ctx_ok();
nix_value * primopValue = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_init_primop(ctx, primopValue, primop);
assert_ctx_ok();
nix_value * three = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_init_int(ctx, three, 3);
assert_ctx_ok();
nix_value * result = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_value_call(ctx, state, primopValue, three, result);
assert_ctx_ok();
auto r = nix_get_int(ctx, result);
ASSERT_EQ(9, r);
}
static void
primop_repeat(void * user_data, nix_c_context * context, EvalState * state, nix_value ** args, nix_value * ret)
{
assert(context);
assert(state);
assert(user_data == SAMPLE_USER_DATA);
// Get the string to repeat
std::string s;
if (nix_get_string(context, args[0], OBSERVE_STRING(s)) != NIX_OK)
return;
// Get the number of times to repeat
auto n = nix_get_int(context, args[1]);
if (nix_err_code(context) != NIX_OK)
return;
// Repeat the string
std::string result;
for (int i = 0; i < n; ++i)
result += s;
nix_init_string(context, ret, result.c_str());
}
TEST_F(nix_api_expr_test, nix_expr_primop_arity_2_multiple_calls)
{
PrimOp * primop =
nix_alloc_primop(ctx, primop_repeat, 2, "repeat", nullptr, "repeat a string", (void *) SAMPLE_USER_DATA);
assert_ctx_ok();
nix_value * primopValue = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_init_primop(ctx, primopValue, primop);
assert_ctx_ok();
nix_value * hello = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_init_string(ctx, hello, "hello");
assert_ctx_ok();
nix_value * three = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_init_int(ctx, three, 3);
assert_ctx_ok();
nix_value * partial = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_value_call(ctx, state, primopValue, hello, partial);
assert_ctx_ok();
nix_value * result = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_value_call(ctx, state, partial, three, result);
assert_ctx_ok();
std::string r;
nix_get_string(ctx, result, OBSERVE_STRING(r));
ASSERT_STREQ("hellohellohello", r.c_str());
}
TEST_F(nix_api_expr_test, nix_expr_primop_arity_2_single_call)
{
PrimOp * primop =
nix_alloc_primop(ctx, primop_repeat, 2, "repeat", nullptr, "repeat a string", (void *) SAMPLE_USER_DATA);
assert_ctx_ok();
nix_value * primopValue = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_init_primop(ctx, primopValue, primop);
assert_ctx_ok();
nix_value * hello = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_init_string(ctx, hello, "hello");
assert_ctx_ok();
nix_value * three = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_init_int(ctx, three, 3);
assert_ctx_ok();
nix_value * result = nix_alloc_value(ctx, state);
assert_ctx_ok();
NIX_VALUE_CALL(ctx, state, result, primopValue, hello, three);
assert_ctx_ok();
std::string r;
nix_get_string(ctx, result, OBSERVE_STRING(r));
assert_ctx_ok();
ASSERT_STREQ("hellohellohello", r.c_str());
}
static void
primop_bad_no_return(void * user_data, nix_c_context * context, EvalState * state, nix_value ** args, nix_value * ret)
{
}
TEST_F(nix_api_expr_test, nix_expr_primop_bad_no_return)
{
PrimOp * primop =
nix_alloc_primop(ctx, primop_bad_no_return, 1, "badNoReturn", nullptr, "a broken primop", nullptr);
assert_ctx_ok();
nix_value * primopValue = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_init_primop(ctx, primopValue, primop);
assert_ctx_ok();
nix_value * three = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_init_int(ctx, three, 3);
assert_ctx_ok();
nix_value * result = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_value_call(ctx, state, primopValue, three, result);
ASSERT_EQ(ctx->last_err_code, NIX_ERR_NIX_ERROR);
ASSERT_THAT(
ctx->last_err,
testing::Optional(
testing::HasSubstr("Implementation error in custom function: return value was not initialized")));
ASSERT_THAT(ctx->last_err, testing::Optional(testing::HasSubstr("badNoReturn")));
}
static void primop_bad_return_thunk(
void * user_data, nix_c_context * context, EvalState * state, nix_value ** args, nix_value * ret)
{
nix_init_apply(context, ret, args[0], args[1]);
}
TEST_F(nix_api_expr_test, nix_expr_primop_bad_return_thunk)
{
PrimOp * primop =
nix_alloc_primop(ctx, primop_bad_return_thunk, 2, "badReturnThunk", nullptr, "a broken primop", nullptr);
assert_ctx_ok();
nix_value * primopValue = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_init_primop(ctx, primopValue, primop);
assert_ctx_ok();
nix_value * toString = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_expr_eval_from_string(ctx, state, "builtins.toString", ".", toString);
assert_ctx_ok();
nix_value * four = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_init_int(ctx, four, 4);
assert_ctx_ok();
nix_value * result = nix_alloc_value(ctx, state);
assert_ctx_ok();
NIX_VALUE_CALL(ctx, state, result, primopValue, toString, four);
ASSERT_EQ(ctx->last_err_code, NIX_ERR_NIX_ERROR);
ASSERT_THAT(
ctx->last_err,
testing::Optional(
testing::HasSubstr("Implementation error in custom function: return value must not be a thunk")));
ASSERT_THAT(ctx->last_err, testing::Optional(testing::HasSubstr("badReturnThunk")));
}
TEST_F(nix_api_expr_test, nix_value_call_multi_no_args)
{
nix_value * n = nix_alloc_value(ctx, state);
nix_init_int(ctx, n, 3);
assert_ctx_ok();
nix_value * r = nix_alloc_value(ctx, state);
nix_value_call_multi(ctx, state, n, 0, nullptr, r);
assert_ctx_ok();
auto rInt = nix_get_int(ctx, r);
assert_ctx_ok();
ASSERT_EQ(3, rInt);
}
} // namespace nixC

View file

@ -0,0 +1,68 @@
#include "nix_api_store.h"
#include "nix_api_store_internal.h"
#include "nix_api_util.h"
#include "nix_api_util_internal.h"
#include "nix_api_expr.h"
#include "nix_api_expr_internal.h"
#include "nix_api_value.h"
#include "nix_api_external.h"
#include "tests/nix_api_expr.hh"
#include "tests/string_callback.hh"
#include <gtest/gtest.h>
namespace nixC {
class MyExternalValueDesc : public NixCExternalValueDesc
{
public:
MyExternalValueDesc(int x)
: _x(x)
{
print = print_function;
showType = show_type_function;
typeOf = type_of_function;
}
private:
int _x;
static void print_function(void * self, nix_printer * printer) {}
static void show_type_function(void * self, nix_string_return * res) {}
static void type_of_function(void * self, nix_string_return * res)
{
MyExternalValueDesc * obj = static_cast<MyExternalValueDesc *>(self);
std::string type_string = "nix-external<MyExternalValueDesc( ";
type_string += std::to_string(obj->_x);
type_string += " )>";
res->str = &*type_string.begin();
}
};
TEST_F(nix_api_expr_test, nix_expr_eval_external)
{
MyExternalValueDesc * external = new MyExternalValueDesc(42);
ExternalValue * val = nix_create_external_value(ctx, external, external);
nix_init_external(ctx, value, val);
EvalState * stateResult = nix_state_create(nullptr, nullptr, store);
nix_value * valueResult = nix_alloc_value(nullptr, stateResult);
EvalState * stateFn = nix_state_create(nullptr, nullptr, store);
nix_value * valueFn = nix_alloc_value(nullptr, stateFn);
nix_expr_eval_from_string(nullptr, state, "builtins.typeOf", ".", valueFn);
ASSERT_EQ(NIX_TYPE_EXTERNAL, nix_get_type(nullptr, value));
nix_value_call(ctx, state, valueFn, value, valueResult);
std::string string_value;
nix_get_string(nullptr, valueResult, OBSERVE_STRING(string_value));
ASSERT_STREQ("nix-external<MyExternalValueDesc( 42 )>", string_value.c_str());
}
}

View file

@ -0,0 +1,402 @@
#include "nix_api_store.h"
#include "nix_api_store_internal.h"
#include "nix_api_util.h"
#include "nix_api_util_internal.h"
#include "nix_api_expr.h"
#include "nix_api_value.h"
#include "nix_api_expr_internal.h"
#include "tests/nix_api_expr.hh"
#include "tests/string_callback.hh"
#include "gmock/gmock.h"
#include <cstddef>
#include <cstdlib>
#include <gtest/gtest.h>
namespace nixC {
TEST_F(nix_api_expr_test, as_nix_value_ptr)
{
// nix_alloc_value casts nix::Value to nix_value
// It should be obvious from the decl that that works, but if it doesn't,
// the whole implementation would be utterly broken.
ASSERT_EQ(sizeof(nix::Value), sizeof(nix_value));
}
TEST_F(nix_api_expr_test, nix_value_get_int_invalid)
{
ASSERT_EQ(0, nix_get_int(ctx, nullptr));
assert_ctx_err();
ASSERT_EQ(0, nix_get_int(ctx, value));
assert_ctx_err();
}
TEST_F(nix_api_expr_test, nix_value_set_get_int)
{
int myInt = 1;
nix_init_int(ctx, value, myInt);
ASSERT_EQ(myInt, nix_get_int(ctx, value));
ASSERT_STREQ("an integer", nix_get_typename(ctx, value));
ASSERT_EQ(NIX_TYPE_INT, nix_get_type(ctx, value));
}
TEST_F(nix_api_expr_test, nix_value_set_get_float_invalid)
{
ASSERT_DOUBLE_EQ(0.0, nix_get_float(ctx, nullptr));
assert_ctx_err();
ASSERT_DOUBLE_EQ(0.0, nix_get_float(ctx, value));
assert_ctx_err();
}
TEST_F(nix_api_expr_test, nix_value_set_get_float)
{
double myDouble = 1.0;
nix_init_float(ctx, value, myDouble);
ASSERT_DOUBLE_EQ(myDouble, nix_get_float(ctx, value));
ASSERT_STREQ("a float", nix_get_typename(ctx, value));
ASSERT_EQ(NIX_TYPE_FLOAT, nix_get_type(ctx, value));
}
TEST_F(nix_api_expr_test, nix_value_set_get_bool_invalid)
{
ASSERT_EQ(false, nix_get_bool(ctx, nullptr));
assert_ctx_err();
ASSERT_EQ(false, nix_get_bool(ctx, value));
assert_ctx_err();
}
TEST_F(nix_api_expr_test, nix_value_set_get_bool)
{
bool myBool = true;
nix_init_bool(ctx, value, myBool);
ASSERT_EQ(myBool, nix_get_bool(ctx, value));
ASSERT_STREQ("a Boolean", nix_get_typename(ctx, value));
ASSERT_EQ(NIX_TYPE_BOOL, nix_get_type(ctx, value));
}
TEST_F(nix_api_expr_test, nix_value_set_get_string_invalid)
{
std::string string_value;
ASSERT_EQ(NIX_ERR_UNKNOWN, nix_get_string(ctx, nullptr, OBSERVE_STRING(string_value)));
assert_ctx_err();
ASSERT_EQ(NIX_ERR_UNKNOWN, nix_get_string(ctx, value, OBSERVE_STRING(string_value)));
assert_ctx_err();
}
TEST_F(nix_api_expr_test, nix_value_set_get_string)
{
std::string string_value;
const char * myString = "some string";
nix_init_string(ctx, value, myString);
nix_get_string(ctx, value, OBSERVE_STRING(string_value));
ASSERT_STREQ(myString, string_value.c_str());
ASSERT_STREQ("a string", nix_get_typename(ctx, value));
ASSERT_EQ(NIX_TYPE_STRING, nix_get_type(ctx, value));
}
TEST_F(nix_api_expr_test, nix_value_set_get_null_invalid)
{
ASSERT_EQ(NULL, nix_get_typename(ctx, value));
assert_ctx_err();
}
TEST_F(nix_api_expr_test, nix_value_set_get_null)
{
nix_init_null(ctx, value);
ASSERT_STREQ("null", nix_get_typename(ctx, value));
ASSERT_EQ(NIX_TYPE_NULL, nix_get_type(ctx, value));
}
TEST_F(nix_api_expr_test, nix_value_set_get_path_invalid)
{
ASSERT_EQ(nullptr, nix_get_path_string(ctx, nullptr));
assert_ctx_err();
ASSERT_EQ(nullptr, nix_get_path_string(ctx, value));
assert_ctx_err();
}
TEST_F(nix_api_expr_test, nix_value_set_get_path)
{
const char * p = "/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname";
nix_init_path_string(ctx, state, value, p);
ASSERT_STREQ(p, nix_get_path_string(ctx, value));
ASSERT_STREQ("a path", nix_get_typename(ctx, value));
ASSERT_EQ(NIX_TYPE_PATH, nix_get_type(ctx, value));
}
TEST_F(nix_api_expr_test, nix_build_and_init_list_invalid)
{
ASSERT_EQ(nullptr, nix_get_list_byidx(ctx, nullptr, state, 0));
assert_ctx_err();
ASSERT_EQ(0, nix_get_list_size(ctx, nullptr));
assert_ctx_err();
ASSERT_EQ(nullptr, nix_get_list_byidx(ctx, value, state, 0));
assert_ctx_err();
ASSERT_EQ(0, nix_get_list_size(ctx, value));
assert_ctx_err();
}
TEST_F(nix_api_expr_test, nix_build_and_init_list)
{
int size = 10;
ListBuilder * builder = nix_make_list_builder(ctx, state, size);
nix_value * intValue = nix_alloc_value(ctx, state);
nix_value * intValue2 = nix_alloc_value(ctx, state);
// `init` and `insert` can be called in any order
nix_init_int(ctx, intValue, 42);
nix_list_builder_insert(ctx, builder, 0, intValue);
nix_list_builder_insert(ctx, builder, 1, intValue2);
nix_init_int(ctx, intValue2, 43);
nix_make_list(ctx, builder, value);
nix_list_builder_free(builder);
ASSERT_EQ(42, nix_get_int(ctx, nix_get_list_byidx(ctx, value, state, 0)));
ASSERT_EQ(43, nix_get_int(ctx, nix_get_list_byidx(ctx, value, state, 1)));
ASSERT_EQ(nullptr, nix_get_list_byidx(ctx, value, state, 2));
ASSERT_EQ(10, nix_get_list_size(ctx, value));
ASSERT_STREQ("a list", nix_get_typename(ctx, value));
ASSERT_EQ(NIX_TYPE_LIST, nix_get_type(ctx, value));
// Clean up
nix_gc_decref(ctx, intValue);
}
TEST_F(nix_api_expr_test, nix_build_and_init_attr_invalid)
{
ASSERT_EQ(nullptr, nix_get_attr_byname(ctx, nullptr, state, 0));
assert_ctx_err();
ASSERT_EQ(nullptr, nix_get_attr_byidx(ctx, nullptr, state, 0, nullptr));
assert_ctx_err();
ASSERT_EQ(nullptr, nix_get_attr_name_byidx(ctx, nullptr, state, 0));
assert_ctx_err();
ASSERT_EQ(0, nix_get_attrs_size(ctx, nullptr));
assert_ctx_err();
ASSERT_EQ(false, nix_has_attr_byname(ctx, nullptr, state, "no-value"));
assert_ctx_err();
ASSERT_EQ(nullptr, nix_get_attr_byname(ctx, value, state, 0));
assert_ctx_err();
ASSERT_EQ(nullptr, nix_get_attr_byidx(ctx, value, state, 0, nullptr));
assert_ctx_err();
ASSERT_EQ(nullptr, nix_get_attr_name_byidx(ctx, value, state, 0));
assert_ctx_err();
ASSERT_EQ(0, nix_get_attrs_size(ctx, value));
assert_ctx_err();
ASSERT_EQ(false, nix_has_attr_byname(ctx, value, state, "no-value"));
assert_ctx_err();
}
TEST_F(nix_api_expr_test, nix_build_and_init_attr)
{
int size = 10;
const char ** out_name = (const char **) malloc(sizeof(char *));
BindingsBuilder * builder = nix_make_bindings_builder(ctx, state, size);
nix_value * intValue = nix_alloc_value(ctx, state);
nix_init_int(ctx, intValue, 42);
nix_value * stringValue = nix_alloc_value(ctx, state);
nix_init_string(ctx, stringValue, "foo");
nix_bindings_builder_insert(ctx, builder, "a", intValue);
nix_bindings_builder_insert(ctx, builder, "b", stringValue);
nix_make_attrs(ctx, value, builder);
nix_bindings_builder_free(builder);
ASSERT_EQ(2, nix_get_attrs_size(ctx, value));
nix_value * out_value = nix_get_attr_byname(ctx, value, state, "a");
ASSERT_EQ(42, nix_get_int(ctx, out_value));
nix_gc_decref(ctx, out_value);
out_value = nix_get_attr_byidx(ctx, value, state, 0, out_name);
ASSERT_EQ(42, nix_get_int(ctx, out_value));
ASSERT_STREQ("a", *out_name);
nix_gc_decref(ctx, out_value);
ASSERT_STREQ("a", nix_get_attr_name_byidx(ctx, value, state, 0));
ASSERT_EQ(true, nix_has_attr_byname(ctx, value, state, "b"));
ASSERT_EQ(false, nix_has_attr_byname(ctx, value, state, "no-value"));
out_value = nix_get_attr_byname(ctx, value, state, "b");
std::string string_value;
nix_get_string(ctx, out_value, OBSERVE_STRING(string_value));
ASSERT_STREQ("foo", string_value.c_str());
nix_gc_decref(nullptr, out_value);
out_value = nix_get_attr_byidx(ctx, value, state, 1, out_name);
nix_get_string(ctx, out_value, OBSERVE_STRING(string_value));
ASSERT_STREQ("foo", string_value.c_str());
ASSERT_STREQ("b", *out_name);
nix_gc_decref(nullptr, out_value);
ASSERT_STREQ("b", nix_get_attr_name_byidx(ctx, value, state, 1));
ASSERT_STREQ("a set", nix_get_typename(ctx, value));
ASSERT_EQ(NIX_TYPE_ATTRS, nix_get_type(ctx, value));
// Clean up
nix_gc_decref(ctx, intValue);
nix_gc_decref(ctx, stringValue);
free(out_name);
}
TEST_F(nix_api_expr_test, nix_value_init)
{
// Setup
// two = 2;
// f = a: a * a;
nix_value * two = nix_alloc_value(ctx, state);
nix_init_int(ctx, two, 2);
nix_value * f = nix_alloc_value(ctx, state);
nix_expr_eval_from_string(
ctx,
state,
R"(
a: a * a
)",
"<test>",
f);
// Test
// r = f two;
nix_value * r = nix_alloc_value(ctx, state);
nix_init_apply(ctx, r, f, two);
assert_ctx_ok();
ValueType t = nix_get_type(ctx, r);
assert_ctx_ok();
ASSERT_EQ(t, NIX_TYPE_THUNK);
nix_value_force(ctx, state, r);
t = nix_get_type(ctx, r);
assert_ctx_ok();
ASSERT_EQ(t, NIX_TYPE_INT);
int n = nix_get_int(ctx, r);
assert_ctx_ok();
ASSERT_EQ(n, 4);
// Clean up
nix_gc_decref(ctx, two);
nix_gc_decref(ctx, f);
nix_gc_decref(ctx, r);
}
TEST_F(nix_api_expr_test, nix_value_init_apply_error)
{
nix_value * some_string = nix_alloc_value(ctx, state);
nix_init_string(ctx, some_string, "some string");
assert_ctx_ok();
nix_value * v = nix_alloc_value(ctx, state);
nix_init_apply(ctx, v, some_string, some_string);
assert_ctx_ok();
// All ok. Call has not been evaluated yet.
// Evaluate it
nix_value_force(ctx, state, v);
ASSERT_EQ(ctx->last_err_code, NIX_ERR_NIX_ERROR);
ASSERT_THAT(ctx->last_err.value(), testing::HasSubstr("attempt to call something which is not a function but"));
// Clean up
nix_gc_decref(ctx, some_string);
nix_gc_decref(ctx, v);
}
TEST_F(nix_api_expr_test, nix_value_init_apply_lazy_arg)
{
// f is a lazy function: it does not evaluate its argument before returning its return value
// g is a helper to produce e
// e is a thunk that throws an exception
//
// r = f e
// r should not throw an exception, because e is not evaluated
nix_value * f = nix_alloc_value(ctx, state);
nix_expr_eval_from_string(
ctx,
state,
R"(
a: { foo = a; }
)",
"<test>",
f);
assert_ctx_ok();
nix_value * e = nix_alloc_value(ctx, state);
{
nix_value * g = nix_alloc_value(ctx, state);
nix_expr_eval_from_string(
ctx,
state,
R"(
_ignore: throw "error message for test case nix_value_init_apply_lazy_arg"
)",
"<test>",
g);
assert_ctx_ok();
nix_init_apply(ctx, e, g, g);
assert_ctx_ok();
nix_gc_decref(ctx, g);
}
nix_value * r = nix_alloc_value(ctx, state);
nix_init_apply(ctx, r, f, e);
assert_ctx_ok();
nix_value_force(ctx, state, r);
assert_ctx_ok();
auto n = nix_get_attrs_size(ctx, r);
assert_ctx_ok();
ASSERT_EQ(1, n);
// nix_get_attr_byname isn't lazy (it could have been) so it will throw the exception
nix_value * foo = nix_get_attr_byname(ctx, r, state, "foo");
ASSERT_EQ(nullptr, foo);
ASSERT_THAT(ctx->last_err.value(), testing::HasSubstr("error message for test case nix_value_init_apply_lazy_arg"));
// Clean up
nix_gc_decref(ctx, f);
nix_gc_decref(ctx, e);
}
TEST_F(nix_api_expr_test, nix_copy_value)
{
nix_value * source = nix_alloc_value(ctx, state);
nix_init_int(ctx, source, 42);
nix_copy_value(ctx, value, source);
ASSERT_EQ(42, nix_get_int(ctx, value));
// Clean up
nix_gc_decref(ctx, source);
}
}

View file

@ -0,0 +1,83 @@
{ lib
, buildPackages
, stdenv
, mkMesonExecutable
, nix-expr
, nix-expr-c
, nix-expr-test-support
, rapidcheck
, gtest
, runCommand
# Configuration Options
, version
, resolvePath
}:
let
inherit (lib) fileset;
in
mkMesonExecutable (finalAttrs: {
pname = "nix-expr-tests";
inherit version;
workDir = ./.;
fileset = fileset.unions [
../../build-utils-meson
./build-utils-meson
../../.version
./.version
./meson.build
# ./meson.options
(fileset.fileFilter (file: file.hasExt "cc") ./.)
(fileset.fileFilter (file: file.hasExt "hh") ./.)
];
buildInputs = [
nix-expr
nix-expr-c
nix-expr-test-support
rapidcheck
gtest
];
preConfigure =
# "Inline" .version so it's not a symlink, and includes the suffix.
# Do the meson utils, without modification.
''
chmod u+w ./.version
echo ${version} > ../../.version
'';
mesonFlags = [
];
env = lib.optionalAttrs (stdenv.isLinux && !(stdenv.hostPlatform.isStatic && stdenv.system == "aarch64-linux")) {
LDFLAGS = "-fuse-ld=gold";
};
passthru = {
tests = {
run = runCommand "${finalAttrs.pname}-run" {
meta.broken = !stdenv.hostPlatform.emulatorAvailable buildPackages;
} (lib.optionalString stdenv.hostPlatform.isWindows ''
export HOME="$PWD/home-dir"
mkdir -p "$HOME"
'' + ''
export _NIX_TEST_UNIT_DATA=${resolvePath ./data}
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
touch $out
'');
};
};
meta = {
platforms = lib.platforms.unix ++ lib.platforms.windows;
mainProgram = finalAttrs.pname + stdenv.hostPlatform.extensions.executable;
};
})

View file

@ -1,7 +1,10 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "libexprtests.hh"
#include "eval-settings.hh"
#include "memory-source-accessor.hh"
#include "tests/libexpr.hh"
namespace nix {
class CaptureLogger : public Logger
@ -15,8 +18,8 @@ namespace nix {
return oss.str();
}
void log(Verbosity lvl, const FormatOrString & fs) override {
oss << fs.s << std::endl;
void log(Verbosity lvl, std::string_view s) override {
oss << s << std::endl;
}
void logEI(const ErrorInfo & ei) override {
@ -69,7 +72,7 @@ namespace nix {
auto v = eval("builtins.tryEval (throw \"\")");
ASSERT_THAT(v, IsAttrsOfSize(2));
auto s = createSymbol("success");
auto p = v.attrs->get(s);
auto p = v.attrs()->get(s);
ASSERT_NE(p, nullptr);
ASSERT_THAT(*p->value, IsFalse());
}
@ -78,17 +81,17 @@ namespace nix {
auto v = eval("builtins.tryEval 123");
ASSERT_THAT(v, IsAttrs());
auto s = createSymbol("success");
auto p = v.attrs->get(s);
auto p = v.attrs()->get(s);
ASSERT_NE(p, nullptr);
ASSERT_THAT(*p->value, IsTrue());
s = createSymbol("value");
p = v.attrs->get(s);
p = v.attrs()->get(s);
ASSERT_NE(p, nullptr);
ASSERT_THAT(*p->value, IsIntEq(123));
}
TEST_F(PrimOpTest, getEnv) {
setenv("_NIX_UNIT_TEST_ENV_VALUE", "test value", 1);
setEnv("_NIX_UNIT_TEST_ENV_VALUE", "test value");
auto v = eval("builtins.getEnv \"_NIX_UNIT_TEST_ENV_VALUE\"");
ASSERT_THAT(v, IsStringEq("test value"));
}
@ -148,23 +151,27 @@ namespace nix {
}
TEST_F(PrimOpTest, unsafeGetAttrPos) {
// The `y` attribute is at position
const char* expr = "builtins.unsafeGetAttrPos \"y\" { y = \"x\"; }";
state.corepkgsFS->addFile(CanonPath("foo.nix"), "\n\r\n\r{ y = \"x\"; }");
auto expr = "builtins.unsafeGetAttrPos \"y\" (import <nix/foo.nix>)";
auto v = eval(expr);
ASSERT_THAT(v, IsAttrsOfSize(3));
auto file = v.attrs->find(createSymbol("file"));
auto file = v.attrs()->find(createSymbol("file"));
ASSERT_NE(file, nullptr);
// FIXME: The file when running these tests is the input string?!?
ASSERT_THAT(*file->value, IsStringEq(expr));
ASSERT_THAT(*file->value, IsString());
auto s = baseNameOf(file->value->string_view());
ASSERT_EQ(s, "foo.nix");
auto line = v.attrs->find(createSymbol("line"));
auto line = v.attrs()->find(createSymbol("line"));
ASSERT_NE(line, nullptr);
ASSERT_THAT(*line->value, IsIntEq(1));
state.forceValue(*line->value, noPos);
ASSERT_THAT(*line->value, IsIntEq(4));
auto column = v.attrs->find(createSymbol("column"));
auto column = v.attrs()->find(createSymbol("column"));
ASSERT_NE(column, nullptr);
ASSERT_THAT(*column->value, IsIntEq(33));
state.forceValue(*column->value, noPos);
ASSERT_THAT(*column->value, IsIntEq(3));
}
TEST_F(PrimOpTest, hasAttr) {
@ -195,14 +202,14 @@ namespace nix {
TEST_F(PrimOpTest, removeAttrsRetains) {
auto v = eval("builtins.removeAttrs { x = 1; y = 2; } [\"x\"]");
ASSERT_THAT(v, IsAttrsOfSize(1));
ASSERT_NE(v.attrs->find(createSymbol("y")), nullptr);
ASSERT_NE(v.attrs()->find(createSymbol("y")), nullptr);
}
TEST_F(PrimOpTest, listToAttrsEmptyList) {
auto v = eval("builtins.listToAttrs []");
ASSERT_THAT(v, IsAttrsOfSize(0));
ASSERT_EQ(v.type(), nAttrs);
ASSERT_EQ(v.attrs->size(), 0);
ASSERT_EQ(v.attrs()->size(), 0);
}
TEST_F(PrimOpTest, listToAttrsNotFieldName) {
@ -212,7 +219,7 @@ namespace nix {
TEST_F(PrimOpTest, listToAttrs) {
auto v = eval("builtins.listToAttrs [ { name = \"key\"; value = 123; } ]");
ASSERT_THAT(v, IsAttrsOfSize(1));
auto key = v.attrs->find(createSymbol("key"));
auto key = v.attrs()->find(createSymbol("key"));
ASSERT_NE(key, nullptr);
ASSERT_THAT(*key->value, IsIntEq(123));
}
@ -220,7 +227,7 @@ namespace nix {
TEST_F(PrimOpTest, intersectAttrs) {
auto v = eval("builtins.intersectAttrs { a = 1; b = 2; } { b = 3; c = 4; }");
ASSERT_THAT(v, IsAttrsOfSize(1));
auto b = v.attrs->find(createSymbol("b"));
auto b = v.attrs()->find(createSymbol("b"));
ASSERT_NE(b, nullptr);
ASSERT_THAT(*b->value, IsIntEq(3));
}
@ -236,11 +243,11 @@ namespace nix {
auto v = eval("builtins.functionArgs ({ x, y ? 123}: 1)");
ASSERT_THAT(v, IsAttrsOfSize(2));
auto x = v.attrs->find(createSymbol("x"));
auto x = v.attrs()->find(createSymbol("x"));
ASSERT_NE(x, nullptr);
ASSERT_THAT(*x->value, IsFalse());
auto y = v.attrs->find(createSymbol("y"));
auto y = v.attrs()->find(createSymbol("y"));
ASSERT_NE(y, nullptr);
ASSERT_THAT(*y->value, IsTrue());
}
@ -249,13 +256,13 @@ namespace nix {
auto v = eval("builtins.mapAttrs (name: value: value * 10) { a = 1; b = 2; }");
ASSERT_THAT(v, IsAttrsOfSize(2));
auto a = v.attrs->find(createSymbol("a"));
auto a = v.attrs()->find(createSymbol("a"));
ASSERT_NE(a, nullptr);
ASSERT_THAT(*a->value, IsThunk());
state.forceValue(*a->value, noPos);
ASSERT_THAT(*a->value, IsIntEq(10));
auto b = v.attrs->find(createSymbol("b"));
auto b = v.attrs()->find(createSymbol("b"));
ASSERT_NE(b, nullptr);
ASSERT_THAT(*b->value, IsThunk());
state.forceValue(*b->value, noPos);
@ -403,13 +410,13 @@ namespace nix {
auto v = eval("builtins.partition (x: x > 10) [1 23 9 3 42]");
ASSERT_THAT(v, IsAttrsOfSize(2));
auto right = v.attrs->get(createSymbol("right"));
auto right = v.attrs()->get(createSymbol("right"));
ASSERT_NE(right, nullptr);
ASSERT_THAT(*right->value, IsListOfSize(2));
ASSERT_THAT(*right->value->listElems()[0], IsIntEq(23));
ASSERT_THAT(*right->value->listElems()[1], IsIntEq(42));
auto wrong = v.attrs->get(createSymbol("wrong"));
auto wrong = v.attrs()->get(createSymbol("wrong"));
ASSERT_NE(wrong, nullptr);
ASSERT_EQ(wrong->value->type(), nList);
ASSERT_EQ(wrong->value->listSize(), 3);
@ -599,7 +606,7 @@ namespace nix {
ASSERT_THAT(v, IsStringEq("401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429080fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1"));
}
TEST_F(PrimOpTest, hashStringInvalidHashType) {
TEST_F(PrimOpTest, hashStringInvalidHashAlgorithm) {
ASSERT_THROW(eval("builtins.hashString \"foobar\" \"asdf\""), Error);
}
@ -617,7 +624,7 @@ namespace nix {
TEST_F(PrimOpTest, storeDir) {
auto v = eval("builtins.storeDir");
ASSERT_THAT(v, IsStringEq("/nix/store"));
ASSERT_THAT(v, IsStringEq(settings.nixStore));
}
TEST_F(PrimOpTest, nixVersion) {
@ -627,21 +634,21 @@ namespace nix {
TEST_F(PrimOpTest, currentSystem) {
auto v = eval("builtins.currentSystem");
ASSERT_THAT(v, IsStringEq(settings.thisSystem.get()));
ASSERT_THAT(v, IsStringEq(evalSettings.getCurrentSystem()));
}
TEST_F(PrimOpTest, derivation) {
auto v = eval("derivation");
ASSERT_EQ(v.type(), nFunction);
ASSERT_TRUE(v.isLambda());
ASSERT_NE(v.lambda.fun, nullptr);
ASSERT_TRUE(v.lambda.fun->hasFormals());
ASSERT_NE(v.payload.lambda.fun, nullptr);
ASSERT_TRUE(v.payload.lambda.fun->hasFormals());
}
TEST_F(PrimOpTest, currentTime) {
auto v = eval("builtins.currentTime");
ASSERT_EQ(v.type(), nInt);
ASSERT_TRUE(v.integer > 0);
ASSERT_TRUE(v.integer() > 0);
}
TEST_F(PrimOpTest, splitVersion) {
@ -702,11 +709,11 @@ namespace nix {
auto v = eval(expr);
ASSERT_THAT(v, IsAttrsOfSize(2));
auto name = v.attrs->find(createSymbol("name"));
auto name = v.attrs()->find(createSymbol("name"));
ASSERT_TRUE(name);
ASSERT_THAT(*name->value, IsStringEq(expectedName));
auto version = v.attrs->find(createSymbol("version"));
auto version = v.attrs()->find(createSymbol("version"));
ASSERT_TRUE(version);
ASSERT_THAT(*version->value, IsStringEq(expectedVersion));
}
@ -724,14 +731,14 @@ namespace nix {
// FIXME: add a test that verifies the string context is as expected
auto v = eval("builtins.replaceStrings [\"oo\" \"a\"] [\"a\" \"i\"] \"foobar\"");
ASSERT_EQ(v.type(), nString);
ASSERT_EQ(v.string.s, std::string_view("fabir"));
ASSERT_EQ(v.string_view(), "fabir");
}
TEST_F(PrimOpTest, concatStringsSep) {
// FIXME: add a test that verifies the string context is as expected
auto v = eval("builtins.concatStringsSep \"%\" [\"foo\" \"bar\" \"baz\"]");
ASSERT_EQ(v.type(), nString);
ASSERT_EQ(std::string_view(v.string.s), "foo%bar%baz");
ASSERT_EQ(v.string_view(), "foo%bar%baz");
}
TEST_F(PrimOpTest, split1) {
@ -827,6 +834,14 @@ namespace nix {
ASSERT_THAT(*v.listElems()[0], IsStringEq("FOO"));
}
TEST_F(PrimOpTest, match5) {
// The regex "\\{}" is valid and matches the string "{}".
// Caused a regression before when trying to switch from std::regex to boost::regex.
// See https://github.com/NixOS/nix/pull/7762#issuecomment-1834303659
auto v = eval("builtins.match \"\\\\{}\" \"{}\"");
ASSERT_THAT(v, IsListOfSize(0));
}
TEST_F(PrimOpTest, attrNames) {
auto v = eval("builtins.attrNames { x = 1; y = 2; z = 3; a = 2; }");
ASSERT_THAT(v, IsListOfSize(4));
@ -836,4 +851,10 @@ namespace nix {
for (const auto [n, elem] : enumerate(v.listItems()))
ASSERT_THAT(*elem, IsStringEq(expected[n]));
}
TEST_F(PrimOpTest, genericClosure_not_strict) {
// Operator should not be used when startSet is empty
auto v = eval("builtins.genericClosure { startSet = []; }");
ASSERT_THAT(v, IsListOfSize(0));
}
} /* namespace nix */

View file

@ -0,0 +1,90 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "search-path.hh"
namespace nix {
TEST(LookupPathElem, parse_justPath) {
ASSERT_EQ(
LookupPath::Elem::parse("foo"),
(LookupPath::Elem {
.prefix = LookupPath::Prefix { .s = "" },
.path = LookupPath::Path { .s = "foo" },
}));
}
TEST(LookupPathElem, parse_emptyPrefix) {
ASSERT_EQ(
LookupPath::Elem::parse("=foo"),
(LookupPath::Elem {
.prefix = LookupPath::Prefix { .s = "" },
.path = LookupPath::Path { .s = "foo" },
}));
}
TEST(LookupPathElem, parse_oneEq) {
ASSERT_EQ(
LookupPath::Elem::parse("foo=bar"),
(LookupPath::Elem {
.prefix = LookupPath::Prefix { .s = "foo" },
.path = LookupPath::Path { .s = "bar" },
}));
}
TEST(LookupPathElem, parse_twoEqs) {
ASSERT_EQ(
LookupPath::Elem::parse("foo=bar=baz"),
(LookupPath::Elem {
.prefix = LookupPath::Prefix { .s = "foo" },
.path = LookupPath::Path { .s = "bar=baz" },
}));
}
TEST(LookupPathElem, suffixIfPotentialMatch_justPath) {
LookupPath::Prefix prefix { .s = "" };
ASSERT_EQ(prefix.suffixIfPotentialMatch("any/thing"), std::optional { "any/thing" });
}
TEST(LookupPathElem, suffixIfPotentialMatch_misleadingPrefix1) {
LookupPath::Prefix prefix { .s = "foo" };
ASSERT_EQ(prefix.suffixIfPotentialMatch("fooX"), std::nullopt);
}
TEST(LookupPathElem, suffixIfPotentialMatch_misleadingPrefix2) {
LookupPath::Prefix prefix { .s = "foo" };
ASSERT_EQ(prefix.suffixIfPotentialMatch("fooX/bar"), std::nullopt);
}
TEST(LookupPathElem, suffixIfPotentialMatch_partialPrefix) {
LookupPath::Prefix prefix { .s = "fooX" };
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo"), std::nullopt);
}
TEST(LookupPathElem, suffixIfPotentialMatch_exactPrefix) {
LookupPath::Prefix prefix { .s = "foo" };
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo"), std::optional { "" });
}
TEST(LookupPathElem, suffixIfPotentialMatch_multiKey) {
LookupPath::Prefix prefix { .s = "foo/bar" };
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo/bar/baz"), std::optional { "baz" });
}
TEST(LookupPathElem, suffixIfPotentialMatch_trailingSlash) {
LookupPath::Prefix prefix { .s = "foo" };
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo/"), std::optional { "" });
}
TEST(LookupPathElem, suffixIfPotentialMatch_trailingDoubleSlash) {
LookupPath::Prefix prefix { .s = "foo" };
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo//"), std::optional { "/" });
}
TEST(LookupPathElem, suffixIfPotentialMatch_trailingPath) {
LookupPath::Prefix prefix { .s = "foo" };
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo/bar/baz"), std::optional { "bar/baz" });
}
}

View file

@ -1,4 +1,4 @@
#include "libexprtests.hh"
#include "tests/libexpr.hh"
namespace nix {
// Testing of trivial expressions
@ -62,11 +62,11 @@ namespace nix {
TEST_F(TrivialExpressionTest, updateAttrs) {
auto v = eval("{ a = 1; } // { b = 2; a = 3; }");
ASSERT_THAT(v, IsAttrsOfSize(2));
auto a = v.attrs->find(createSymbol("a"));
auto a = v.attrs()->find(createSymbol("a"));
ASSERT_NE(a, nullptr);
ASSERT_THAT(*a->value, IsIntEq(3));
auto b = v.attrs->find(createSymbol("b"));
auto b = v.attrs()->find(createSymbol("b"));
ASSERT_NE(b, nullptr);
ASSERT_THAT(*b->value, IsIntEq(2));
}
@ -151,7 +151,7 @@ namespace nix {
auto v = eval(expr);
ASSERT_THAT(v, IsAttrsOfSize(1));
auto a = v.attrs->find(createSymbol("a"));
auto a = v.attrs()->find(createSymbol("a"));
ASSERT_NE(a, nullptr);
ASSERT_THAT(*a->value, IsThunk());
@ -159,11 +159,11 @@ namespace nix {
ASSERT_THAT(*a->value, IsAttrsOfSize(2));
auto b = a->value->attrs->find(createSymbol("b"));
auto b = a->value->attrs()->find(createSymbol("b"));
ASSERT_NE(b, nullptr);
ASSERT_THAT(*b->value, IsIntEq(1));
auto c = a->value->attrs->find(createSymbol("c"));
auto c = a->value->attrs()->find(createSymbol("c"));
ASSERT_NE(c, nullptr);
ASSERT_THAT(*c->value, IsIntEq(2));
}
@ -182,10 +182,64 @@ namespace nix {
ASSERT_THAT(v, IsIntEq(15));
}
TEST_F(TrivialExpressionTest, forwardPipe) {
auto v = eval("1 |> builtins.add 2 |> builtins.mul 3");
ASSERT_THAT(v, IsIntEq(9));
}
TEST_F(TrivialExpressionTest, backwardPipe) {
auto v = eval("builtins.add 1 <| builtins.mul 2 <| 3");
ASSERT_THAT(v, IsIntEq(7));
}
TEST_F(TrivialExpressionTest, forwardPipeEvaluationOrder) {
auto v = eval("1 |> null |> (x: 2)");
ASSERT_THAT(v, IsIntEq(2));
}
TEST_F(TrivialExpressionTest, backwardPipeEvaluationOrder) {
auto v = eval("(x: 1) <| null <| 2");
ASSERT_THAT(v, IsIntEq(1));
}
TEST_F(TrivialExpressionTest, differentPipeOperatorsDoNotAssociate) {
ASSERT_THROW(eval("(x: 1) <| 2 |> (x: 3)"), ParseError);
}
TEST_F(TrivialExpressionTest, differentPipeOperatorsParensLeft) {
auto v = eval("((x: 1) <| 2) |> (x: 3)");
ASSERT_THAT(v, IsIntEq(3));
}
TEST_F(TrivialExpressionTest, differentPipeOperatorsParensRight) {
auto v = eval("(x: 1) <| (2 |> (x: 3))");
ASSERT_THAT(v, IsIntEq(1));
}
TEST_F(TrivialExpressionTest, forwardPipeLowestPrecedence) {
auto v = eval("false -> true |> (x: !x)");
ASSERT_THAT(v, IsFalse());
}
TEST_F(TrivialExpressionTest, backwardPipeLowestPrecedence) {
auto v = eval("(x: !x) <| false -> true");
ASSERT_THAT(v, IsFalse());
}
TEST_F(TrivialExpressionTest, forwardPipeStrongerThanElse) {
auto v = eval("if true then 1 else 2 |> 3");
ASSERT_THAT(v, IsIntEq(1));
}
TEST_F(TrivialExpressionTest, backwardPipeStrongerThanElse) {
auto v = eval("if true then 1 else 2 <| 3");
ASSERT_THAT(v, IsIntEq(1));
}
TEST_F(TrivialExpressionTest, bindOr) {
auto v = eval("{ or = 1; }");
ASSERT_THAT(v, IsAttrsOfSize(1));
auto b = v.attrs->find(createSymbol("or"));
auto b = v.attrs()->find(createSymbol("or"));
ASSERT_NE(b, nullptr);
ASSERT_THAT(*b->value, IsIntEq(1));
}

View file

@ -0,0 +1,132 @@
#include <nlohmann/json.hpp>
#include <gtest/gtest.h>
#include <rapidcheck/gtest.h>
#include "tests/path.hh"
#include "tests/libexpr.hh"
#include "tests/value/context.hh"
namespace nix {
// Test a few cases of invalid string context elements.
TEST(NixStringContextElemTest, empty_invalid) {
EXPECT_THROW(
NixStringContextElem::parse(""),
BadNixStringContextElem);
}
TEST(NixStringContextElemTest, single_bang_invalid) {
EXPECT_THROW(
NixStringContextElem::parse("!"),
BadNixStringContextElem);
}
TEST(NixStringContextElemTest, double_bang_invalid) {
EXPECT_THROW(
NixStringContextElem::parse("!!/"),
BadStorePath);
}
TEST(NixStringContextElemTest, eq_slash_invalid) {
EXPECT_THROW(
NixStringContextElem::parse("=/"),
BadStorePath);
}
TEST(NixStringContextElemTest, slash_invalid) {
EXPECT_THROW(
NixStringContextElem::parse("/"),
BadStorePath);
}
/**
* Round trip (string <-> data structure) test for
* `NixStringContextElem::Opaque`.
*/
TEST(NixStringContextElemTest, opaque) {
std::string_view opaque = "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x";
auto elem = NixStringContextElem::parse(opaque);
auto * p = std::get_if<NixStringContextElem::Opaque>(&elem.raw);
ASSERT_TRUE(p);
ASSERT_EQ(p->path, StorePath { opaque });
ASSERT_EQ(elem.to_string(), opaque);
}
/**
* Round trip (string <-> data structure) test for
* `NixStringContextElem::DrvDeep`.
*/
TEST(NixStringContextElemTest, drvDeep) {
std::string_view drvDeep = "=g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv";
auto elem = NixStringContextElem::parse(drvDeep);
auto * p = std::get_if<NixStringContextElem::DrvDeep>(&elem.raw);
ASSERT_TRUE(p);
ASSERT_EQ(p->drvPath, StorePath { drvDeep.substr(1) });
ASSERT_EQ(elem.to_string(), drvDeep);
}
/**
* Round trip (string <-> data structure) test for a simpler
* `NixStringContextElem::Built`.
*/
TEST(NixStringContextElemTest, built_opaque) {
std::string_view built = "!foo!g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv";
auto elem = NixStringContextElem::parse(built);
auto * p = std::get_if<NixStringContextElem::Built>(&elem.raw);
ASSERT_TRUE(p);
ASSERT_EQ(p->output, "foo");
ASSERT_EQ(*p->drvPath, ((SingleDerivedPath) SingleDerivedPath::Opaque {
.path = StorePath { built.substr(5) },
}));
ASSERT_EQ(elem.to_string(), built);
}
/**
* Round trip (string <-> data structure) test for a more complex,
* inductive `NixStringContextElem::Built`.
*/
TEST(NixStringContextElemTest, built_built) {
/**
* We set these in tests rather than the regular globals so we don't have
* to worry about race conditions if the tests run concurrently.
*/
ExperimentalFeatureSettings mockXpSettings;
mockXpSettings.set("experimental-features", "dynamic-derivations ca-derivations");
std::string_view built = "!foo!bar!g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv";
auto elem = NixStringContextElem::parse(built, mockXpSettings);
auto * p = std::get_if<NixStringContextElem::Built>(&elem.raw);
ASSERT_TRUE(p);
ASSERT_EQ(p->output, "foo");
auto * drvPath = std::get_if<SingleDerivedPath::Built>(&*p->drvPath);
ASSERT_TRUE(drvPath);
ASSERT_EQ(drvPath->output, "bar");
ASSERT_EQ(*drvPath->drvPath, ((SingleDerivedPath) SingleDerivedPath::Opaque {
.path = StorePath { built.substr(9) },
}));
ASSERT_EQ(elem.to_string(), built);
}
/**
* Without the right experimental features enabled, we cannot parse a
* complex inductive string context element.
*/
TEST(NixStringContextElemTest, built_built_xp) {
ASSERT_THROW(
NixStringContextElem::parse("!foo!bar!g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv"), MissingExperimentalFeature);
}
#ifndef COVERAGE
RC_GTEST_PROP(
NixStringContextElemTest,
prop_round_rip,
(const NixStringContextElem & o))
{
RC_ASSERT(o == NixStringContextElem::parse(o.to_string()));
}
#endif
}

View file

@ -0,0 +1,788 @@
#include "tests/libexpr.hh"
#include "value.hh"
#include "print.hh"
namespace nix {
using namespace testing;
struct ValuePrintingTests : LibExprTest
{
template<class... A>
void test(Value v, std::string_view expected, A... args)
{
std::stringstream out;
v.print(state, out, args...);
ASSERT_EQ(out.str(), expected);
}
};
TEST_F(ValuePrintingTests, tInt)
{
Value vInt;
vInt.mkInt(10);
test(vInt, "10");
}
TEST_F(ValuePrintingTests, tBool)
{
Value vBool;
vBool.mkBool(true);
test(vBool, "true");
}
TEST_F(ValuePrintingTests, tString)
{
Value vString;
vString.mkString("some-string");
test(vString, "\"some-string\"");
}
TEST_F(ValuePrintingTests, tPath)
{
Value vPath;
vPath.mkString("/foo");
test(vPath, "\"/foo\"");
}
TEST_F(ValuePrintingTests, tNull)
{
Value vNull;
vNull.mkNull();
test(vNull, "null");
}
TEST_F(ValuePrintingTests, tAttrs)
{
Value vOne;
vOne.mkInt(1);
Value vTwo;
vTwo.mkInt(2);
BindingsBuilder builder(state, state.allocBindings(10));
builder.insert(state.symbols.create("one"), &vOne);
builder.insert(state.symbols.create("two"), &vTwo);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
test(vAttrs, "{ one = 1; two = 2; }");
}
TEST_F(ValuePrintingTests, tList)
{
Value vOne;
vOne.mkInt(1);
Value vTwo;
vTwo.mkInt(2);
auto list = state.buildList(3);
list.elems[0] = &vOne;
list.elems[1] = &vTwo;
Value vList;
vList.mkList(list);
test(vList, "[ 1 2 «nullptr» ]");
}
TEST_F(ValuePrintingTests, vThunk)
{
Value vThunk;
vThunk.mkThunk(nullptr, nullptr);
test(vThunk, "«thunk»");
}
TEST_F(ValuePrintingTests, vApp)
{
Value vApp;
vApp.mkApp(nullptr, nullptr);
test(vApp, "«thunk»");
}
TEST_F(ValuePrintingTests, vLambda)
{
Env env {
.up = nullptr,
.values = { }
};
PosTable::Origin origin = state.positions.addOrigin(std::monostate(), 1);
auto posIdx = state.positions.add(origin, 0);
auto body = ExprInt(0);
auto formals = Formals {};
ExprLambda eLambda(posIdx, createSymbol("a"), &formals, &body);
Value vLambda;
vLambda.mkLambda(&env, &eLambda);
test(vLambda, "«lambda @ «none»:1:1»");
eLambda.setName(createSymbol("puppy"));
test(vLambda, "«lambda puppy @ «none»:1:1»");
}
TEST_F(ValuePrintingTests, vPrimOp)
{
Value vPrimOp;
PrimOp primOp{
.name = "puppy"
};
vPrimOp.mkPrimOp(&primOp);
test(vPrimOp, "«primop puppy»");
}
TEST_F(ValuePrintingTests, vPrimOpApp)
{
PrimOp primOp{
.name = "puppy"
};
Value vPrimOp;
vPrimOp.mkPrimOp(&primOp);
Value vPrimOpApp;
vPrimOpApp.mkPrimOpApp(&vPrimOp, nullptr);
test(vPrimOpApp, "«partially applied primop puppy»");
}
TEST_F(ValuePrintingTests, vExternal)
{
struct MyExternal : ExternalValueBase
{
public:
std::string showType() const override
{
return "";
}
std::string typeOf() const override
{
return "";
}
virtual std::ostream & print(std::ostream & str) const override
{
str << "testing-external!";
return str;
}
} myExternal;
Value vExternal;
vExternal.mkExternal(&myExternal);
test(vExternal, "testing-external!");
}
TEST_F(ValuePrintingTests, vFloat)
{
Value vFloat;
vFloat.mkFloat(2.0);
test(vFloat, "2");
}
TEST_F(ValuePrintingTests, vBlackhole)
{
Value vBlackhole;
vBlackhole.mkBlackhole();
test(vBlackhole, "«potential infinite recursion»");
}
TEST_F(ValuePrintingTests, depthAttrs)
{
Value vOne;
vOne.mkInt(1);
Value vTwo;
vTwo.mkInt(2);
BindingsBuilder builderEmpty(state, state.allocBindings(0));
Value vAttrsEmpty;
vAttrsEmpty.mkAttrs(builderEmpty.finish());
BindingsBuilder builder(state, state.allocBindings(10));
builder.insert(state.symbols.create("one"), &vOne);
builder.insert(state.symbols.create("two"), &vTwo);
builder.insert(state.symbols.create("nested"), &vAttrsEmpty);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
BindingsBuilder builder2(state, state.allocBindings(10));
builder2.insert(state.symbols.create("one"), &vOne);
builder2.insert(state.symbols.create("two"), &vTwo);
builder2.insert(state.symbols.create("nested"), &vAttrs);
Value vNested;
vNested.mkAttrs(builder2.finish());
test(vNested, "{ nested = { ... }; one = 1; two = 2; }", PrintOptions { .maxDepth = 1 });
test(vNested, "{ nested = { nested = { ... }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 2 });
test(vNested, "{ nested = { nested = { }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 3 });
test(vNested, "{ nested = { nested = { }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 4 });
}
TEST_F(ValuePrintingTests, depthList)
{
Value vOne;
vOne.mkInt(1);
Value vTwo;
vTwo.mkInt(2);
BindingsBuilder builder(state, state.allocBindings(10));
builder.insert(state.symbols.create("one"), &vOne);
builder.insert(state.symbols.create("two"), &vTwo);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
BindingsBuilder builder2(state, state.allocBindings(10));
builder2.insert(state.symbols.create("one"), &vOne);
builder2.insert(state.symbols.create("two"), &vTwo);
builder2.insert(state.symbols.create("nested"), &vAttrs);
Value vNested;
vNested.mkAttrs(builder2.finish());
auto list = state.buildList(3);
list.elems[0] = &vOne;
list.elems[1] = &vTwo;
list.elems[2] = &vNested;
Value vList;
vList.mkList(list);
test(vList, "[ 1 2 { ... } ]", PrintOptions { .maxDepth = 1 });
test(vList, "[ 1 2 { nested = { ... }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 2 });
test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 3 });
test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 4 });
test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 5 });
}
struct StringPrintingTests : LibExprTest
{
template<class... A>
void test(std::string_view literal, std::string_view expected, unsigned int maxLength, A... args)
{
Value v;
v.mkString(literal);
std::stringstream out;
printValue(state, out, v, PrintOptions {
.maxStringLength = maxLength
});
ASSERT_EQ(out.str(), expected);
}
};
TEST_F(StringPrintingTests, maxLengthTruncation)
{
test("abcdefghi", "\"abcdefghi\"", 10);
test("abcdefghij", "\"abcdefghij\"", 10);
test("abcdefghijk", "\"abcdefghij\" «1 byte elided»", 10);
test("abcdefghijkl", "\"abcdefghij\" «2 bytes elided»", 10);
test("abcdefghijklm", "\"abcdefghij\" «3 bytes elided»", 10);
}
// Check that printing an attrset shows 'important' attributes like `type`
// first, but only reorder the attrs when we have a maxAttrs budget.
TEST_F(ValuePrintingTests, attrsTypeFirst)
{
Value vType;
vType.mkString("puppy");
Value vApple;
vApple.mkString("apple");
BindingsBuilder builder(state, state.allocBindings(10));
builder.insert(state.symbols.create("type"), &vType);
builder.insert(state.symbols.create("apple"), &vApple);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
test(vAttrs,
"{ type = \"puppy\"; apple = \"apple\"; }",
PrintOptions {
.maxAttrs = 100
});
test(vAttrs,
"{ apple = \"apple\"; type = \"puppy\"; }",
PrintOptions { });
}
TEST_F(ValuePrintingTests, ansiColorsInt)
{
Value v;
v.mkInt(10);
test(v,
ANSI_CYAN "10" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsFloat)
{
Value v;
v.mkFloat(1.6);
test(v,
ANSI_CYAN "1.6" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsBool)
{
Value v;
v.mkBool(true);
test(v,
ANSI_CYAN "true" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsString)
{
Value v;
v.mkString("puppy");
test(v,
ANSI_MAGENTA "\"puppy\"" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsStringElided)
{
Value v;
v.mkString("puppy");
test(v,
ANSI_MAGENTA "\"pup\" " ANSI_FAINT "«2 bytes elided»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true,
.maxStringLength = 3
});
}
TEST_F(ValuePrintingTests, ansiColorsPath)
{
Value v;
v.mkPath(state.rootPath(CanonPath("puppy")));
test(v,
ANSI_GREEN "/puppy" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsNull)
{
Value v;
v.mkNull();
test(v,
ANSI_CYAN "null" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsAttrs)
{
Value vOne;
vOne.mkInt(1);
Value vTwo;
vTwo.mkInt(2);
BindingsBuilder builder(state, state.allocBindings(10));
builder.insert(state.symbols.create("one"), &vOne);
builder.insert(state.symbols.create("two"), &vTwo);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
test(vAttrs,
"{ one = " ANSI_CYAN "1" ANSI_NORMAL "; two = " ANSI_CYAN "2" ANSI_NORMAL "; }",
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsDerivation)
{
Value vDerivation;
vDerivation.mkString("derivation");
BindingsBuilder builder(state, state.allocBindings(10));
builder.insert(state.sType, &vDerivation);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
test(vAttrs,
ANSI_GREEN "«derivation»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true,
.force = true,
.derivationPaths = true
});
test(vAttrs,
"{ type = " ANSI_MAGENTA "\"derivation\"" ANSI_NORMAL "; }",
PrintOptions {
.ansiColors = true,
.force = true
});
}
TEST_F(ValuePrintingTests, ansiColorsError)
{
Value throw_ = state.getBuiltin("throw");
Value message;
message.mkString("uh oh!");
Value vError;
vError.mkApp(&throw_, &message);
test(vError,
ANSI_RED
"«error: uh oh!»"
ANSI_NORMAL,
PrintOptions {
.ansiColors = true,
.force = true,
});
}
TEST_F(ValuePrintingTests, ansiColorsDerivationError)
{
Value throw_ = state.getBuiltin("throw");
Value message;
message.mkString("uh oh!");
Value vError;
vError.mkApp(&throw_, &message);
Value vDerivation;
vDerivation.mkString("derivation");
BindingsBuilder builder(state, state.allocBindings(10));
builder.insert(state.sType, &vDerivation);
builder.insert(state.sDrvPath, &vError);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
test(vAttrs,
"{ drvPath = "
ANSI_RED
"«error: uh oh!»"
ANSI_NORMAL
"; type = "
ANSI_MAGENTA
"\"derivation\""
ANSI_NORMAL
"; }",
PrintOptions {
.ansiColors = true,
.force = true
});
test(vAttrs,
ANSI_RED
"«error: uh oh!»"
ANSI_NORMAL,
PrintOptions {
.ansiColors = true,
.force = true,
.derivationPaths = true,
});
}
TEST_F(ValuePrintingTests, ansiColorsAssert)
{
ExprVar eFalse(state.symbols.create("false"));
eFalse.bindVars(state, state.staticBaseEnv);
ExprInt eInt(1);
ExprAssert expr(noPos, &eFalse, &eInt);
Value v;
state.mkThunk_(v, &expr);
test(v,
ANSI_RED "«error: assertion 'false' failed»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true,
.force = true
});
}
TEST_F(ValuePrintingTests, ansiColorsList)
{
Value vOne;
vOne.mkInt(1);
Value vTwo;
vTwo.mkInt(2);
auto list = state.buildList(3);
list.elems[0] = &vOne;
list.elems[1] = &vTwo;
Value vList;
vList.mkList(list);
test(vList,
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_CYAN "2" ANSI_NORMAL " " ANSI_MAGENTA "«nullptr»" ANSI_NORMAL " ]",
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsLambda)
{
Env env {
.up = nullptr,
.values = { }
};
PosTable::Origin origin = state.positions.addOrigin(std::monostate(), 1);
auto posIdx = state.positions.add(origin, 0);
auto body = ExprInt(0);
auto formals = Formals {};
ExprLambda eLambda(posIdx, createSymbol("a"), &formals, &body);
Value vLambda;
vLambda.mkLambda(&env, &eLambda);
test(vLambda,
ANSI_BLUE "«lambda @ «none»:1:1»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true,
.force = true
});
eLambda.setName(createSymbol("puppy"));
test(vLambda,
ANSI_BLUE "«lambda puppy @ «none»:1:1»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true,
.force = true
});
}
TEST_F(ValuePrintingTests, ansiColorsPrimOp)
{
PrimOp primOp{
.name = "puppy"
};
Value v;
v.mkPrimOp(&primOp);
test(v,
ANSI_BLUE "«primop puppy»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsPrimOpApp)
{
PrimOp primOp{
.name = "puppy"
};
Value vPrimOp;
vPrimOp.mkPrimOp(&primOp);
Value v;
v.mkPrimOpApp(&vPrimOp, nullptr);
test(v,
ANSI_BLUE "«partially applied primop puppy»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsThunk)
{
Value v;
v.mkThunk(nullptr, nullptr);
test(v,
ANSI_MAGENTA "«thunk»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsBlackhole)
{
Value v;
v.mkBlackhole();
test(v,
ANSI_RED "«potential infinite recursion»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsAttrsRepeated)
{
BindingsBuilder emptyBuilder(state, state.allocBindings(1));
Value vEmpty;
vEmpty.mkAttrs(emptyBuilder.finish());
BindingsBuilder builder(state, state.allocBindings(10));
builder.insert(state.symbols.create("a"), &vEmpty);
builder.insert(state.symbols.create("b"), &vEmpty);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
test(vAttrs,
"{ a = { }; b = " ANSI_MAGENTA "«repeated»" ANSI_NORMAL "; }",
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsListRepeated)
{
BindingsBuilder emptyBuilder(state, state.allocBindings(1));
Value vEmpty;
vEmpty.mkAttrs(emptyBuilder.finish());
auto list = state.buildList(2);
list.elems[0] = &vEmpty;
list.elems[1] = &vEmpty;
Value vList;
vList.mkList(list);
test(vList,
"[ { } " ANSI_MAGENTA "«repeated»" ANSI_NORMAL " ]",
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, listRepeated)
{
BindingsBuilder emptyBuilder(state, state.allocBindings(1));
Value vEmpty;
vEmpty.mkAttrs(emptyBuilder.finish());
auto list = state.buildList(2);
list.elems[0] = &vEmpty;
list.elems[1] = &vEmpty;
Value vList;
vList.mkList(list);
test(vList, "[ { } «repeated» ]", PrintOptions { });
test(vList,
"[ { } { } ]",
PrintOptions {
.trackRepeated = false
});
}
TEST_F(ValuePrintingTests, ansiColorsAttrsElided)
{
Value vOne;
vOne.mkInt(1);
Value vTwo;
vTwo.mkInt(2);
BindingsBuilder builder(state, state.allocBindings(10));
builder.insert(state.symbols.create("one"), &vOne);
builder.insert(state.symbols.create("two"), &vTwo);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
test(vAttrs,
"{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT "«1 attribute elided»" ANSI_NORMAL " }",
PrintOptions {
.ansiColors = true,
.maxAttrs = 1
});
Value vThree;
vThree.mkInt(3);
builder.insert(state.symbols.create("three"), &vThree);
vAttrs.mkAttrs(builder.finish());
test(vAttrs,
"{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT "«2 attributes elided»" ANSI_NORMAL " }",
PrintOptions {
.ansiColors = true,
.maxAttrs = 1
});
}
TEST_F(ValuePrintingTests, ansiColorsListElided)
{
BindingsBuilder emptyBuilder(state, state.allocBindings(1));
Value vOne;
vOne.mkInt(1);
Value vTwo;
vTwo.mkInt(2);
{
auto list = state.buildList(2);
list.elems[0] = &vOne;
list.elems[1] = &vTwo;
Value vList;
vList.mkList(list);
test(vList,
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«1 item elided»" ANSI_NORMAL " ]",
PrintOptions {
.ansiColors = true,
.maxListItems = 1
});
}
Value vThree;
vThree.mkInt(3);
{
auto list = state.buildList(3);
list.elems[0] = &vOne;
list.elems[1] = &vTwo;
list.elems[2] = &vThree;
Value vList;
vList.mkList(list);
test(vList,
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«2 items elided»" ANSI_NORMAL " ]",
PrintOptions {
.ansiColors = true,
.maxListItems = 1
});
}
}
} // namespace nix

View file

@ -0,0 +1,25 @@
#include "value.hh"
#include "tests/libstore.hh"
namespace nix {
class ValueTest : public LibStoreTest
{};
TEST_F(ValueTest, unsetValue)
{
Value unsetValue;
ASSERT_EQ(false, unsetValue.isValid());
ASSERT_EQ(nThunk, unsetValue.type(true));
ASSERT_DEATH(unsetValue.type(), "");
}
TEST_F(ValueTest, vInt)
{
Value vInt;
vInt.mkInt(42);
ASSERT_EQ(true, vInt.isValid());
}
} // namespace nix

1
src/libexpr/.version Symbolic link
View file

@ -0,0 +1 @@
../../.version

View file

@ -1,6 +1,5 @@
#include "attr-path.hh"
#include "eval-inline.hh"
#include "util.hh"
namespace nix {
@ -66,18 +65,18 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
if (!attrIndex) {
if (v->type() != nAttrs)
throw TypeError(
state.error<TypeError>(
"the expression selected by the selection path '%1%' should be a set but is %2%",
attrPath,
showType(*v));
showType(*v)).debugThrow();
if (attr.empty())
throw Error("empty attribute name in selection path '%1%'", attrPath);
Bindings::iterator a = v->attrs->find(state.symbols.create(attr));
if (a == v->attrs->end()) {
auto a = v->attrs()->get(state.symbols.create(attr));
if (!a) {
std::set<std::string> attrNames;
for (auto & attr : *v->attrs)
attrNames.insert(state.symbols[attr.name]);
for (auto & attr : *v->attrs())
attrNames.insert(std::string(state.symbols[attr.name]));
auto suggestions = Suggestions::bestMatches(attrNames, attr);
throw AttrPathNotFound(suggestions, "attribute '%1%' in selection path '%2%' not found", attr, attrPath);
@ -89,10 +88,10 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
else {
if (!v->isList())
throw TypeError(
state.error<TypeError>(
"the expression selected by the selection path '%1%' should be a list but is %2%",
attrPath,
showType(*v));
showType(*v)).debugThrow();
if (*attrIndex >= v->listSize())
throw AttrPathNotFound("list index %1% in selection path '%2%' is out of range", *attrIndex, attrPath);
@ -106,7 +105,7 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
}
std::pair<std::string, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what)
std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what)
{
Value * v2;
try {
@ -118,21 +117,25 @@ std::pair<std::string, uint32_t> findPackageFilename(EvalState & state, Value &
// FIXME: is it possible to extract the Pos object instead of doing this
// toString + parsing?
auto pos = state.forceString(*v2);
NixStringContext context;
auto path = state.coerceToPath(noPos, *v2, context, "while evaluating the 'meta.position' attribute of a derivation");
auto colon = pos.rfind(':');
if (colon == std::string::npos)
throw ParseError("cannot parse meta.position attribute '%s'", pos);
auto fn = path.path.abs();
auto fail = [fn]() {
throw ParseError("cannot parse 'meta.position' attribute '%s'", fn);
};
std::string filename(pos, 0, colon);
unsigned int lineno;
try {
lineno = std::stoi(std::string(pos, colon + 1, std::string::npos));
auto colon = fn.rfind(':');
if (colon == std::string::npos) fail();
std::string filename(fn, 0, colon);
auto lineno = std::stoi(std::string(fn, colon + 1, std::string::npos));
return {SourcePath{path.accessor, CanonPath(fn.substr(0, colon))}, lineno};
} catch (std::invalid_argument & e) {
throw ParseError("cannot parse line number '%s'", pos);
fail();
unreachable();
}
return { std::move(filename), lineno };
}

View file

@ -1,4 +1,5 @@
#pragma once
///@file
#include "eval.hh"
@ -16,8 +17,10 @@ std::pair<Value *, PosIdx> findAlongAttrPath(
Bindings & autoArgs,
Value & vIn);
/* Heuristic to find the filename and lineno or a nix value. */
std::pair<std::string, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what);
/**
* Heuristic to find the filename and lineno or a nix value.
*/
std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what);
std::vector<Symbol> parseAttrPath(EvalState & state, std::string_view s);

View file

@ -23,23 +23,6 @@ Bindings * EvalState::allocBindings(size_t capacity)
}
/* Create a new attribute named 'name' on an existing attribute set stored
in 'vAttrs' and return the newly allocated Value which is associated with
this attribute. */
Value * EvalState::allocAttr(Value & vAttrs, Symbol name)
{
Value * v = allocValue();
vAttrs.attrs->push_back(Attr(name, v));
return v;
}
Value * EvalState::allocAttr(Value & vAttrs, std::string_view name)
{
return allocAttr(vAttrs, symbols.create(name));
}
Value & BindingsBuilder::alloc(Symbol name, PosIdx pos)
{
auto value = state.allocValue();

View file

@ -1,10 +1,10 @@
#pragma once
///@file
#include "nixexpr.hh"
#include "symbol-table.hh"
#include <algorithm>
#include <optional>
namespace nix {
@ -12,7 +12,9 @@ namespace nix {
class EvalState;
struct Value;
/* Map one attribute name to its value. */
/**
* Map one attribute name to its value.
*/
struct Attr
{
/* the placement of `name` and `pos` in this struct is important.
@ -25,9 +27,9 @@ struct Attr
Attr(Symbol name, Value * value, PosIdx pos = noPos)
: name(name), pos(pos), value(value) { };
Attr() { };
bool operator < (const Attr & a) const
auto operator <=> (const Attr & a) const
{
return name < a.name;
return name <=> a.name;
}
};
@ -36,10 +38,12 @@ static_assert(sizeof(Attr) == 2 * sizeof(uint32_t) + sizeof(Value *),
"avoid introducing any padding into Attr if at all possible, and do not "
"introduce new fields that need not be present for almost every instance.");
/* Bindings contains all the attributes of an attribute set. It is defined
by its size and its capacity, the capacity being the number of Attr
elements allocated after this structure, while the size corresponds to
the number of elements already inserted in this structure. */
/**
* Bindings contains all the attributes of an attribute set. It is defined
* by its size and its capacity, the capacity being the number of Attr
* elements allocated after this structure, while the size corresponds to
* the number of elements already inserted in this structure.
*/
class Bindings
{
public:
@ -60,24 +64,26 @@ public:
typedef Attr * iterator;
typedef const Attr * const_iterator;
void push_back(const Attr & attr)
{
assert(size_ < capacity_);
attrs[size_++] = attr;
}
iterator find(Symbol name)
const_iterator find(Symbol name) const
{
Attr key(name, 0);
iterator i = std::lower_bound(begin(), end(), key);
const_iterator i = std::lower_bound(begin(), end(), key);
if (i != end() && i->name == name) return i;
return end();
}
Attr * get(Symbol name)
const Attr * get(Symbol name) const
{
Attr key(name, 0);
iterator i = std::lower_bound(begin(), end(), key);
const_iterator i = std::lower_bound(begin(), end(), key);
if (i != end() && i->name == name) return &*i;
return nullptr;
}
@ -85,16 +91,26 @@ public:
iterator begin() { return &attrs[0]; }
iterator end() { return &attrs[size_]; }
const_iterator begin() const { return &attrs[0]; }
const_iterator end() const { return &attrs[size_]; }
Attr & operator[](size_t pos)
{
return attrs[pos];
}
const Attr & operator[](size_t pos) const
{
return attrs[pos];
}
void sort();
size_t capacity() { return capacity_; }
size_t capacity() const { return capacity_; }
/* Returns the attributes in lexicographically sorted order. */
/**
* Returns the attributes in lexicographically sorted order.
*/
std::vector<const Attr *> lexicographicOrder(const SymbolTable & symbols) const
{
std::vector<const Attr *> res;
@ -111,9 +127,11 @@ public:
friend class EvalState;
};
/* A wrapper around Bindings that ensures that its always in sorted
order at the end. The only way to consume a BindingsBuilder is to
call finish(), which sorts the bindings. */
/**
* A wrapper around Bindings that ensures that its always in sorted
* order at the end. The only way to consume a BindingsBuilder is to
* call finish(), which sorts the bindings.
*/
class BindingsBuilder
{
Bindings * bindings;
@ -157,6 +175,20 @@ public:
{
return bindings;
}
size_t capacity()
{
return bindings->capacity();
}
void grow(Bindings * newBindings)
{
for (auto & i : *bindings)
newBindings->push_back(i);
bindings = newBindings;
}
friend struct ExprAttrs;
};
}

View file

@ -0,0 +1 @@
../../build-utils-meson

View file

@ -0,0 +1,85 @@
# This is a helper to callFlake() to lazily fetch flake inputs.
# The contents of the lock file, in JSON format.
lockFileStr:
# A mapping of lock file node IDs to { sourceInfo, subdir } attrsets,
# with sourceInfo.outPath providing an SourceAccessor to a previously
# fetched tree. This is necessary for possibly unlocked inputs, in
# particular the root input, but also --override-inputs pointing to
# unlocked trees.
overrides:
let
lockFile = builtins.fromJSON lockFileStr;
# Resolve a input spec into a node name. An input spec is
# either a node name, or a 'follows' path from the root
# node.
resolveInput = inputSpec:
if builtins.isList inputSpec
then getInputByPath lockFile.root inputSpec
else inputSpec;
# Follow an input path (e.g. ["dwarffs" "nixpkgs"]) from the
# root node, returning the final node.
getInputByPath = nodeName: path:
if path == []
then nodeName
else
getInputByPath
# Since this could be a 'follows' input, call resolveInput.
(resolveInput lockFile.nodes.${nodeName}.inputs.${builtins.head path})
(builtins.tail path);
allNodes =
builtins.mapAttrs
(key: node:
let
sourceInfo =
if overrides ? ${key}
then
overrides.${key}.sourceInfo
else
# FIXME: remove obsolete node.info.
fetchTree (node.info or {} // removeAttrs node.locked ["dir"]);
subdir = overrides.${key}.dir or node.locked.dir or "";
outPath = sourceInfo + ((if subdir == "" then "" else "/") + subdir);
flake = import (outPath + "/flake.nix");
inputs = builtins.mapAttrs
(inputName: inputSpec: allNodes.${resolveInput inputSpec})
(node.inputs or {});
outputs = flake.outputs (inputs // { self = result; });
result =
outputs
# We add the sourceInfo attribute for its metadata, as they are
# relevant metadata for the flake. However, the outPath of the
# sourceInfo does not necessarily match the outPath of the flake,
# as the flake may be in a subdirectory of a source.
# This is shadowed in the next //
// sourceInfo
// {
# This shadows the sourceInfo.outPath
inherit outPath;
inherit inputs; inherit outputs; inherit sourceInfo; _type = "flake";
};
in
if node.flake or true then
assert builtins.isFunction flake.outputs;
result
else
sourceInfo
)
lockFile.nodes;
in allNodes.${lockFile.root}

Some files were not shown because too many files have changed in this diff Show more