1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-07-06 21:41:48 +02:00

Merge remote-tracking branch 'upstream/master' into support-hardlinks-in-tarballs

This commit is contained in:
Robert Hensing 2024-07-11 11:43:02 +02:00
commit 86420753ec
583 changed files with 11313 additions and 16547 deletions

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,58 @@
# 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

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,63 @@
{ lib
, mkMesonDerivation
, meson
, ninja
, 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 = [
meson
ninja
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
'';
enableParallelBuilding = true;
strictDeps = true;
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,99 @@
# 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@/nix-expr-tests \
@src@/nix-expr-tests/value \
@src@/nix-expr-test-support/test \
@src@/nix-expr-test-support/test/value \
@src@/libexpr/value \
@src@/libfetchers \
@src@/libmain \
@src@/libstore \
@src@/libstore/build \
@src@/libstore/builtins \
@src@/nix-store-tests \
@src@/nix-store-test-support/test \
@src@/libutil \
@src@/nix-util-tests \
@src@/nix-util-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

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,58 @@
{ lib
, mkMesonDerivation
, meson
, ninja
, 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 = [
meson
ninja
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
'';
enableParallelBuilding = true;
strictDeps = true;
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

View file

@ -127,12 +127,12 @@ ref<EvalState> EvalCommand::getEvalState()
if (!evalState) {
evalState =
#if HAVE_BOEHMGC
std::allocate_shared<EvalState>(traceable_allocator<EvalState>(),
lookupPath, getEvalStore(), getStore())
std::allocate_shared<EvalState>(
traceable_allocator<EvalState>(),
#else
std::make_shared<EvalState>(
lookupPath, getEvalStore(), getStore())
#endif
lookupPath, getEvalStore(), evalSettings, getStore())
;
evalState->repair = repair;

View file

@ -1,6 +1,7 @@
#include "eval-settings.hh"
#include "common-eval-args.hh"
#include "shared.hh"
#include "config-global.hh"
#include "filetransfer.hh"
#include "eval.hh"
#include "fetchers.hh"
@ -10,9 +11,35 @@
#include "command.hh"
#include "tarball.hh"
#include "fetch-to-store.hh"
#include "compatibility-settings.hh"
#include "eval-settings.hh"
namespace nix {
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(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);
CompatibilitySettings compatibilitySettings {};
static GlobalConfig::Register rCompatibilitySettings(&compatibilitySettings);
MixEvalArgs::MixEvalArgs()
{
addFlag({
@ -54,7 +81,7 @@ MixEvalArgs::MixEvalArgs()
.description = R"(
Add *path* to the Nix search path. The Nix search path is
initialized from the colon-separated [`NIX_PATH`](@docroot@/command-ref/env-common.md#env-NIX_PATH) environment
variable, and is used to look up the location of Nix expressions using [paths](@docroot@/language/values.md#type-path) enclosed in angle
variable, and is used to look up the location of Nix expressions using [paths](@docroot@/language/types.md#type-path) enclosed in angle
brackets (i.e., `<nixpkgs>`).
For instance, passing

View file

@ -12,9 +12,21 @@ namespace nix {
class Store;
class EvalState;
struct EvalSettings;
struct CompatibilitySettings;
class Bindings;
struct SourcePath;
/**
* @todo Get rid of global setttings variables
*/
extern EvalSettings evalSettings;
/**
* 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";

View file

@ -0,0 +1,19 @@
#pragma once
#include "config.hh"
namespace nix {
struct CompatibilitySettings : public Config
{
CompatibilitySettings() = default;
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 revert to the Nix 2.3 behavior.
)"};
};
};

View file

@ -6,10 +6,10 @@ libcmd_DIR := $(d)
libcmd_SOURCES := $(wildcard $(d)/*.cc)
libcmd_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libfetchers) $(INCLUDE_libexpr) $(INCLUDE_libmain)
libcmd_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libfetchers) $(INCLUDE_libexpr) $(INCLUDE_libflake) $(INCLUDE_libmain)
libcmd_LDFLAGS = $(EDITLINE_LIBS) $(LOWDOWN_LIBS) $(THREAD_LDFLAGS)
libcmd_LIBS = libstore libutil libexpr libmain libfetchers
libcmd_LIBS = libutil libstore libfetchers libflake libexpr libmain
$(eval $(call install-file-in, $(buildprefix)$(d)/nix-cmd.pc, $(libdir)/pkgconfig, 0644))

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

@ -0,0 +1,128 @@
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')
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

@ -1,7 +1,6 @@
#include "network-proxy.hh"
#include <algorithm>
#include <boost/algorithm/string.hpp>
#include "environment-variables.hh"
@ -13,7 +12,10 @@ static StringSet getAllVariables()
{
StringSet variables = lowercaseVariables;
for (const auto & variable : lowercaseVariables) {
variables.insert(boost::to_upper_copy(variable));
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;
}

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

@ -0,0 +1,110 @@
{ lib
, stdenv
, mkMesonDerivation
, releaseTools
, meson
, ninja
, pkg-config
, 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
mkMesonDerivation (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") ./.)
];
outputs = [ "out" "dev" ];
nativeBuildInputs = [
meson
ninja
pkg-config
];
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";
};
enableParallelBuilding = true;
separateDebugInfo = !stdenv.hostPlatform.isStatic;
# TODO `releaseTools.coverageAnalysis` in Nixpkgs needs to be updated
# to work with `strictDeps`.
strictDeps = true;
hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie";
meta = {
platforms = lib.platforms.unix ++ lib.platforms.windows;
};
})

View file

@ -18,7 +18,7 @@ extern "C" {
#include "finally.hh"
#include "repl-interacter.hh"
#include "file-system.hh"
#include "libcmd/repl.hh"
#include "repl.hh"
namespace nix {

View file

@ -3,11 +3,12 @@
#include <cstring>
#include <climits>
#include "libcmd/repl-interacter.hh"
#include "repl-interacter.hh"
#include "repl.hh"
#include "ansicolor.hh"
#include "shared.hh"
#include "config-global.hh"
#include "eval.hh"
#include "eval-cache.hh"
#include "eval-inline.hh"

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

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

View file

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

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

@ -0,0 +1,91 @@
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')
# 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')
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

@ -7,6 +7,7 @@
#include "eval.hh"
#include "globals.hh"
#include "util.hh"
#include "eval-settings.hh"
#include "nix_api_expr.h"
#include "nix_api_expr_internal.h"
@ -42,56 +43,56 @@ nix_err nix_libexpr_init(nix_c_context * context)
}
nix_err nix_expr_eval_from_string(
nix_c_context * context, EvalState * state, const char * expr, const char * path, Value * value)
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, *(nix::Value *) value);
state->state.forceValue(*(nix::Value *) value, nix::noPos);
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, Value * arg, Value * value)
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(*(nix::Value *) fn, *(nix::Value *) arg, *(nix::Value *) value, nix::noPos);
state->state.forceValue(*(nix::Value *) value, nix::noPos);
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, Value * fn, size_t nargs, Value ** args, Value * value)
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(*(nix::Value *) fn, nargs, (nix::Value * *)args, *(nix::Value *) value, nix::noPos);
state->state.forceValue(*(nix::Value *) value, nix::noPos);
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, Value * value)
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(*(nix::Value *) value, nix::noPos);
state->state.forceValue(value->value, nix::noPos);
}
NIXC_CATCH_ERRS
}
nix_err nix_value_force_deep(nix_c_context * context, EvalState * state, Value * value)
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(*(nix::Value *) value);
state->state.forceValueDeep(value->value);
}
NIXC_CATCH_ERRS
}
@ -106,7 +107,21 @@ EvalState * nix_state_create(nix_c_context * context, const char ** lookupPath_c
for (size_t i = 0; lookupPath_c[i] != nullptr; i++)
lookupPath.push_back(lookupPath_c[i]);
return new EvalState{nix::EvalState(nix::LookupPath::parse(lookupPath), store->ptr)};
void * p = ::operator new(
sizeof(EvalState),
static_cast<std::align_val_t>(alignof(EvalState)));
auto * p2 = static_cast<EvalState *>(p);
new (p) EvalState {
.settings = nix::EvalSettings{
nix::settings.readOnlyMode,
},
.state = nix::EvalState(
nix::LookupPath::parse(lookupPath),
store->ptr,
p2->settings),
};
loadConfFile(p2->settings);
return p2;
}
NIXC_CATCH_ERRS_NULL
}
@ -181,6 +196,15 @@ nix_err nix_gc_decref(nix_c_context * context, const void *)
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))
{
#ifdef HAVE_BOEHMGC

View file

@ -29,14 +29,23 @@ extern "C" {
* @see nix_state_create
*/
typedef struct EvalState EvalState; // nix::EvalState
/**
* @brief Represents a value in the Nix language.
/** @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.
*
* Owned by the garbage collector.
* @struct Value
* @see value_manip
* @see nix_value_incref, nix_value_decref
*/
typedef void Value; // nix::Value
typedef struct nix_value nix_value;
[[deprecated("use nix_value instead")]] typedef nix_value Value;
// Function prototypes
/**
@ -65,7 +74,7 @@ nix_err nix_libexpr_init(nix_c_context * context);
* @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, Value * value);
nix_c_context * context, EvalState * state, const char * expr, const char * path, nix_value * value);
/**
* @brief Calls a Nix function with an argument.
@ -79,7 +88,7 @@ nix_err nix_expr_eval_from_string(
* @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, Value * fn, Value * arg, Value * value);
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.
@ -98,7 +107,7 @@ nix_err nix_value_call(nix_c_context * context, EvalState * state, Value * fn, V
* @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, Value * fn, size_t nargs, Value ** args, Value * value);
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.
@ -116,7 +125,7 @@ nix_err nix_value_call_multi(
*/
#define NIX_VALUE_CALL(context, state, value, fn, ...) \
do { \
Value * args_array[] = {__VA_ARGS__}; \
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)
@ -124,12 +133,10 @@ nix_err nix_value_call_multi(
/**
* @brief Forces the evaluation of a Nix value.
*
* The Nix interpreter is lazy, and not-yet-evaluated Values can be
* The Nix interpreter is lazy, and not-yet-evaluated values can be
* of type NIX_TYPE_THUNK instead of their actual value.
*
* This function converts these Values into their final type.
*
* @note This function is mainly needed before calling @ref getters, but not for API calls that return a `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.
@ -138,7 +145,7 @@ nix_err nix_value_call_multi(
* @return NIX_OK if the force operation was successful, an error code
* otherwise.
*/
nix_err nix_value_force(nix_c_context * context, EvalState * state, Value * value);
nix_err nix_value_force(nix_c_context * context, EvalState * state, nix_value * value);
/**
* @brief Forces the deep evaluation of a Nix value.
@ -154,7 +161,7 @@ nix_err nix_value_force(nix_c_context * context, EvalState * state, Value * valu
* @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, Value * value);
nix_err nix_value_force_deep(nix_c_context * context, EvalState * state, nix_value * value);
/**
* @brief Create a new Nix language evaluator state.
@ -188,6 +195,11 @@ void nix_state_free(EvalState * state);
* 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.
*

View file

@ -2,11 +2,13 @@
#define NIX_API_EXPR_INTERNAL_H
#include "eval.hh"
#include "eval-settings.hh"
#include "attr-set.hh"
#include "nix_api_value.h"
struct EvalState
{
nix::EvalSettings settings;
nix::EvalState state;
};
@ -20,6 +22,11 @@ struct ListBuilder
nix::ListBuilder builder;
};
struct nix_value
{
nix::Value value;
};
struct nix_string_return
{
std::string str;

View file

@ -21,49 +21,54 @@
#endif
// Internal helper functions to check [in] and [out] `Value *` parameters
static const nix::Value & check_value_not_null(const Value * value)
static const nix::Value & check_value_not_null(const nix_value * value)
{
if (!value) {
throw std::runtime_error("Value is null");
throw std::runtime_error("nix_value is null");
}
return *((const nix::Value *) value);
}
static nix::Value & check_value_not_null(Value * value)
static nix::Value & check_value_not_null(nix_value * value)
{
if (!value) {
throw std::runtime_error("Value is null");
throw std::runtime_error("nix_value is null");
}
return *((nix::Value *) value);
return value->value;
}
static const nix::Value & check_value_in(const 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 Value");
throw std::runtime_error("Uninitialized nix_value");
}
return v;
}
static nix::Value & check_value_in(Value * value)
static nix::Value & check_value_in(nix_value * value)
{
auto & v = check_value_not_null(value);
if (!v.isValid()) {
throw std::runtime_error("Uninitialized Value");
throw std::runtime_error("Uninitialized nix_value");
}
return v;
}
static nix::Value & check_value_out(Value * value)
static nix::Value & check_value_out(nix_value * value)
{
auto & v = check_value_not_null(value);
if (v.isValid()) {
throw std::runtime_error("Value already initialized. Variables are immutable");
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.
*
@ -87,7 +92,7 @@ static void nix_c_primop_wrapper(
// or maybe something to make blackholes work better; we don't know).
nix::Value vTmp;
f(userdata, &ctx, (EvalState *) &state, (Value **) args, (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 */
@ -154,19 +159,19 @@ nix_err nix_register_primop(nix_c_context * context, PrimOp * primOp)
NIXC_CATCH_ERRS
}
Value * nix_alloc_value(nix_c_context * context, EvalState * state)
nix_value * nix_alloc_value(nix_c_context * context, EvalState * state)
{
if (context)
context->last_err_code = NIX_OK;
try {
Value * res = state->state.allocValue();
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 Value * value)
ValueType nix_get_type(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
@ -202,7 +207,7 @@ ValueType nix_get_type(nix_c_context * context, const Value * value)
NIXC_CATCH_ERRS_RES(NIX_TYPE_NULL);
}
const char * nix_get_typename(nix_c_context * context, const Value * value)
const char * nix_get_typename(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
@ -214,7 +219,7 @@ const char * nix_get_typename(nix_c_context * context, const Value * value)
NIXC_CATCH_ERRS_NULL
}
bool nix_get_bool(nix_c_context * context, const Value * value)
bool nix_get_bool(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
@ -226,7 +231,8 @@ bool nix_get_bool(nix_c_context * context, const Value * value)
NIXC_CATCH_ERRS_RES(false);
}
nix_err nix_get_string(nix_c_context * context, const Value * value, nix_get_string_callback callback, void * user_data)
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;
@ -238,7 +244,7 @@ nix_err nix_get_string(nix_c_context * context, const Value * value, nix_get_str
NIXC_CATCH_ERRS
}
const char * nix_get_path_string(nix_c_context * context, const Value * value)
const char * nix_get_path_string(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
@ -257,7 +263,7 @@ const char * nix_get_path_string(nix_c_context * context, const Value * value)
NIXC_CATCH_ERRS_NULL
}
unsigned int nix_get_list_size(nix_c_context * context, const Value * value)
unsigned int nix_get_list_size(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
@ -269,7 +275,7 @@ unsigned int nix_get_list_size(nix_c_context * context, const Value * value)
NIXC_CATCH_ERRS_RES(0);
}
unsigned int nix_get_attrs_size(nix_c_context * context, const Value * value)
unsigned int nix_get_attrs_size(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
@ -281,7 +287,7 @@ unsigned int nix_get_attrs_size(nix_c_context * context, const Value * value)
NIXC_CATCH_ERRS_RES(0);
}
double nix_get_float(nix_c_context * context, const Value * value)
double nix_get_float(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
@ -293,7 +299,7 @@ double nix_get_float(nix_c_context * context, const Value * value)
NIXC_CATCH_ERRS_RES(0.0);
}
int64_t nix_get_int(nix_c_context * context, const Value * value)
int64_t nix_get_int(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
@ -305,7 +311,7 @@ int64_t nix_get_int(nix_c_context * context, const Value * value)
NIXC_CATCH_ERRS_RES(0);
}
ExternalValue * nix_get_external(nix_c_context * context, Value * value)
ExternalValue * nix_get_external(nix_c_context * context, nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
@ -317,7 +323,7 @@ ExternalValue * nix_get_external(nix_c_context * context, Value * value)
NIXC_CATCH_ERRS_NULL;
}
Value * nix_get_list_byidx(nix_c_context * context, const Value * value, EvalState * state, unsigned int ix)
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;
@ -328,12 +334,12 @@ Value * nix_get_list_byidx(nix_c_context * context, const Value * value, EvalSta
nix_gc_incref(nullptr, p);
if (p != nullptr)
state->state.forceValue(*p, nix::noPos);
return (Value *) p;
return as_nix_value_ptr(p);
}
NIXC_CATCH_ERRS_NULL
}
Value * nix_get_attr_byname(nix_c_context * context, const Value * value, EvalState * state, const char * name)
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;
@ -345,7 +351,7 @@ Value * nix_get_attr_byname(nix_c_context * context, const Value * value, EvalSt
if (attr) {
nix_gc_incref(nullptr, attr->value);
state->state.forceValue(*attr->value, nix::noPos);
return attr->value;
return as_nix_value_ptr(attr->value);
}
nix_set_err_msg(context, NIX_ERR_KEY, "missing attribute");
return nullptr;
@ -353,7 +359,7 @@ Value * nix_get_attr_byname(nix_c_context * context, const Value * value, EvalSt
NIXC_CATCH_ERRS_NULL
}
bool nix_has_attr_byname(nix_c_context * context, const Value * value, EvalState * state, const char * name)
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;
@ -369,8 +375,8 @@ bool nix_has_attr_byname(nix_c_context * context, const Value * value, EvalState
NIXC_CATCH_ERRS_RES(false);
}
Value *
nix_get_attr_byidx(nix_c_context * context, const Value * value, EvalState * state, unsigned int i, const char ** name)
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;
@ -380,12 +386,13 @@ nix_get_attr_byidx(nix_c_context * context, const Value * value, EvalState * sta
*name = ((const std::string &) (state->state.symbols[a.name])).c_str();
nix_gc_incref(nullptr, a.value);
state->state.forceValue(*a.value, nix::noPos);
return a.value;
return as_nix_value_ptr(a.value);
}
NIXC_CATCH_ERRS_NULL
}
const char * nix_get_attr_name_byidx(nix_c_context * context, const Value * value, EvalState * state, unsigned int i)
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;
@ -397,7 +404,7 @@ const char * nix_get_attr_name_byidx(nix_c_context * context, const Value * valu
NIXC_CATCH_ERRS_NULL
}
nix_err nix_init_bool(nix_c_context * context, Value * value, bool b)
nix_err nix_init_bool(nix_c_context * context, nix_value * value, bool b)
{
if (context)
context->last_err_code = NIX_OK;
@ -409,7 +416,7 @@ nix_err nix_init_bool(nix_c_context * context, Value * value, bool b)
}
// todo string context
nix_err nix_init_string(nix_c_context * context, Value * value, const char * str)
nix_err nix_init_string(nix_c_context * context, nix_value * value, const char * str)
{
if (context)
context->last_err_code = NIX_OK;
@ -420,7 +427,7 @@ nix_err nix_init_string(nix_c_context * context, Value * value, const char * str
NIXC_CATCH_ERRS
}
nix_err nix_init_path_string(nix_c_context * context, EvalState * s, Value * value, const char * str)
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;
@ -431,7 +438,7 @@ nix_err nix_init_path_string(nix_c_context * context, EvalState * s, Value * val
NIXC_CATCH_ERRS
}
nix_err nix_init_float(nix_c_context * context, Value * value, double d)
nix_err nix_init_float(nix_c_context * context, nix_value * value, double d)
{
if (context)
context->last_err_code = NIX_OK;
@ -442,7 +449,7 @@ nix_err nix_init_float(nix_c_context * context, Value * value, double d)
NIXC_CATCH_ERRS
}
nix_err nix_init_int(nix_c_context * context, Value * value, int64_t i)
nix_err nix_init_int(nix_c_context * context, nix_value * value, int64_t i)
{
if (context)
context->last_err_code = NIX_OK;
@ -453,7 +460,7 @@ nix_err nix_init_int(nix_c_context * context, Value * value, int64_t i)
NIXC_CATCH_ERRS
}
nix_err nix_init_null(nix_c_context * context, Value * value)
nix_err nix_init_null(nix_c_context * context, nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
@ -464,7 +471,7 @@ nix_err nix_init_null(nix_c_context * context, Value * value)
NIXC_CATCH_ERRS
}
nix_err nix_init_apply(nix_c_context * context, Value * value, Value * fn, Value * arg)
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;
@ -477,7 +484,7 @@ nix_err nix_init_apply(nix_c_context * context, Value * value, Value * fn, Value
NIXC_CATCH_ERRS
}
nix_err nix_init_external(nix_c_context * context, Value * value, ExternalValue * val)
nix_err nix_init_external(nix_c_context * context, nix_value * value, ExternalValue * val)
{
if (context)
context->last_err_code = NIX_OK;
@ -504,7 +511,8 @@ ListBuilder * nix_make_list_builder(nix_c_context * context, EvalState * state,
NIXC_CATCH_ERRS_NULL
}
nix_err nix_list_builder_insert(nix_c_context * context, ListBuilder * list_builder, unsigned int index, Value * value)
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;
@ -524,7 +532,7 @@ void nix_list_builder_free(ListBuilder * list_builder)
#endif
}
nix_err nix_make_list(nix_c_context * context, ListBuilder * list_builder, Value * value)
nix_err nix_make_list(nix_c_context * context, ListBuilder * list_builder, nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
@ -535,7 +543,7 @@ nix_err nix_make_list(nix_c_context * context, ListBuilder * list_builder, Value
NIXC_CATCH_ERRS
}
nix_err nix_init_primop(nix_c_context * context, Value * value, PrimOp * p)
nix_err nix_init_primop(nix_c_context * context, nix_value * value, PrimOp * p)
{
if (context)
context->last_err_code = NIX_OK;
@ -546,7 +554,7 @@ nix_err nix_init_primop(nix_c_context * context, Value * value, PrimOp * p)
NIXC_CATCH_ERRS
}
nix_err nix_copy_value(nix_c_context * context, Value * value, const Value * source)
nix_err nix_copy_value(nix_c_context * context, nix_value * value, const nix_value * source)
{
if (context)
context->last_err_code = NIX_OK;
@ -558,7 +566,7 @@ nix_err nix_copy_value(nix_c_context * context, Value * value, const Value * sou
NIXC_CATCH_ERRS
}
nix_err nix_make_attrs(nix_c_context * context, Value * value, BindingsBuilder * b)
nix_err nix_make_attrs(nix_c_context * context, nix_value * value, BindingsBuilder * b)
{
if (context)
context->last_err_code = NIX_OK;
@ -584,7 +592,7 @@ BindingsBuilder * nix_make_bindings_builder(nix_c_context * context, EvalState *
NIXC_CATCH_ERRS_NULL
}
nix_err nix_bindings_builder_insert(nix_c_context * context, BindingsBuilder * bb, const char * name, Value * value)
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;
@ -605,7 +613,7 @@ void nix_bindings_builder_free(BindingsBuilder * bb)
#endif
}
nix_realised_string * nix_string_realise(nix_c_context * context, EvalState * state, Value * value, bool isIFD)
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;

View file

@ -35,8 +35,11 @@ typedef enum {
} ValueType;
// forward declarations
typedef void Value;
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
@ -90,7 +93,8 @@ typedef struct nix_realised_string nix_realised_string;
* @param[out] ret return value
* @see nix_alloc_primop, nix_init_primop
*/
typedef void (*PrimOpFun)(void * user_data, nix_c_context * context, EvalState * state, Value ** args, Value * ret);
typedef void (*PrimOpFun)(
void * user_data, nix_c_context * context, EvalState * state, nix_value ** args, nix_value * ret);
/** @brief Allocate a PrimOp
*
@ -142,10 +146,29 @@ nix_err nix_register_primop(nix_c_context * context, PrimOp * primOp);
* @return value, or null in case of errors
*
*/
Value * nix_alloc_value(nix_c_context * context, EvalState * state);
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 Value.
* @brief Functions to inspect and change Nix language values, represented by nix_value.
* @{
*/
/** @anchor getters
@ -157,7 +180,7 @@ Value * nix_alloc_value(nix_c_context * context, EvalState * state);
* @param[in] value Nix value to inspect
* @return type of nix value
*/
ValueType nix_get_type(nix_c_context * context, const Value * 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
@ -165,14 +188,14 @@ ValueType nix_get_type(nix_c_context * context, const Value * value);
* @return type name, owned string
* @todo way to free the result
*/
const char * nix_get_typename(nix_c_context * context, const Value * value);
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 Value * value);
bool nix_get_bool(nix_c_context * context, const nix_value * value);
/** @brief Get the raw string
*
@ -186,7 +209,7 @@ bool nix_get_bool(nix_c_context * context, const Value * value);
* @return error code, NIX_OK on success.
*/
nix_err
nix_get_string(nix_c_context * context, const Value * value, nix_get_string_callback callback, void * user_data);
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
@ -194,42 +217,42 @@ nix_get_string(nix_c_context * context, const Value * value, nix_get_string_call
* @return string
* @return NULL in case of error.
*/
const char * nix_get_path_string(nix_c_context * context, const Value * value);
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 Value * value);
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 Value * value);
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 Value * value);
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 Value * value);
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, Value *);
ExternalValue * nix_get_external(nix_c_context * context, nix_value *);
/** @brief Get the ix'th element of a list
*
@ -240,7 +263,7 @@ ExternalValue * nix_get_external(nix_c_context * context, Value *);
* @param[in] ix list element to get
* @return value, NULL in case of errors
*/
Value * nix_get_list_byidx(nix_c_context * context, const Value * value, EvalState * state, unsigned int ix);
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
*
@ -251,7 +274,7 @@ Value * nix_get_list_byidx(nix_c_context * context, const Value * value, EvalSta
* @param[in] name attribute name
* @return value, NULL in case of errors
*/
Value * nix_get_attr_byname(nix_c_context * context, const Value * value, EvalState * state, const char * name);
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
@ -260,7 +283,7 @@ Value * nix_get_attr_byname(nix_c_context * context, const Value * value, EvalSt
* @param[in] name attribute name
* @return value, error info via context
*/
bool nix_has_attr_byname(nix_c_context * context, const Value * value, EvalState * state, const char * name);
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
*
@ -274,8 +297,8 @@ bool nix_has_attr_byname(nix_c_context * context, const Value * value, EvalState
* @param[out] name will store a pointer to the attribute name
* @return value, NULL in case of errors
*/
Value *
nix_get_attr_byidx(nix_c_context * context, const Value * value, EvalState * state, unsigned int i, const char ** name);
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
*
@ -288,7 +311,8 @@ nix_get_attr_byidx(nix_c_context * context, const Value * value, EvalState * sta
* @param[in] i attribute index
* @return name, NULL in case of errors
*/
const char * nix_get_attr_name_byidx(nix_c_context * context, const Value * value, EvalState * state, unsigned int i);
const char *
nix_get_attr_name_byidx(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int i);
/**@}*/
/** @name Initializers
@ -305,7 +329,7 @@ const char * nix_get_attr_name_byidx(nix_c_context * context, const Value * valu
* @param[in] b the boolean value
* @return error code, NIX_OK on success.
*/
nix_err nix_init_bool(nix_c_context * context, Value * value, bool b);
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
@ -313,7 +337,7 @@ nix_err nix_init_bool(nix_c_context * context, Value * value, bool b);
* @param[in] str the string, copied
* @return error code, NIX_OK on success.
*/
nix_err nix_init_string(nix_c_context * context, Value * value, const char * str);
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
@ -321,7 +345,7 @@ nix_err nix_init_string(nix_c_context * context, Value * value, const char * str
* @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, Value * value, const char * str);
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
@ -329,7 +353,7 @@ nix_err nix_init_path_string(nix_c_context * context, EvalState * s, Value * val
* @param[in] d the float, 64-bits
* @return error code, NIX_OK on success.
*/
nix_err nix_init_float(nix_c_context * context, Value * value, double d);
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
@ -338,13 +362,13 @@ nix_err nix_init_float(nix_c_context * context, Value * value, double d);
* @return error code, NIX_OK on success.
*/
nix_err nix_init_int(nix_c_context * context, Value * value, int64_t i);
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, Value * value);
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.
*
@ -360,7 +384,7 @@ nix_err nix_init_null(nix_c_context * context, Value * value);
* @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, Value * value, Value * fn, Value * arg);
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
@ -368,7 +392,7 @@ nix_err nix_init_apply(nix_c_context * context, Value * value, Value * fn, Value
* @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, Value * value, ExternalValue * val);
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
@ -376,7 +400,7 @@ nix_err nix_init_external(nix_c_context * context, Value * value, ExternalValue
* @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, Value * value);
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
@ -393,7 +417,8 @@ ListBuilder * nix_make_list_builder(nix_c_context * context, EvalState * state,
* @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, Value * value);
nix_err
nix_list_builder_insert(nix_c_context * context, ListBuilder * list_builder, unsigned int index, nix_value * value);
/** @brief Free a list builder
*
@ -408,7 +433,7 @@ void nix_list_builder_free(ListBuilder * list_builder);
* @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, Value * value, BindingsBuilder * b);
nix_err nix_make_attrs(nix_c_context * context, nix_value * value, BindingsBuilder * b);
/** @brief Set primop
* @param[out] context Optional, stores error information
@ -417,14 +442,14 @@ nix_err nix_make_attrs(nix_c_context * context, Value * value, BindingsBuilder *
* @see nix_alloc_primop
* @return error code, NIX_OK on success.
*/
nix_err nix_init_primop(nix_c_context * context, Value * value, PrimOp * op);
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, Value * value, const Value * source);
nix_err nix_copy_value(nix_c_context * context, nix_value * value, const nix_value * source);
/**@}*/
/** @brief Create a bindings builder
@ -444,7 +469,7 @@ BindingsBuilder * nix_make_bindings_builder(nix_c_context * context, EvalState *
* @return error code, NIX_OK on success.
*/
nix_err
nix_bindings_builder_insert(nix_c_context * context, BindingsBuilder * builder, const char * name, Value * value);
nix_bindings_builder_insert(nix_c_context * context, BindingsBuilder * builder, const char * name, nix_value * value);
/** @brief Free a bindings builder
*
@ -471,7 +496,7 @@ void nix_bindings_builder_free(BindingsBuilder * builder);
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, Value * value, bool isIFD);
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

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

@ -0,0 +1,78 @@
{ lib
, stdenv
, mkMesonDerivation
, meson
, ninja
, pkg-config
, nix-store-c
, nix-expr
# Configuration Options
, version
}:
let
inherit (lib) fileset;
in
mkMesonDerivation (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") ./.)
];
outputs = [ "out" "dev" ];
nativeBuildInputs = [
meson
ninja
pkg-config
];
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";
};
enableParallelBuilding = true;
separateDebugInfo = !stdenv.hostPlatform.isStatic;
strictDeps = true;
hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie";
meta = {
platforms = lib.platforms.unix ++ lib.platforms.windows;
};
})

1
src/libexpr/.version Symbolic link
View file

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

View file

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

226
src/libexpr/eval-gc.cc Normal file
View file

@ -0,0 +1,226 @@
#include "error.hh"
#include "environment-variables.hh"
#include "serialise.hh"
#include "eval-gc.hh"
#if HAVE_BOEHMGC
# include <pthread.h>
# if __FreeBSD__
# include <pthread_np.h>
# endif
# include <gc/gc.h>
# include <gc/gc_cpp.h>
# include <gc/gc_allocator.h>
# include <boost/coroutine2/coroutine.hpp>
# include <boost/coroutine2/protected_fixedsize_stack.hpp>
# include <boost/context/stack_context.hpp>
#endif
namespace nix {
#if HAVE_BOEHMGC
/* Called when the Boehm GC runs out of memory. */
static void * oomHandler(size_t requested)
{
/* Convert this to a proper C++ exception. */
throw std::bad_alloc();
}
class BoehmGCStackAllocator : public StackAllocator
{
boost::coroutines2::protected_fixedsize_stack stack{
// We allocate 8 MB, the default max stack size on NixOS.
// A smaller stack might be quicker to allocate but reduces the stack
// depth available for source filter expressions etc.
std::max(boost::context::stack_traits::default_size(), static_cast<std::size_t>(8 * 1024 * 1024))};
// This is specific to boost::coroutines2::protected_fixedsize_stack.
// The stack protection page is included in sctx.size, so we have to
// subtract one page size from the stack size.
std::size_t pfss_usable_stack_size(boost::context::stack_context & sctx)
{
return sctx.size - boost::context::stack_traits::page_size();
}
public:
boost::context::stack_context allocate() override
{
auto sctx = stack.allocate();
// Stacks generally start at a high address and grow to lower addresses.
// Architectures that do the opposite are rare; in fact so rare that
// boost_routine does not implement it.
// So we subtract the stack size.
GC_add_roots(static_cast<char *>(sctx.sp) - pfss_usable_stack_size(sctx), sctx.sp);
return sctx;
}
void deallocate(boost::context::stack_context sctx) override
{
GC_remove_roots(static_cast<char *>(sctx.sp) - pfss_usable_stack_size(sctx), sctx.sp);
stack.deallocate(sctx);
}
};
static BoehmGCStackAllocator boehmGCStackAllocator;
/**
* When a thread goes into a coroutine, we lose its original sp until
* control flow returns to the thread.
* While in the coroutine, the sp points outside the thread stack,
* so we can detect this and push the entire thread stack instead,
* as an approximation.
* The coroutine's stack is covered by `BoehmGCStackAllocator`.
* This is not an optimal solution, because the garbage is scanned when a
* coroutine is active, for both the coroutine and the original thread stack.
* However, the implementation is quite lean, and usually we don't have active
* coroutines during evaluation, so this is acceptable.
*/
void fixupBoehmStackPointer(void ** sp_ptr, void * _pthread_id)
{
void *& sp = *sp_ptr;
auto pthread_id = reinterpret_cast<pthread_t>(_pthread_id);
pthread_attr_t pattr;
size_t osStackSize;
void * osStackLow;
void * osStackBase;
# ifdef __APPLE__
osStackSize = pthread_get_stacksize_np(pthread_id);
osStackLow = pthread_get_stackaddr_np(pthread_id);
# else
if (pthread_attr_init(&pattr)) {
throw Error("fixupBoehmStackPointer: pthread_attr_init failed");
}
# ifdef HAVE_PTHREAD_GETATTR_NP
if (pthread_getattr_np(pthread_id, &pattr)) {
throw Error("fixupBoehmStackPointer: pthread_getattr_np failed");
}
# elif HAVE_PTHREAD_ATTR_GET_NP
if (!pthread_attr_init(&pattr)) {
throw Error("fixupBoehmStackPointer: pthread_attr_init failed");
}
if (!pthread_attr_get_np(pthread_id, &pattr)) {
throw Error("fixupBoehmStackPointer: pthread_attr_get_np failed");
}
# else
# error "Need one of `pthread_attr_get_np` or `pthread_getattr_np`"
# endif
if (pthread_attr_getstack(&pattr, &osStackLow, &osStackSize)) {
throw Error("fixupBoehmStackPointer: pthread_attr_getstack failed");
}
if (pthread_attr_destroy(&pattr)) {
throw Error("fixupBoehmStackPointer: pthread_attr_destroy failed");
}
# endif
osStackBase = (char *) osStackLow + osStackSize;
// NOTE: We assume the stack grows down, as it does on all architectures we support.
// Architectures that grow the stack up are rare.
if (sp >= osStackBase || sp < osStackLow) { // lo is outside the os stack
sp = osStackBase;
}
}
/* Disable GC while this object lives. Used by CoroutineContext.
*
* Boehm keeps a count of GC_disable() and GC_enable() calls,
* and only enables GC when the count matches.
*/
class BoehmDisableGC
{
public:
BoehmDisableGC()
{
GC_disable();
};
~BoehmDisableGC()
{
GC_enable();
};
};
static inline void initGCReal()
{
/* Initialise the Boehm garbage collector. */
/* Don't look for interior pointers. This reduces the odds of
misdetection a bit. */
GC_set_all_interior_pointers(0);
/* We don't have any roots in data segments, so don't scan from
there. */
GC_set_no_dls(1);
GC_INIT();
GC_set_oom_fn(oomHandler);
StackAllocator::defaultAllocator = &boehmGCStackAllocator;
// TODO: Remove __APPLE__ condition.
// Comment suggests an implementation that works on darwin and windows
// https://github.com/ivmai/bdwgc/issues/362#issuecomment-1936672196
# if GC_VERSION_MAJOR >= 8 && GC_VERSION_MINOR >= 2 && GC_VERSION_MICRO >= 4 && !defined(__APPLE__)
GC_set_sp_corrector(&fixupBoehmStackPointer);
if (!GC_get_sp_corrector()) {
printTalkative("BoehmGC on this platform does not support sp_corrector; will disable GC inside coroutines");
/* Used to disable GC when entering coroutines on macOS */
create_coro_gc_hook = []() -> std::shared_ptr<void> { return std::make_shared<BoehmDisableGC>(); };
}
# else
# warning \
"BoehmGC version does not support GC while coroutine exists. GC will be disabled inside coroutines. Consider updating bdw-gc to 8.2.4 or later."
# endif
/* Set the initial heap size to something fairly big (25% of
physical RAM, up to a maximum of 384 MiB) so that in most cases
we don't need to garbage collect at all. (Collection has a
fairly significant overhead.) The heap size can be overridden
through libgc's GC_INITIAL_HEAP_SIZE environment variable. We
should probably also provide a nix.conf setting for this. Note
that GC_expand_hp() causes a lot of virtual, but not physical
(resident) memory to be allocated. This might be a problem on
systems that don't overcommit. */
if (!getEnv("GC_INITIAL_HEAP_SIZE")) {
size_t size = 32 * 1024 * 1024;
# if HAVE_SYSCONF && defined(_SC_PAGESIZE) && defined(_SC_PHYS_PAGES)
size_t maxSize = 384 * 1024 * 1024;
long pageSize = sysconf(_SC_PAGESIZE);
long pages = sysconf(_SC_PHYS_PAGES);
if (pageSize != -1)
size = (pageSize * pages) / 4; // 25% of RAM
if (size > maxSize)
size = maxSize;
# endif
debug("setting initial heap size to %1% bytes", size);
GC_expand_hp(size);
}
}
#endif
static bool gcInitialised = false;
void initGC()
{
if (gcInitialised)
return;
#if HAVE_BOEHMGC
initGCReal();
#endif
gcInitialised = true;
}
void assertGCInitialized()
{
assert(gcInitialised);
}
}

16
src/libexpr/eval-gc.hh Normal file
View file

@ -0,0 +1,16 @@
#pragma once
///@file
namespace nix {
/**
* Initialise the Boehm GC, if applicable.
*/
void initGC();
/**
* Make sure `initGC` has already been called.
*/
void assertGCInitialized();
}

View file

@ -1,4 +1,5 @@
#include "users.hh"
#include "config-global.hh"
#include "globals.hh"
#include "profiles.hh"
#include "eval.hh"
@ -44,7 +45,9 @@ static Strings parseNixPath(const std::string & s)
return res;
}
EvalSettings::EvalSettings()
EvalSettings::EvalSettings(bool & readOnlyMode, EvalSettings::LookupPathHooks lookupPathHooks)
: readOnlyMode{readOnlyMode}
, lookupPathHooks{lookupPathHooks}
{
auto var = getEnv("NIX_PATH");
if (var) nixPath = parseNixPath(*var);
@ -54,7 +57,7 @@ EvalSettings::EvalSettings()
builtinsAbortOnWarn = true;
}
Strings EvalSettings::getDefaultNixPath()
Strings EvalSettings::getDefaultNixPath() const
{
Strings res;
auto add = [&](const Path & p, const std::string & s = std::string()) {
@ -67,7 +70,7 @@ Strings EvalSettings::getDefaultNixPath()
}
};
if (!evalSettings.restrictEval && !evalSettings.pureEval) {
if (!restrictEval && !pureEval) {
add(getNixDefExpr() + "/channels");
add(rootChannelsDir() + "/nixpkgs", "nixpkgs");
add(rootChannelsDir());
@ -93,16 +96,12 @@ std::string EvalSettings::resolvePseudoUrl(std::string_view url)
return std::string(url);
}
const std::string & EvalSettings::getCurrentSystem()
const std::string & EvalSettings::getCurrentSystem() const
{
const auto & evalSystem = currentSystem.get();
return evalSystem != "" ? evalSystem : settings.thisSystem.get();
}
EvalSettings evalSettings;
static GlobalConfig::Register rEvalSettings(&evalSettings);
Path getNixDefExpr()
{
return settings.useXDGBaseDirectories

View file

@ -5,16 +5,51 @@
namespace nix {
class Store;
struct EvalSettings : Config
{
EvalSettings();
/**
* Function used to interpet look path entries of a given scheme.
*
* The argument is the non-scheme part of the lookup path entry (see
* `LookupPathHooks` below).
*
* The return value is (a) whether the entry was valid, and, if so,
* what does it map to.
*
* @todo Return (`std::optional` of) `SourceAccssor` or something
* more structured instead of mere `std::string`?
*/
using LookupPathHook = std::optional<std::string>(ref<Store> store, std::string_view);
static Strings getDefaultNixPath();
/**
* Map from "scheme" to a `LookupPathHook`.
*
* Given a lookup path value (i.e. either the whole thing, or after
* the `<key>=`) in the form of:
*
* ```
* <scheme>:<arbitrary string>
* ```
*
* if `<scheme>` is a key in this map, then `<arbitrary string>` is
* passed to the hook that is the value in this map.
*/
using LookupPathHooks = std::map<std::string, std::function<LookupPathHook>>;
EvalSettings(bool & readOnlyMode, LookupPathHooks lookupPathHooks = {});
bool & readOnlyMode;
Strings getDefaultNixPath() const;
static bool isPseudoUrl(std::string_view s);
static std::string resolvePseudoUrl(std::string_view url);
LookupPathHooks lookupPathHooks;
Setting<bool> enableNativeCode{this, false, "allow-unsafe-native-code-during-evaluation", R"(
Enable built-in functions that allow executing native code.
@ -39,7 +74,7 @@ struct EvalSettings : Config
R"(
List of search paths to use for [lookup path](@docroot@/language/constructs/lookup-path.md) resolution.
This setting determines the value of
[`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtins-nixPath) and can be used with [`builtins.findFile`](@docroot@/language/builtin-constants.md#builtins-findFile).
[`builtins.nixPath`](@docroot@/language/builtins.md#builtins-nixPath) and can be used with [`builtins.findFile`](@docroot@/language/builtins.md#builtins-findFile).
The default value is
@ -60,7 +95,7 @@ struct EvalSettings : Config
this, "", "eval-system",
R"(
This option defines
[`builtins.currentSystem`](@docroot@/language/builtin-constants.md#builtins-currentSystem)
[`builtins.currentSystem`](@docroot@/language/builtins.md#builtins-currentSystem)
in the Nix language if it is set as a non-empty string.
Otherwise, if it is defined as the empty string (the default), the value of the
[`system` ](#conf-system)
@ -74,14 +109,14 @@ struct EvalSettings : Config
* Implements the `eval-system` vs `system` defaulting logic
* described for `eval-system`.
*/
const std::string & getCurrentSystem();
const std::string & getCurrentSystem() const;
Setting<bool> restrictEval{
this, false, "restrict-eval",
R"(
If set to `true`, the Nix evaluator will not allow access to any
files outside of
[`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtins-nixPath),
[`builtins.nixPath`](@docroot@/language/builtins.md#builtins-nixPath),
or to URIs outside of
[`allowed-uris`](@docroot@/command-ref/conf-file.md#conf-allowed-uris).
)"};
@ -92,10 +127,10 @@ struct EvalSettings : Config
- Restrict file system and network access to files specified by cryptographic hash
- Disable impure constants:
- [`builtins.currentSystem`](@docroot@/language/builtin-constants.md#builtins-currentSystem)
- [`builtins.currentTime`](@docroot@/language/builtin-constants.md#builtins-currentTime)
- [`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtins-nixPath)
- [`builtins.storePath`](@docroot@/language/builtin-constants.md#builtins-storePath)
- [`builtins.currentSystem`](@docroot@/language/builtins.md#builtins-currentSystem)
- [`builtins.currentTime`](@docroot@/language/builtins.md#builtins-currentTime)
- [`builtins.nixPath`](@docroot@/language/builtins.md#builtins-nixPath)
- [`builtins.storePath`](@docroot@/language/builtins.md#builtins-storePath)
)"
};
@ -193,8 +228,6 @@ struct EvalSettings : Config
)"};
};
extern EvalSettings evalSettings;
/**
* Conventionally part of the default nix path in impure mode.
*/

View file

@ -3,13 +3,12 @@
#include "hash.hh"
#include "primops.hh"
#include "print-options.hh"
#include "shared.hh"
#include "exit.hh"
#include "types.hh"
#include "util.hh"
#include "store-api.hh"
#include "derivations.hh"
#include "downstream-placeholder.hh"
#include "globals.hh"
#include "eval-inline.hh"
#include "filetransfer.hh"
#include "function-trace.hh"
@ -22,7 +21,6 @@
#include "url.hh"
#include "fetch-to-store.hh"
#include "tarball.hh"
#include "flake/flakeref.hh"
#include "parser-tab.hh"
#include <algorithm>
@ -40,22 +38,16 @@
#include <boost/container/small_vector.hpp>
#ifndef _WIN32 // TODO use portable implementation
# include <sys/resource.h>
# include <sys/resource.h>
#endif
#if HAVE_BOEHMGC
#define GC_INCLUDE_NEW
# define GC_INCLUDE_NEW
#include <pthread.h>
#include <gc/gc.h>
#include <gc/gc_cpp.h>
#include <gc/gc_allocator.h>
#include <boost/coroutine2/coroutine.hpp>
#include <boost/coroutine2/protected_fixedsize_stack.hpp>
#include <boost/context/stack_context.hpp>
# include <gc/gc.h>
# include <gc/gc_cpp.h>
# include <gc/gc_allocator.h>
#endif
@ -208,97 +200,6 @@ bool Value::isTrivial() const
}
#if HAVE_BOEHMGC
/* Called when the Boehm GC runs out of memory. */
static void * oomHandler(size_t requested)
{
/* Convert this to a proper C++ exception. */
throw std::bad_alloc();
}
class BoehmGCStackAllocator : public StackAllocator {
boost::coroutines2::protected_fixedsize_stack stack {
// We allocate 8 MB, the default max stack size on NixOS.
// A smaller stack might be quicker to allocate but reduces the stack
// depth available for source filter expressions etc.
std::max(boost::context::stack_traits::default_size(), static_cast<std::size_t>(8 * 1024 * 1024))
};
// This is specific to boost::coroutines2::protected_fixedsize_stack.
// The stack protection page is included in sctx.size, so we have to
// subtract one page size from the stack size.
std::size_t pfss_usable_stack_size(boost::context::stack_context &sctx) {
return sctx.size - boost::context::stack_traits::page_size();
}
public:
boost::context::stack_context allocate() override {
auto sctx = stack.allocate();
// Stacks generally start at a high address and grow to lower addresses.
// Architectures that do the opposite are rare; in fact so rare that
// boost_routine does not implement it.
// So we subtract the stack size.
GC_add_roots(static_cast<char *>(sctx.sp) - pfss_usable_stack_size(sctx), sctx.sp);
return sctx;
}
void deallocate(boost::context::stack_context sctx) override {
GC_remove_roots(static_cast<char *>(sctx.sp) - pfss_usable_stack_size(sctx), sctx.sp);
stack.deallocate(sctx);
}
};
static BoehmGCStackAllocator boehmGCStackAllocator;
/**
* When a thread goes into a coroutine, we lose its original sp until
* control flow returns to the thread.
* While in the coroutine, the sp points outside the thread stack,
* so we can detect this and push the entire thread stack instead,
* as an approximation.
* The coroutine's stack is covered by `BoehmGCStackAllocator`.
* This is not an optimal solution, because the garbage is scanned when a
* coroutine is active, for both the coroutine and the original thread stack.
* However, the implementation is quite lean, and usually we don't have active
* coroutines during evaluation, so this is acceptable.
*/
void fixupBoehmStackPointer(void ** sp_ptr, void * pthread_id) {
void *& sp = *sp_ptr;
pthread_attr_t pattr;
size_t osStackSize;
void * osStackLow;
void * osStackBase;
#ifdef __APPLE__
osStackSize = pthread_get_stacksize_np((pthread_t)pthread_id);
osStackLow = pthread_get_stackaddr_np((pthread_t)pthread_id);
#else
if (pthread_attr_init(&pattr)) {
throw Error("fixupBoehmStackPointer: pthread_attr_init failed");
}
if (pthread_getattr_np((pthread_t)pthread_id, &pattr)) {
throw Error("fixupBoehmStackPointer: pthread_getattr_np failed");
}
if (pthread_attr_getstack(&pattr, &osStackLow, &osStackSize)) {
throw Error("fixupBoehmStackPointer: pthread_attr_getstack failed");
}
if (pthread_attr_destroy(&pattr)) {
throw Error("fixupBoehmStackPointer: pthread_attr_destroy failed");
}
#endif
osStackBase = (char *)osStackLow + osStackSize;
// NOTE: We assume the stack grows down, as it does on all architectures we support.
// Architectures that grow the stack up are rare.
if (sp >= osStackBase || sp < osStackLow) { // lo is outside the os stack
sp = osStackBase;
}
}
#endif
static Symbol getName(const AttrName & name, EvalState & state, Env & env)
{
if (name.symbol) {
@ -311,99 +212,15 @@ static Symbol getName(const AttrName & name, EvalState & state, Env & env)
}
}
#if HAVE_BOEHMGC
/* Disable GC while this object lives. Used by CoroutineContext.
*
* Boehm keeps a count of GC_disable() and GC_enable() calls,
* and only enables GC when the count matches.
*/
class BoehmDisableGC {
public:
BoehmDisableGC() {
GC_disable();
};
~BoehmDisableGC() {
GC_enable();
};
};
#endif
static bool gcInitialised = false;
void initGC()
{
if (gcInitialised) return;
#if HAVE_BOEHMGC
/* Initialise the Boehm garbage collector. */
/* Don't look for interior pointers. This reduces the odds of
misdetection a bit. */
GC_set_all_interior_pointers(0);
/* We don't have any roots in data segments, so don't scan from
there. */
GC_set_no_dls(1);
GC_INIT();
GC_set_oom_fn(oomHandler);
StackAllocator::defaultAllocator = &boehmGCStackAllocator;
// TODO: Remove __APPLE__ condition.
// Comment suggests an implementation that works on darwin and windows
// https://github.com/ivmai/bdwgc/issues/362#issuecomment-1936672196
#if GC_VERSION_MAJOR >= 8 && GC_VERSION_MINOR >= 2 && GC_VERSION_MICRO >= 4 && !defined(__APPLE__)
GC_set_sp_corrector(&fixupBoehmStackPointer);
if (!GC_get_sp_corrector()) {
printTalkative("BoehmGC on this platform does not support sp_corrector; will disable GC inside coroutines");
/* Used to disable GC when entering coroutines on macOS */
create_coro_gc_hook = []() -> std::shared_ptr<void> {
return std::make_shared<BoehmDisableGC>();
};
}
#else
#warning "BoehmGC version does not support GC while coroutine exists. GC will be disabled inside coroutines. Consider updating bdw-gc to 8.2.4 or later."
#endif
/* Set the initial heap size to something fairly big (25% of
physical RAM, up to a maximum of 384 MiB) so that in most cases
we don't need to garbage collect at all. (Collection has a
fairly significant overhead.) The heap size can be overridden
through libgc's GC_INITIAL_HEAP_SIZE environment variable. We
should probably also provide a nix.conf setting for this. Note
that GC_expand_hp() causes a lot of virtual, but not physical
(resident) memory to be allocated. This might be a problem on
systems that don't overcommit. */
if (!getEnv("GC_INITIAL_HEAP_SIZE")) {
size_t size = 32 * 1024 * 1024;
#if HAVE_SYSCONF && defined(_SC_PAGESIZE) && defined(_SC_PHYS_PAGES)
size_t maxSize = 384 * 1024 * 1024;
long pageSize = sysconf(_SC_PAGESIZE);
long pages = sysconf(_SC_PHYS_PAGES);
if (pageSize != -1)
size = (pageSize * pages) / 4; // 25% of RAM
if (size > maxSize) size = maxSize;
#endif
debug("setting initial heap size to %1% bytes", size);
GC_expand_hp(size);
}
#endif
gcInitialised = true;
}
static constexpr size_t BASE_ENV_SIZE = 128;
EvalState::EvalState(
const LookupPath & _lookupPath,
ref<Store> store,
const EvalSettings & settings,
std::shared_ptr<Store> buildStore)
: sWith(symbols.create("<with>"))
: settings{settings}
, sWith(symbols.create("<with>"))
, sOutPath(symbols.create("outPath"))
, sDrvPath(symbols.create("drvPath"))
, sType(symbols.create("type"))
@ -423,6 +240,12 @@ EvalState::EvalState(
, sRight(symbols.create("right"))
, sWrong(symbols.create("wrong"))
, sStructuredAttrs(symbols.create("__structuredAttrs"))
, sAllowedReferences(symbols.create("allowedReferences"))
, sAllowedRequisites(symbols.create("allowedRequisites"))
, sDisallowedReferences(symbols.create("disallowedReferences"))
, sDisallowedRequisites(symbols.create("disallowedRequisites"))
, sMaxSize(symbols.create("maxSize"))
, sMaxClosureSize(symbols.create("maxClosureSize"))
, sBuilder(symbols.create("builder"))
, sArgs(symbols.create("args"))
, sContentAddressed(symbols.create("__contentAddressed"))
@ -453,10 +276,10 @@ EvalState::EvalState(
, repair(NoRepair)
, emptyBindings(0)
, rootFS(
evalSettings.restrictEval || evalSettings.pureEval
settings.restrictEval || settings.pureEval
? ref<SourceAccessor>(AllowListSourceAccessor::create(getFSSourceAccessor(), {},
[](const CanonPath & path) -> RestrictedPathError {
auto modeInformation = evalSettings.pureEval
[&settings](const CanonPath & path) -> RestrictedPathError {
auto modeInformation = settings.pureEval
? "in pure evaluation mode (use '--impure' to override)"
: "in restricted mode";
throw RestrictedPathError("access to absolute path '%1%' is forbidden %2%", path, modeInformation);
@ -470,7 +293,7 @@ EvalState::EvalState(
)}
, callFlakeInternal{internalFS->addFile(
CanonPath("call-flake.nix"),
#include "flake/call-flake.nix.gen.hh"
#include "call-flake.nix.gen.hh"
)}
, store(store)
, buildStore(buildStore ? buildStore : store)
@ -493,7 +316,7 @@ EvalState::EvalState(
countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0";
assert(gcInitialised);
assertGCInitialized();
static_assert(sizeof(Env) <= 16, "environment must be <= 16 bytes");
@ -507,10 +330,10 @@ EvalState::EvalState(
vStringUnknown.mkString("unknown");
/* Initialise the Nix expression search path. */
if (!evalSettings.pureEval) {
if (!settings.pureEval) {
for (auto & i : _lookupPath.elements)
lookupPath.elements.emplace_back(LookupPath::Elem {i});
for (auto & i : evalSettings.nixPath.get())
for (auto & i : settings.nixPath.get())
lookupPath.elements.emplace_back(LookupPath::Elem::parse(i));
}
@ -588,9 +411,9 @@ bool isAllowedURI(std::string_view uri, const Strings & allowedUris)
void EvalState::checkURI(const std::string & uri)
{
if (!evalSettings.restrictEval) return;
if (!settings.restrictEval) return;
if (isAllowedURI(uri, evalSettings.allowedUris.get())) return;
if (isAllowedURI(uri, settings.allowedUris.get())) return;
/* If the URI is a path, then check it against allowedPaths as
well. */
@ -635,7 +458,7 @@ void EvalState::addConstant(const std::string & name, Value * v, Constant info)
constantInfos.push_back({name2, info});
if (!(evalSettings.pureEval && info.impureOnly)) {
if (!(settings.pureEval && info.impureOnly)) {
/* Check the type, if possible.
We might know the type of a thunk in advance, so be allowed
@ -1590,11 +1413,11 @@ public:
void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos)
{
if (callDepth > evalSettings.maxCallDepth)
if (callDepth > settings.maxCallDepth)
error<EvalError>("stack overflow; max-call-depth exceeded").atPos(pos).debugThrow();
CallDepth _level(callDepth);
auto trace = evalSettings.traceFunctionCalls
auto trace = settings.traceFunctionCalls
? std::make_unique<FunctionCallTrace>(positions[pos])
: nullptr;
@ -2480,7 +2303,7 @@ StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePat
path.resolveSymlinks(),
settings.readOnlyMode ? FetchMode::DryRun : FetchMode::Copy,
path.baseName(),
FileIngestionMethod::Recursive,
ContentAddressMethod::Raw::NixArchive,
nullptr,
repair);
allowPath(dstPath);
@ -2827,7 +2650,7 @@ void EvalState::printStatistics()
}
SourcePath resolveExprPath(SourcePath path)
SourcePath resolveExprPath(SourcePath path, bool addDefaultNix)
{
unsigned int followCount = 0, maxFollow = 1024;
@ -2843,7 +2666,7 @@ SourcePath resolveExprPath(SourcePath path)
}
/* If `path' refers to a directory, append `/default.nix'. */
if (path.resolveSymlinks().lstat().type == SourceAccessor::tDirectory)
if (addDefaultNix && path.resolveSymlinks().lstat().type == SourceAccessor::tDirectory)
return path / "default.nix";
return path;
@ -2922,7 +2745,7 @@ SourcePath EvalState::findFile(const LookupPath & lookupPath, const std::string_
return {corepkgsFS, CanonPath(path.substr(3))};
error<ThrownError>(
evalSettings.pureEval
settings.pureEval
? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)"
: "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)",
path
@ -2936,14 +2759,18 @@ std::optional<std::string> EvalState::resolveLookupPathPath(const LookupPath::Pa
auto i = lookupPathResolved.find(value);
if (i != lookupPathResolved.end()) return i->second;
std::optional<std::string> res;
auto finish = [&](std::string res) {
debug("resolved search path element '%s' to '%s'", value, res);
lookupPathResolved.emplace(value, res);
return res;
};
if (EvalSettings::isPseudoUrl(value)) {
try {
auto accessor = fetchers::downloadTarball(
EvalSettings::resolvePseudoUrl(value)).accessor;
auto storePath = fetchToStore(*store, SourcePath(accessor), FetchMode::Copy);
res = { store->toRealPath(storePath) };
return finish(store->toRealPath(storePath));
} catch (Error & e) {
logWarning({
.msg = HintFmt("Nix search path entry '%1%' cannot be downloaded, ignoring", value)
@ -2951,15 +2778,17 @@ std::optional<std::string> EvalState::resolveLookupPathPath(const LookupPath::Pa
}
}
else if (hasPrefix(value, "flake:")) {
experimentalFeatureSettings.require(Xp::Flakes);
auto flakeRef = parseFlakeRef(value.substr(6), {}, true, false);
debug("fetching flake search path element '%s''", value);
auto storePath = flakeRef.resolve(store).fetchTree(store).first;
res = { store->toRealPath(storePath) };
if (auto colPos = value.find(':'); colPos != value.npos) {
auto scheme = value.substr(0, colPos);
auto rest = value.substr(colPos + 1);
if (auto * hook = get(settings.lookupPathHooks, scheme)) {
auto res = (*hook)(store, rest);
if (res)
return finish(std::move(*res));
}
}
else {
{
auto path = absPath(value);
/* Allow access to paths in the search path. */
@ -2976,22 +2805,17 @@ std::optional<std::string> EvalState::resolveLookupPathPath(const LookupPath::Pa
}
if (pathExists(path))
res = { path };
return finish(std::move(path));
else {
logWarning({
.msg = HintFmt("Nix search path entry '%1%' does not exist, ignoring", value)
});
res = std::nullopt;
}
}
if (res)
debug("resolved search path element '%s' to '%s'", value, *res);
else
debug("failed to resolve search path element '%s'", value);
debug("failed to resolve search path element '%s'", value);
return std::nullopt;
lookupPathResolved.emplace(value, res);
return res;
}
@ -3002,7 +2826,7 @@ Expr * EvalState::parse(
const SourcePath & basePath,
std::shared_ptr<StaticEnv> & staticEnv)
{
auto result = parseExprFromBuf(text, length, origin, basePath, symbols, positions, rootFS, exprSymbols);
auto result = parseExprFromBuf(text, length, origin, basePath, symbols, settings, positions, rootFS, exprSymbols);
result->bindVars(*this, staticEnv);

View file

@ -3,6 +3,7 @@
#include "attr-set.hh"
#include "eval-error.hh"
#include "eval-gc.hh"
#include "types.hh"
#include "value.hh"
#include "nixexpr.hh"
@ -29,6 +30,7 @@ namespace nix {
constexpr size_t maxPrimOpArity = 8;
class Store;
struct EvalSettings;
class EvalState;
class StorePath;
struct SingleDerivedPath;
@ -38,7 +40,6 @@ namespace eval_cache {
class EvalCache;
}
/**
* Function that implements a primop.
*/
@ -146,12 +147,6 @@ std::string printValue(EvalState & state, Value & v);
std::ostream & operator << (std::ostream & os, const ValueType t);
/**
* Initialise the Boehm GC, if applicable.
*/
void initGC();
struct RegexCache;
std::shared_ptr<RegexCache> makeRegexCache();
@ -167,13 +162,17 @@ struct DebugTrace {
class EvalState : public std::enable_shared_from_this<EvalState>
{
public:
const EvalSettings & settings;
SymbolTable symbols;
PosTable positions;
const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue,
sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls,
sFile, sLine, sColumn, sFunctor, sToString,
sRight, sWrong, sStructuredAttrs, sBuilder, sArgs,
sRight, sWrong, sStructuredAttrs,
sAllowedReferences, sAllowedRequisites, sDisallowedReferences, sDisallowedRequisites,
sMaxSize, sMaxClosureSize,
sBuilder, sArgs,
sContentAddressed, sImpure,
sOutputHash, sOutputHashAlgo, sOutputHashMode,
sRecurseForDerivations,
@ -354,6 +353,7 @@ public:
EvalState(
const LookupPath & _lookupPath,
ref<Store> store,
const EvalSettings & settings,
std::shared_ptr<Store> buildStore = nullptr);
~EvalState();
@ -850,8 +850,10 @@ std::string showType(const Value & v);
/**
* If `path` refers to a directory, then append "/default.nix".
*
* @param addDefaultNix Whether to append "/default.nix" after resolving symlinks.
*/
SourcePath resolveExprPath(SourcePath path);
SourcePath resolveExprPath(SourcePath path, bool addDefaultNix = true);
/**
* Whether a URI is allowed, assuming restrictEval is enabled

View file

@ -374,21 +374,26 @@ static void getDerivations(EvalState & state, Value & vIn,
bound to the attribute with the "lower" name should take
precedence). */
for (auto & i : v.attrs()->lexicographicOrder(state.symbols)) {
debug("evaluating attribute '%1%'", state.symbols[i->name]);
if (!std::regex_match(std::string(state.symbols[i->name]), attrRegex))
continue;
std::string pathPrefix2 = addToPath(pathPrefix, state.symbols[i->name]);
if (combineChannels)
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
else if (getDerivation(state, *i->value, pathPrefix2, drvs, done, ignoreAssertionFailures)) {
/* If the value of this attribute is itself a set,
should we recurse into it? => Only if it has a
`recurseForDerivations = true' attribute. */
if (i->value->type() == nAttrs) {
auto j = i->value->attrs()->get(state.sRecurseForDerivations);
if (j && state.forceBool(*j->value, j->pos, "while evaluating the attribute `recurseForDerivations`"))
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
try {
debug("evaluating attribute '%1%'", state.symbols[i->name]);
if (!std::regex_match(std::string(state.symbols[i->name]), attrRegex))
continue;
std::string pathPrefix2 = addToPath(pathPrefix, state.symbols[i->name]);
if (combineChannels)
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
else if (getDerivation(state, *i->value, pathPrefix2, drvs, done, ignoreAssertionFailures)) {
/* If the value of this attribute is itself a set,
should we recurse into it? => Only if it has a
`recurseForDerivations = true' attribute. */
if (i->value->type() == nAttrs) {
auto j = i->value->attrs()->get(state.sRecurseForDerivations);
if (j && state.forceBool(*j->value, j->pos, "while evaluating the attribute `recurseForDerivations`"))
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
}
}
} catch (Error & e) {
e.addTrace(state.positions[i->pos], "while evaluating the attribute '%s'", state.symbols[i->name]);
throw;
}
}
}

View file

@ -8,7 +8,6 @@ libexpr_SOURCES := \
$(wildcard $(d)/*.cc) \
$(wildcard $(d)/value/*.cc) \
$(wildcard $(d)/primops/*.cc) \
$(wildcard $(d)/flake/*.cc) \
$(d)/lexer-tab.cc \
$(d)/parser-tab.cc
# Not just for this library itself, but also for downstream libraries using this library
@ -16,7 +15,7 @@ libexpr_SOURCES := \
INCLUDE_libexpr := -I $(d)
libexpr_CXXFLAGS += \
$(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libfetchers) $(INCLUDE_libmain) $(INCLUDE_libexpr) \
$(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libfetchers) $(INCLUDE_libexpr) \
-DGC_THREADS
libexpr_LIBS = libutil libstore libfetchers
@ -45,11 +44,7 @@ $(eval $(call install-file-in, $(buildprefix)$(d)/nix-expr.pc, $(libdir)/pkgconf
$(foreach i, $(wildcard src/libexpr/value/*.hh), \
$(eval $(call install-file-in, $(i), $(includedir)/nix/value, 0644)))
$(foreach i, $(wildcard src/libexpr/flake/*.hh), \
$(eval $(call install-file-in, $(i), $(includedir)/nix/flake, 0644)))
$(d)/primops.cc: $(d)/imported-drv-to-derivation.nix.gen.hh
$(d)/eval.cc: $(d)/primops/derivation.nix.gen.hh $(d)/fetchurl.nix.gen.hh $(d)/flake/call-flake.nix.gen.hh
$(buildprefix)src/libexpr/primops/fromTOML.o: ERROR_SWITCH_ENUM =
$(d)/eval.cc: $(d)/primops/derivation.nix.gen.hh $(d)/fetchurl.nix.gen.hh $(d)/call-flake.nix.gen.hh

202
src/libexpr/meson.build Normal file
View file

@ -0,0 +1,202 @@
project('nix-expr', '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'),
]
subdir('build-utils-meson/subprojects')
subdir('build-utils-meson/threads')
boost = dependency(
'boost',
modules : ['container', 'context'],
)
# boost is a public dependency, but not a pkg-config dependency unfortunately, so we
# put in `deps_other`.
deps_other += boost
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
deps_public += nlohmann_json
bdw_gc = dependency('bdw-gc', required : get_option('gc'))
if bdw_gc.found()
deps_public += bdw_gc
foreach funcspec : [
'pthread_attr_get_np',
'pthread_getattr_np',
]
define_name = 'HAVE_' + funcspec.underscorify().to_upper()
define_value = cxx.has_function(funcspec).to_int()
configdata.set(define_name, define_value)
endforeach
configdata.set('GC_THREADS', 1)
endif
configdata.set('HAVE_BOEHMGC', bdw_gc.found().to_int())
toml11 = dependency('toml11', version : '>=3.7.0', method : 'cmake')
deps_other += toml11
config_h = configure_file(
configuration : configdata,
output : 'config-expr.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',
language : 'cpp',
)
subdir('build-utils-meson/diagnostics')
parser_tab = custom_target(
input : 'parser.y',
output : [
'parser-tab.cc',
'parser-tab.hh',
],
command : [
'bison',
'-v',
'-o',
'@OUTPUT0@',
'@INPUT@',
'-d',
],
# NOTE(Qyriad): Meson doesn't support installing only part of a custom target, so we add
# an install script below which removes parser-tab.cc.
install : true,
install_dir : get_option('includedir') / 'nix',
)
lexer_tab = custom_target(
input : [
'lexer.l',
parser_tab,
],
output : [
'lexer-tab.cc',
'lexer-tab.hh',
],
command : [
'flex',
'--outfile',
'@OUTPUT0@',
'--header-file=' + '@OUTPUT1@',
'@INPUT0@',
],
# NOTE(Qyriad): Meson doesn't support installing only part of a custom target, so we add
# an install script below which removes lexer-tab.cc.
install : true,
install_dir : get_option('includedir') / 'nix',
)
subdir('build-utils-meson/generate-header')
generated_headers = []
foreach header : [
'imported-drv-to-derivation.nix',
'fetchurl.nix',
'call-flake.nix',
]
generated_headers += gen_header.process(header)
endforeach
sources = files(
'attr-path.cc',
'attr-set.cc',
'eval-cache.cc',
'eval-error.cc',
'eval-gc.cc',
'eval-settings.cc',
'eval.cc',
'function-trace.cc',
'get-drvs.cc',
'json-to-value.cc',
'nixexpr.cc',
'paths.cc',
'primops.cc',
'print-ambiguous.cc',
'print.cc',
'search-path.cc',
'value-to-json.cc',
'value-to-xml.cc',
'value/context.cc',
)
include_dirs = [include_directories('.')]
headers = [config_h] + files(
'attr-path.hh',
'attr-set.hh',
'eval-cache.hh',
'eval-error.hh',
'eval-gc.hh',
'eval-inline.hh',
'eval-settings.hh',
'eval.hh',
'function-trace.hh',
'gc-small-vector.hh',
'get-drvs.hh',
'json-to-value.hh',
'nixexpr.hh',
'parser-state.hh',
'pos-idx.hh',
'pos-table.hh',
'primops.hh',
'print-ambiguous.hh',
'print-options.hh',
'print.hh',
'repl-exit-status.hh',
'search-path.hh',
'symbol-table.hh',
'value-to-json.hh',
'value-to-xml.hh',
'value.hh',
'value/context.hh',
)
subdir('primops')
this_library = library(
'nixexpr',
sources,
parser_tab,
lexer_tab,
generated_headers,
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')

View file

@ -0,0 +1,3 @@
option('gc', type : 'feature',
description : 'enable garbage collection in the Nix expression evaluator (requires Boehm GC)',
)

117
src/libexpr/package.nix Normal file
View file

@ -0,0 +1,117 @@
{ lib
, stdenv
, mkMesonDerivation
, releaseTools
, meson
, ninja
, pkg-config
, bison
, flex
, cmake # for resolving toml11 dep
, nix-util
, nix-store
, nix-fetchers
, boost
, boehmgc
, nlohmann_json
, toml11
# Configuration Options
, version
# Whether to use garbage collection for the Nix language evaluator.
#
# If it is disabled, we just leak memory, but this is not as bad as it
# sounds so long as evaluation just takes places within short-lived
# processes. (When the process exits, the memory is reclaimed; it is
# only leaked *within* the process.)
#
# Temporarily disabled on Windows because the `GC_throw_bad_alloc`
# symbol is missing during linking.
, enableGC ? !stdenv.hostPlatform.isWindows
}:
let
inherit (lib) fileset;
in
mkMesonDerivation (finalAttrs: {
pname = "nix-expr";
inherit version;
workDir = ./.;
fileset = fileset.unions [
../../build-utils-meson
./build-utils-meson
../../.version
./.version
./meson.build
./meson.options
./primops/meson.build
(fileset.fileFilter (file: file.hasExt "cc") ./.)
(fileset.fileFilter (file: file.hasExt "hh") ./.)
./lexer.l
./parser.y
(fileset.fileFilter (file: file.hasExt "nix") ./.)
];
outputs = [ "out" "dev" ];
nativeBuildInputs = [
meson
ninja
pkg-config
bison
flex
cmake
];
buildInputs = [
toml11
];
propagatedBuildInputs = [
nix-util
nix-store
nix-fetchers
boost
nlohmann_json
] ++ lib.optional enableGC boehmgc;
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 "gc" enableGC)
];
env = {
# Needed for Meson to find Boost.
# https://github.com/NixOS/nixpkgs/issues/86131.
BOOST_INCLUDEDIR = "${lib.getDev boost}/include";
BOOST_LIBRARYDIR = "${lib.getLib boost}/lib";
} // lib.optionalAttrs (stdenv.isLinux && !(stdenv.hostPlatform.isStatic && stdenv.system == "aarch64-linux")) {
LDFLAGS = "-fuse-ld=gold";
};
enableParallelBuilding = true;
separateDebugInfo = !stdenv.hostPlatform.isStatic;
strictDeps = true;
hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie";
meta = {
platforms = lib.platforms.unix ++ lib.platforms.windows;
};
})

View file

@ -46,6 +46,7 @@ struct ParserState
PosTable::Origin origin;
const ref<SourceAccessor> rootFS;
const Expr::AstSymbols & s;
const EvalSettings & settings;
void dupAttr(const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos);
void dupAttr(Symbol attr, const PosIdx pos, const PosIdx prevPos);

View file

@ -25,7 +25,6 @@
#include "nixexpr.hh"
#include "eval.hh"
#include "eval-settings.hh"
#include "globals.hh"
#include "parser-state.hh"
#define YYLTYPE ::nix::ParserLocation
@ -40,6 +39,7 @@ Expr * parseExprFromBuf(
Pos::Origin origin,
const SourcePath & basePath,
SymbolTable & symbols,
const EvalSettings & settings,
PosTable & positions,
const ref<SourceAccessor> rootFS,
const Expr::AstSymbols & astSymbols);
@ -294,7 +294,7 @@ path_start
$$ = new ExprPath(ref<SourceAccessor>(state->rootFS), std::move(path));
}
| HPATH {
if (evalSettings.pureEval) {
if (state->settings.pureEval) {
throw Error(
"the path '%s' can not be resolved in pure mode",
std::string_view($1.p, $1.l)
@ -429,6 +429,7 @@ Expr * parseExprFromBuf(
Pos::Origin origin,
const SourcePath & basePath,
SymbolTable & symbols,
const EvalSettings & settings,
PosTable & positions,
const ref<SourceAccessor> rootFS,
const Expr::AstSymbols & astSymbols)
@ -441,6 +442,7 @@ Expr * parseExprFromBuf(
.origin = positions.addOrigin(origin, length),
.rootFS = rootFS,
.s = astSymbols,
.settings = settings,
};
yylex_init(&scanner);

View file

@ -5,7 +5,6 @@
#include "eval.hh"
#include "eval-settings.hh"
#include "gc-small-vector.hh"
#include "globals.hh"
#include "json-to-value.hh"
#include "names.hh"
#include "path-references.hh"
@ -78,8 +77,8 @@ StringMap EvalState::realiseContext(const NixStringContext & context, StorePathS
if (drvs.empty()) return {};
if (isIFD && !evalSettings.enableImportFromDerivation)
error<EvalError>(
if (isIFD && !settings.enableImportFromDerivation)
error<EvalBaseError>(
"cannot build '%1%' during evaluation because the option 'allow-import-from-derivation' is disabled",
drvs.begin()->to_string(*store)
).debugThrow();
@ -733,11 +732,12 @@ static RegisterPrimOp primop_genericClosure(PrimOp {
Each attribute set in the list `startSet` and the list returned by `operator` must have an attribute `key`, which must support equality comparison.
The value of `key` can be one of the following types:
- [Number](@docroot@/language/values.md#type-number)
- [Boolean](@docroot@/language/values.md#type-boolean)
- [String](@docroot@/language/values.md#type-string)
- [Path](@docroot@/language/values.md#type-path)
- [List](@docroot@/language/values.md#list)
- [Int](@docroot@/language/types.md#type-int)
- [Float](@docroot@/language/types.md#type-float)
- [Boolean](@docroot@/language/types.md#type-boolean)
- [String](@docroot@/language/types.md#type-string)
- [Path](@docroot@/language/types.md#type-path)
- [List](@docroot@/language/types.md#list)
The result is produced by calling the `operator` on each `item` that has not been called yet, including newly added items, until no new items are added.
Items are compared by their `key` attribute.
@ -901,7 +901,7 @@ static void prim_tryEval(EvalState & state, const PosIdx pos, Value * * args, Va
MaintainCount trylevel(state.trylevel);
ReplExitStatus (* savedDebugRepl)(ref<EvalState> es, const ValMap & extraEnv) = nullptr;
if (state.debugRepl && evalSettings.ignoreExceptionsDuringTry)
if (state.debugRepl && state.settings.ignoreExceptionsDuringTry)
{
/* to prevent starting the repl from exceptions withing a tryEval, null it. */
savedDebugRepl = state.debugRepl;
@ -950,7 +950,7 @@ static RegisterPrimOp primop_tryEval({
static void prim_getEnv(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
std::string name(state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.getEnv"));
v.mkString(evalSettings.restrictEval || evalSettings.pureEval ? "" : getEnv(name).value_or(""));
v.mkString(state.settings.restrictEval || state.settings.pureEval ? "" : getEnv(name).value_or(""));
}
static RegisterPrimOp primop_getEnv({
@ -1017,7 +1017,7 @@ static void prim_trace(EvalState & state, const PosIdx pos, Value * * args, Valu
printError("trace: %1%", args[0]->string_view());
else
printError("trace: %1%", ValuePrinter(state, *args[0]));
if (evalSettings.builtinsTraceDebugger) {
if (state.settings.builtinsTraceDebugger) {
state.runDebugRepl(nullptr);
}
state.forceValue(*args[1], pos);
@ -1056,11 +1056,11 @@ static void prim_warn(EvalState & state, const PosIdx pos, Value * * args, Value
logWarning(info);
}
if (evalSettings.builtinsAbortOnWarn) {
if (state.settings.builtinsAbortOnWarn) {
// Not an EvalError or subclass, which would cause the error to be stored in the eval cache.
state.error<EvalBaseError>("aborting to reveal stack trace of warning, as abort-on-warn is set").setIsFromExpr().debugThrow();
}
if (evalSettings.builtinsTraceDebugger || evalSettings.builtinsDebuggerOnWarn) {
if (state.settings.builtinsTraceDebugger || state.settings.builtinsDebuggerOnWarn) {
state.runDebugRepl(nullptr);
}
state.forceValue(*args[1], pos);
@ -1163,12 +1163,34 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
}
}
/**
* Early validation for the derivation name, for better error message.
* It is checked again when constructing store paths.
*
* @todo Check that the `.drv` suffix also fits.
*/
static void checkDerivationName(EvalState & state, std::string_view drvName)
{
try {
checkName(drvName);
} catch (BadStorePathName & e) {
// "Please pass a different name": Users may not be aware that they can
// pass a different one, in functions like `fetchurl` where the name
// is optional.
// Note that Nixpkgs generally won't trigger this, because `mkDerivation`
// sanitizes the name.
state.error<EvalError>("invalid derivation name: %s. Please pass a different '%s'.", Uncolored(e.message()), "name").debugThrow();
}
}
static void derivationStrictInternal(
EvalState & state,
const std::string & drvName,
const Bindings * attrs,
Value & v)
{
checkDerivationName(state, drvName);
/* Check whether attributes should be passed as a JSON file. */
using nlohmann::json;
std::optional<json> jsonObject;
@ -1209,7 +1231,7 @@ static void derivationStrictInternal(
auto handleHashMode = [&](const std::string_view s) {
if (s == "recursive") {
// back compat, new name is "nar"
ingestionMethod = FileIngestionMethod::Recursive;
ingestionMethod = ContentAddressMethod::Raw::NixArchive;
} else try {
ingestionMethod = ContentAddressMethod::parse(s);
} catch (UsageError &) {
@ -1217,9 +1239,9 @@ static void derivationStrictInternal(
"invalid value '%s' for 'outputHashMode' attribute", s
).atPos(v).debugThrow();
}
if (ingestionMethod == TextIngestionMethod {})
if (ingestionMethod == ContentAddressMethod::Raw::Text)
experimentalFeatureSettings.require(Xp::DynamicDerivations);
if (ingestionMethod == FileIngestionMethod::Git)
if (ingestionMethod == ContentAddressMethod::Raw::Git)
experimentalFeatureSettings.require(Xp::GitHashing);
};
@ -1308,6 +1330,20 @@ static void derivationStrictInternal(
handleOutputs(ss);
}
if (i->name == state.sAllowedReferences)
warn("In a derivation named '%s', 'structuredAttrs' disables the effect of the derivation attribute 'allowedReferences'; use 'outputChecks.<output>.allowedReferences' instead", drvName);
if (i->name == state.sAllowedRequisites)
warn("In a derivation named '%s', 'structuredAttrs' disables the effect of the derivation attribute 'allowedRequisites'; use 'outputChecks.<output>.allowedRequisites' instead", drvName);
if (i->name == state.sDisallowedReferences)
warn("In a derivation named '%s', 'structuredAttrs' disables the effect of the derivation attribute 'disallowedReferences'; use 'outputChecks.<output>.disallowedReferences' instead", drvName);
if (i->name == state.sDisallowedRequisites)
warn("In a derivation named '%s', 'structuredAttrs' disables the effect of the derivation attribute 'disallowedRequisites'; use 'outputChecks.<output>.disallowedRequisites' instead", drvName);
if (i->name == state.sMaxSize)
warn("In a derivation named '%s', 'structuredAttrs' disables the effect of the derivation attribute 'maxSize'; use 'outputChecks.<output>.maxSize' instead", drvName);
if (i->name == state.sMaxClosureSize)
warn("In a derivation named '%s', 'structuredAttrs' disables the effect of the derivation attribute 'maxClosureSize'; use 'outputChecks.<output>.maxClosureSize' instead", drvName);
} else {
auto s = state.coerceToString(pos, *i->value, context, context_below, true).toOwned();
drv.env.emplace(key, s);
@ -1377,7 +1413,7 @@ static void derivationStrictInternal(
/* Check whether the derivation name is valid. */
if (isDerivation(drvName) &&
!(ingestionMethod == ContentAddressMethod { TextIngestionMethod { } } &&
!(ingestionMethod == ContentAddressMethod::Raw::Text &&
outputs.size() == 1 &&
*(outputs.begin()) == "out"))
{
@ -1399,7 +1435,7 @@ static void derivationStrictInternal(
auto h = newHashAllowEmpty(*outputHash, outputHashAlgo);
auto method = ingestionMethod.value_or(FileIngestionMethod::Flat);
auto method = ingestionMethod.value_or(ContentAddressMethod::Raw::Flat);
DerivationOutput::CAFixed dof {
.ca = ContentAddress {
@ -1418,7 +1454,7 @@ static void derivationStrictInternal(
.atPos(v).debugThrow();
auto ha = outputHashAlgo.value_or(HashAlgorithm::SHA256);
auto method = ingestionMethod.value_or(FileIngestionMethod::Recursive);
auto method = ingestionMethod.value_or(ContentAddressMethod::Raw::NixArchive);
for (auto & i : outputs) {
drv.env[i] = hashPlaceholder(i);
@ -1564,7 +1600,7 @@ static RegisterPrimOp primop_toPath({
corner cases. */
static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
if (evalSettings.pureEval)
if (state.settings.pureEval)
state.error<EvalError>(
"'%s' is not allowed in pure evaluation mode",
"builtins.storePath"
@ -1674,7 +1710,7 @@ static RegisterPrimOp primop_baseNameOf({
.name = "baseNameOf",
.args = {"x"},
.doc = R"(
Return the *base name* of either a [path value](@docroot@/language/values.md#type-path) *x* or a string *x*, depending on which type is passed, and according to the following rules.
Return the *base name* of either a [path value](@docroot@/language/types.md#type-path) *x* or a string *x*, depending on which type is passed, and according to the following rules.
For a path value, the *base name* is considered to be the part of the path after the last directory separator, including any file extensions.
This is the simple case, as path values don't have trailing slashes.
@ -1808,7 +1844,7 @@ static RegisterPrimOp primop_findFile(PrimOp {
.doc = R"(
Find *lookup-path* in *search-path*.
A search path is represented list of [attribute sets](./values.md#attribute-set) with two attributes:
A search path is represented list of [attribute sets](./types.md#attribute-set) with two attributes:
- `prefix` is a relative path.
- `path` denotes a file system location
The exact syntax depends on the command line interface.
@ -1829,14 +1865,14 @@ static RegisterPrimOp primop_findFile(PrimOp {
}
```
The lookup algorithm checks each entry until a match is found, returning a [path value](@docroot@/language/values.html#type-path) of the match:
The lookup algorithm checks each entry until a match is found, returning a [path value](@docroot@/language/types.md#type-path) of the match:
- If *lookup-path* matches `prefix`, then the remainder of *lookup-path* (the "suffix") is searched for within the directory denoted by `path`.
Note that the `path` may need to be downloaded at this point to look inside.
- If the suffix is found inside that directory, then the entry is a match.
The combined absolute path of the directory (now downloaded if need be) and the suffix is returned.
[Lookup path](@docroot@/language/constructs/lookup-path.md) expressions are [desugared](https://en.wikipedia.org/wiki/Syntactic_sugar) using this and [`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtins-nixPath):
[Lookup path](@docroot@/language/constructs/lookup-path.md) expressions are [desugared](https://en.wikipedia.org/wiki/Syntactic_sugar) using this and [`builtins.nixPath`](#builtins-nixPath):
```nix
<nixpkgs>
@ -2194,7 +2230,7 @@ static void prim_toFile(EvalState & state, const PosIdx pos, Value * * args, Val
})
: ({
StringSource s { contents };
state.store->addToStoreFromDump(s, name, FileSerialisationMethod::Flat, TextIngestionMethod {}, HashAlgorithm::SHA256, refs, state.repair);
state.store->addToStoreFromDump(s, name, FileSerialisationMethod::Flat, ContentAddressMethod::Raw::Text, HashAlgorithm::SHA256, refs, state.repair);
});
/* Note: we don't need to add `context' to the context of the
@ -2257,7 +2293,7 @@ static RegisterPrimOp primop_toFile({
```
Note that `${configFile}` is a
[string interpolation](@docroot@/language/values.md#type-string), so the result of the
[string interpolation](@docroot@/language/types.md#type-string), so the result of the
expression `configFile`
(i.e., a path like `/nix/store/m7p7jfny445k...-foo.conf`) will be
spliced into the resulting string.
@ -2377,7 +2413,7 @@ static void prim_filterSource(EvalState & state, const PosIdx pos, Value * * arg
"while evaluating the second argument (the path to filter) passed to 'builtins.filterSource'");
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.filterSource");
addPath(state, pos, path.baseName(), path, args[0], FileIngestionMethod::Recursive, std::nullopt, v, context);
addPath(state, pos, path.baseName(), path, args[0], ContentAddressMethod::Raw::NixArchive, std::nullopt, v, context);
}
static RegisterPrimOp primop_filterSource({
@ -2440,7 +2476,7 @@ static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value
std::optional<SourcePath> path;
std::string name;
Value * filterFun = nullptr;
ContentAddressMethod method = FileIngestionMethod::Recursive;
auto method = ContentAddressMethod::Raw::NixArchive;
std::optional<Hash> expectedHash;
NixStringContext context;
@ -2456,8 +2492,8 @@ static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value
state.forceFunction(*(filterFun = attr.value), attr.pos, "while evaluating the `filter` parameter passed to builtins.path");
else if (n == "recursive")
method = state.forceBool(*attr.value, attr.pos, "while evaluating the `recursive` attribute passed to builtins.path")
? FileIngestionMethod::Recursive
: FileIngestionMethod::Flat;
? ContentAddressMethod::Raw::NixArchive
: ContentAddressMethod::Raw::Flat;
else if (n == "sha256")
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `sha256` attribute passed to builtins.path"), HashAlgorithm::SHA256);
else
@ -4483,7 +4519,7 @@ void EvalState::createBaseEnv()
addConstant("builtins", v, {
.type = nAttrs,
.doc = R"(
Contains all the [built-in functions](@docroot@/language/builtins.md) and values.
Contains all the built-in functions and values.
Since built-in functions were added over time, [testing for attributes](./operators.md#has-attribute) in `builtins` can be used for graceful fallback on older Nix installations:
@ -4503,7 +4539,7 @@ void EvalState::createBaseEnv()
It can be returned by
[comparison operators](@docroot@/language/operators.md#Comparison)
and used in
[conditional expressions](@docroot@/language/constructs.md#Conditionals).
[conditional expressions](@docroot@/language/syntax.md#Conditionals).
The name `true` is not special, and can be shadowed:
@ -4523,7 +4559,7 @@ void EvalState::createBaseEnv()
It can be returned by
[comparison operators](@docroot@/language/operators.md#Comparison)
and used in
[conditional expressions](@docroot@/language/constructs.md#Conditionals).
[conditional expressions](@docroot@/language/syntax.md#Conditionals).
The name `false` is not special, and can be shadowed:
@ -4548,7 +4584,7 @@ void EvalState::createBaseEnv()
)",
});
if (!evalSettings.pureEval) {
if (!settings.pureEval) {
v.mkInt(time(0));
}
addConstant("__currentTime", v, {
@ -4575,8 +4611,8 @@ void EvalState::createBaseEnv()
.impureOnly = true,
});
if (!evalSettings.pureEval)
v.mkString(evalSettings.getCurrentSystem());
if (!settings.pureEval)
v.mkString(settings.getCurrentSystem());
addConstant("__currentSystem", v, {
.type = nString,
.doc = R"(
@ -4656,7 +4692,7 @@ void EvalState::createBaseEnv()
#ifndef _WIN32 // TODO implement on Windows
// Miscellaneous
if (evalSettings.enableNativeCode) {
if (settings.enableNativeCode) {
addPrimOp({
.name = "__importNative",
.arity = 2,
@ -4679,7 +4715,7 @@ void EvalState::createBaseEnv()
error if `--trace-verbose` is enabled. Then return *e2*. This function
is useful for debugging.
)",
.fun = evalSettings.traceVerbose ? prim_trace : prim_second,
.fun = settings.traceVerbose ? prim_trace : prim_second,
});
/* Add a value containing the current Nix expression search path. */

View file

@ -53,7 +53,7 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
// whitelist. Ah well.
state.checkURI(url);
if (evalSettings.pureEval && !rev)
if (state.settings.pureEval && !rev)
throw Error("in pure evaluation mode, 'fetchMercurial' requires a Mercurial revision");
fetchers::Attrs attrs;

View file

@ -1,4 +1,4 @@
#include "libfetchers/attrs.hh"
#include "attrs.hh"
#include "primops.hh"
#include "eval-inline.hh"
#include "eval-settings.hh"
@ -171,10 +171,10 @@ static void fetchTree(
}
}
if (!evalSettings.pureEval && !input.isDirect() && experimentalFeatureSettings.isEnabled(Xp::Flakes))
if (!state.settings.pureEval && !input.isDirect() && experimentalFeatureSettings.isEnabled(Xp::Flakes))
input = lookupInRegistries(state.store, input).first;
if (evalSettings.pureEval && !input.isLocked()) {
if (state.settings.pureEval && !input.isLocked()) {
auto fetcher = "fetchTree";
if (params.isFetchGit)
fetcher = "fetchGit";
@ -431,7 +431,10 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
state.forceValue(*args[0], pos);
if (args[0]->type() == nAttrs) {
bool isArgAttrs = args[0]->type() == nAttrs;
bool nameAttrPassed = false;
if (isArgAttrs) {
for (auto & attr : *args[0]->attrs()) {
std::string_view n(state.symbols[attr.name]);
@ -439,8 +442,10 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
url = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the url we should fetch");
else if (n == "sha256")
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the sha256 of the content we should fetch"), HashAlgorithm::SHA256);
else if (n == "name")
else if (n == "name") {
nameAttrPassed = true;
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the name of the content we should fetch");
}
else
state.error<EvalError>("unsupported argument '%s' to '%s'", n, who)
.atPos(pos).debugThrow();
@ -453,14 +458,27 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch");
if (who == "fetchTarball")
url = evalSettings.resolvePseudoUrl(*url);
url = state.settings.resolvePseudoUrl(*url);
state.checkURI(*url);
if (name == "")
name = baseNameOf(*url);
if (evalSettings.pureEval && !expectedHash)
try {
checkName(name);
} catch (BadStorePathName & e) {
auto resolution =
nameAttrPassed ? HintFmt("Please change the value for the 'name' attribute passed to '%s', so that it can create a valid store path.", who) :
isArgAttrs ? HintFmt("Please add a valid 'name' attribute to the argument for '%s', so that it can create a valid store path.", who) :
HintFmt("Please pass an attribute set with 'url' and 'name' attributes to '%s', so that it can create a valid store path.", who);
state.error<EvalError>(
std::string("invalid store path name when fetching URL '%s': %s. %s"), *url, Uncolored(e.message()), Uncolored(resolution.str()))
.atPos(pos).debugThrow();
}
if (state.settings.pureEval && !expectedHash)
state.error<EvalError>("in pure evaluation mode, '%s' requires a 'sha256' argument", who).atPos(pos).debugThrow();
// early exit if pinned and already in the store
@ -468,7 +486,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
auto expectedPath = state.store->makeFixedOutputPath(
name,
FixedOutputInfo {
.method = unpack ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat,
.method = unpack ? FileIngestionMethod::NixArchive : FileIngestionMethod::Flat,
.hash = *expectedHash,
.references = {}
});

View file

@ -1,9 +1,8 @@
#include "primops.hh"
#include "eval-inline.hh"
#include "../../toml11/toml.hpp"
#include <sstream>
#include <toml.hpp>
namespace nix {

View file

@ -0,0 +1,12 @@
generated_headers += gen_header.process(
'derivation.nix',
preserve_path_from: meson.project_source_root(),
)
sources += files(
'context.cc',
'fetchClosure.cc',
'fetchMercurial.cc',
'fetchTree.cc',
'fromTOML.cc',
)

View file

@ -163,8 +163,8 @@ private:
EvalState & state;
PrintOptions options;
std::optional<ValuesSeen> seen;
size_t attrsPrinted = 0;
size_t listItemsPrinted = 0;
size_t totalAttrsPrinted = 0;
size_t totalListItemsPrinted = 0;
std::string indent;
void increaseIndent()
@ -345,11 +345,13 @@ private:
auto prettyPrint = shouldPrettyPrintAttrs(sorted);
size_t currentAttrsPrinted = 0;
for (auto & i : sorted) {
printSpace(prettyPrint);
if (attrsPrinted >= options.maxAttrs) {
printElided(sorted.size() - attrsPrinted, "attribute", "attributes");
if (totalAttrsPrinted >= options.maxAttrs) {
printElided(sorted.size() - currentAttrsPrinted, "attribute", "attributes");
break;
}
@ -357,7 +359,8 @@ private:
output << " = ";
print(*i.second, depth + 1);
output << ";";
attrsPrinted++;
totalAttrsPrinted++;
currentAttrsPrinted++;
}
decreaseIndent();
@ -402,11 +405,14 @@ private:
output << "[";
auto listItems = v.listItems();
auto prettyPrint = shouldPrettyPrintList(listItems);
size_t currentListItemsPrinted = 0;
for (auto elem : listItems) {
printSpace(prettyPrint);
if (listItemsPrinted >= options.maxListItems) {
printElided(listItems.size() - listItemsPrinted, "item", "items");
if (totalListItemsPrinted >= options.maxListItems) {
printElided(listItems.size() - currentListItemsPrinted, "item", "items");
break;
}
@ -415,7 +421,8 @@ private:
} else {
printNullptr();
}
listItemsPrinted++;
totalListItemsPrinted++;
currentListItemsPrinted++;
}
decreaseIndent();
@ -588,8 +595,8 @@ public:
void print(Value & v)
{
attrsPrinted = 0;
listItemsPrinted = 0;
totalAttrsPrinted = 0;
totalListItemsPrinted = 0;
indent.clear();
if (options.trackRepeated) {

1
src/libfetchers/.version Symbolic link
View file

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

View file

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

View file

@ -1,4 +1,5 @@
#include "fetch-settings.hh"
#include "config-global.hh"
namespace nix {

View file

@ -70,30 +70,6 @@ struct FetchSettings : public Config
Setting<bool> warnDirty{this, true, "warn-dirty",
"Whether to warn about dirty Git/Mercurial trees."};
Setting<std::string> flakeRegistry{this, "https://channels.nixos.org/flake-registry.json", "flake-registry",
R"(
Path or URI of the global flake registry.
When empty, disables the global flake registry.
)",
{}, true, Xp::Flakes};
Setting<bool> useRegistries{this, true, "use-registries",
"Whether to use flake registries to resolve flake references.",
{}, true, Xp::Flakes};
Setting<bool> acceptFlakeConfig{this, false, "accept-flake-config",
"Whether to accept nix configuration from a flake without prompting.",
{}, true, Xp::Flakes};
Setting<std::string> commitLockFileSummary{
this, "", "commit-lock-file-summary",
R"(
The commit summary to use when committing changed flake lock files. If
empty, the summary is generated based on the action performed.
)",
{"commit-lockfile-summary"}, true, Xp::Flakes};
Setting<bool> trustTarballsFromGitForges{
this, true, "trust-tarballs-from-git-forges",
R"(
@ -108,7 +84,6 @@ struct FetchSettings : public Config
`narHash` attribute is specified,
e.g. `github:NixOS/patchelf/7c2f768bf9601268a4e71c2ebe91e2011918a70f?narHash=sha256-PPXqKY2hJng4DBVE0I4xshv/vGLUskL7jl53roB8UdU%3D`.
)"};
};
// FIXME: don't use a global variable.

View file

@ -18,7 +18,7 @@ StorePath fetchToStore(
const SourcePath & path,
FetchMode mode,
std::string_view name = "source",
ContentAddressMethod method = FileIngestionMethod::Recursive,
ContentAddressMethod method = ContentAddressMethod::Raw::NixArchive,
PathFilter * filter = nullptr,
RepairFlag repair = NoRepair);

View file

@ -260,6 +260,7 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> sto
auto [accessor, final] = scheme->getAccessor(store, *this);
assert(!accessor->fingerprint);
accessor->fingerprint = scheme->getFingerprint(store, final);
return {accessor, std::move(final)};
@ -305,7 +306,7 @@ StorePath Input::computeStorePath(Store & store) const
if (!narHash)
throw Error("cannot compute store path for unlocked input '%s'", to_string());
return store.makeFixedOutputPath(getName(), FixedOutputInfo {
.method = FileIngestionMethod::Recursive,
.method = FileIngestionMethod::NixArchive,
.hash = *narHash,
.references = {},
});
@ -418,7 +419,7 @@ namespace nlohmann {
using namespace nix;
fetchers::PublicKey adl_serializer<fetchers::PublicKey>::from_json(const json & json) {
fetchers::PublicKey res = { };
fetchers::PublicKey res = { };
if (auto type = optionalValueAt(json, "type"))
res.type = getString(*type);

View file

@ -851,10 +851,10 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
}
void createRegularFile(
const Path & path,
const CanonPath & path,
std::function<void(CreateRegularFileSink &)> func) override
{
auto pathComponents = tokenizeString<std::vector<std::string>>(path, "/");
auto pathComponents = tokenizeString<std::vector<std::string>>(path.rel(), "/");
if (!prepareDirs(pathComponents, false)) return;
git_writestream * stream = nullptr;
@ -862,11 +862,11 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
throw Error("creating a blob stream object: %s", git_error_last()->message);
struct CRF : CreateRegularFileSink {
const Path & path;
const CanonPath & path;
GitFileSystemObjectSinkImpl & back;
git_writestream * stream;
bool executable = false;
CRF(const Path & path, GitFileSystemObjectSinkImpl & back, git_writestream * stream)
CRF(const CanonPath & path, GitFileSystemObjectSinkImpl & back, git_writestream * stream)
: path(path), back(back), stream(stream)
{}
void operator () (std::string_view data) override
@ -891,15 +891,15 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
: GIT_FILEMODE_BLOB);
}
void createDirectory(const Path & path) override
void createDirectory(const CanonPath & path) override
{
auto pathComponents = tokenizeString<std::vector<std::string>>(path, "/");
auto pathComponents = tokenizeString<std::vector<std::string>>(path.rel(), "/");
(void) prepareDirs(pathComponents, true);
}
void createSymlink(const Path & path, const std::string & target) override
void createSymlink(const CanonPath & path, const std::string & target) override
{
auto pathComponents = tokenizeString<std::vector<std::string>>(path, "/");
auto pathComponents = tokenizeString<std::vector<std::string>>(path.rel(), "/");
if (!prepareDirs(pathComponents, false)) return;
git_oid oid;

View file

@ -41,21 +41,6 @@ bool isCacheFileWithinTtl(time_t now, const struct stat & st)
return st.st_mtime + settings.tarballTtl > now;
}
bool touchCacheFile(const Path & path, time_t touch_time)
{
#ifndef _WIN32 // TODO implement
struct timeval times[2];
times[0].tv_sec = touch_time;
times[0].tv_usec = 0;
times[1].tv_sec = touch_time;
times[1].tv_usec = 0;
return lutimes(path.c_str(), times) == 0;
#else
return false;
#endif
}
Path getCachePath(std::string_view key, bool shallow)
{
return getCacheDir()
@ -594,8 +579,11 @@ struct GitInputScheme : InputScheme
warn("could not update local clone of Git repository '%s'; continuing with the most recent version", repoInfo.url);
}
if (!touchCacheFile(localRefFile, now))
warn("could not update mtime for file '%s': %s", localRefFile, strerror(errno));
try {
setWriteTime(localRefFile, now, now);
} catch (Error & e) {
warn("could not update mtime for file '%s': %s", localRefFile, e.msg());
}
if (!originalRef && !storeCachedHead(repoInfo.url, ref))
warn("could not update cached head '%s' for '%s'", ref, repoInfo.url);
}

View file

@ -433,7 +433,7 @@ struct GitLabInputScheme : GitArchiveInputScheme
store->toRealPath(
downloadFile(store, url, "source", headers).storePath)));
if (json.is_array() && json.size() == 1 && json[0]["id"] != nullptr) {
if (json.is_array() && json.size() >= 1 && json[0]["id"] != nullptr) {
return RefInfo {
.rev = Hash::parseAny(std::string(json[0]["id"]), HashAlgorithm::SHA1)
};

View file

@ -213,7 +213,7 @@ struct MercurialInputScheme : InputScheme
auto storePath = store->addToStore(
input.getName(),
{getFSSourceAccessor(), CanonPath(actualPath)},
FileIngestionMethod::Recursive, HashAlgorithm::SHA256, {},
ContentAddressMethod::Raw::NixArchive, HashAlgorithm::SHA256, {},
filter);
return storePath;

View file

@ -0,0 +1,93 @@
project('nix-fetchers', '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'),
]
subdir('build-utils-meson/subprojects')
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
deps_public += nlohmann_json
libgit2 = dependency('libgit2')
deps_private += libgit2
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',
language : 'cpp',
)
subdir('build-utils-meson/diagnostics')
sources = files(
'attrs.cc',
'cache.cc',
'fetch-settings.cc',
'fetch-to-store.cc',
'fetchers.cc',
'filtering-source-accessor.cc',
'git.cc',
'git-utils.cc',
'github.cc',
'indirect.cc',
'mercurial.cc',
'mounted-source-accessor.cc',
'path.cc',
'store-path-accessor.cc',
'registry.cc',
'tarball.cc',
)
include_dirs = [include_directories('.')]
headers = files(
'attrs.hh',
'cache.hh',
'fetch-settings.hh',
'fetch-to-store.hh',
'filtering-source-accessor.hh',
'git-utils.hh',
'mounted-source-accessor.hh',
'fetchers.hh',
'registry.hh',
'store-path-accessor.hh',
'tarball.hh',
)
this_library = library(
'nixfetchers',
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')

View file

@ -0,0 +1,84 @@
{ lib
, stdenv
, mkMesonDerivation
, releaseTools
, meson
, ninja
, pkg-config
, nix-util
, nix-store
, nlohmann_json
, libgit2
, man
# Configuration Options
, version
}:
let
inherit (lib) fileset;
in
mkMesonDerivation (finalAttrs: {
pname = "nix-fetchers";
inherit version;
workDir = ./.;
fileset = fileset.unions [
../../build-utils-meson
./build-utils-meson
../../.version
./.version
./meson.build
(fileset.fileFilter (file: file.hasExt "cc") ./.)
(fileset.fileFilter (file: file.hasExt "hh") ./.)
];
outputs = [ "out" "dev" ];
nativeBuildInputs = [
meson
ninja
pkg-config
];
buildInputs = [
libgit2
];
propagatedBuildInputs = [
nix-store
nix-util
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
'';
env = lib.optionalAttrs (stdenv.isLinux && !(stdenv.hostPlatform.isStatic && stdenv.system == "aarch64-linux")) {
LDFLAGS = "-fuse-ld=gold";
};
enableParallelBuilding = true;
separateDebugInfo = !stdenv.hostPlatform.isStatic;
# TODO `releaseTools.coverageAnalysis` in Nixpkgs needs to be updated
# to work with `strictDeps`.
strictDeps = true;
hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie";
meta = {
platforms = lib.platforms.unix ++ lib.platforms.windows;
};
})

View file

@ -1,12 +1,11 @@
#include "registry.hh"
#include "tarball.hh"
#include "users.hh"
#include "config-global.hh"
#include "globals.hh"
#include "store-api.hh"
#include "local-fs-store.hh"
#include "fetch-settings.hh"
#include <nlohmann/json.hpp>
namespace nix::fetchers {
@ -149,10 +148,25 @@ void overrideRegistry(
flagRegistry->add(from, to, extraAttrs);
}
struct RegistrySettings : Config
{
Setting<std::string> flakeRegistry{this, "https://channels.nixos.org/flake-registry.json", "flake-registry",
R"(
Path or URI of the global flake registry.
When empty, disables the global flake registry.
)",
{}, true, Xp::Flakes};
};
RegistrySettings registrySettings;
static GlobalConfig::Register rRegistrySettings(&registrySettings);
static std::shared_ptr<Registry> getGlobalRegistry(ref<Store> store)
{
static auto reg = [&]() {
auto path = fetchSettings.flakeRegistry.get();
auto path = registrySettings.flakeRegistry.get();
if (path == "") {
return std::make_shared<Registry>(Registry::Global); // empty registry
}

View file

@ -365,6 +365,16 @@ struct TarballInputScheme : CurlInputScheme
return {result.accessor, input};
}
std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const override
{
if (auto narHash = input.getNarHash())
return narHash->to_string(HashFormat::SRI, true);
else if (auto rev = input.getRev())
return rev->gitRev();
else
return std::nullopt;
}
};
static auto rTarballInputScheme = OnStartup([] { registerInputScheme(std::make_unique<TarballInputScheme>()); });

1
src/libflake/.version Symbolic link
View file

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

View file

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

View file

@ -0,0 +1,12 @@
#include "flake-settings.hh"
#include "config-global.hh"
namespace nix {
FlakeSettings::FlakeSettings() {}
FlakeSettings flakeSettings;
static GlobalConfig::Register rFlakeSettings(&flakeSettings);
}

View file

@ -0,0 +1,53 @@
#pragma once
///@file
#include "types.hh"
#include "config.hh"
#include "util.hh"
#include <map>
#include <limits>
#include <sys/types.h>
namespace nix {
struct FlakeSettings : public Config
{
FlakeSettings();
Setting<bool> useRegistries{
this,
true,
"use-registries",
"Whether to use flake registries to resolve flake references.",
{},
true,
Xp::Flakes};
Setting<bool> acceptFlakeConfig{
this,
false,
"accept-flake-config",
"Whether to accept nix configuration from a flake without prompting.",
{},
true,
Xp::Flakes};
Setting<std::string> commitLockFileSummary{
this,
"",
"commit-lock-file-summary",
R"(
The commit summary to use when committing changed flake lock files. If
empty, the summary is generated based on the action performed.
)",
{"commit-lockfile-summary"},
true,
Xp::Flakes};
};
// TODO: don't use a global variable.
extern FlakeSettings flakeSettings;
}

View file

@ -1,6 +1,6 @@
#include "users.hh"
#include "globals.hh"
#include "fetch-settings.hh"
#include "config-global.hh"
#include "flake-settings.hh"
#include "flake.hh"
#include <nlohmann/json.hpp>
@ -51,7 +51,7 @@ void ConfigFile::apply()
else
assert(false);
if (!whitelist.count(baseName) && !nix::fetchSettings.acceptFlakeConfig) {
if (!whitelist.count(baseName) && !nix::flakeSettings.acceptFlakeConfig) {
bool trusted = false;
auto trustedList = readTrustedList();
auto tlname = get(trustedList, name);

View file

@ -9,6 +9,7 @@
#include "fetchers.hh"
#include "finally.hh"
#include "fetch-settings.hh"
#include "flake-settings.hh"
#include "value-to-json.hh"
#include "local-fs-store.hh"
@ -346,7 +347,7 @@ LockedFlake lockFlake(
FlakeCache flakeCache;
auto useRegistries = lockFlags.useRegistries.value_or(fetchSettings.useRegistries);
auto useRegistries = lockFlags.useRegistries.value_or(flakeSettings.useRegistries);
auto flake = getFlake(state, topRef, useRegistries, flakeCache);
@ -691,7 +692,7 @@ LockedFlake lockFlake(
if (lockFlags.commitLockFile) {
std::string cm;
cm = fetchSettings.commitLockFileSummary.get();
cm = flakeSettings.commitLockFileSummary.get();
if (cm == "") {
cm = fmt("%s: %s", relPath, lockFileExists ? "Update" : "Add");
@ -803,7 +804,7 @@ static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, V
{
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake"));
auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
if (evalSettings.pureEval && !flakeRef.input.isLocked())
if (state.settings.pureEval && !flakeRef.input.isLocked())
throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, state.positions[pos]);
callFlake(state,
@ -811,8 +812,8 @@ static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, V
LockFlags {
.updateLockFile = false,
.writeLockFile = false,
.useRegistries = !evalSettings.pureEval && fetchSettings.useRegistries,
.allowUnlocked = !evalSettings.pureEval,
.useRegistries = !state.settings.pureEval && flakeSettings.useRegistries,
.allowUnlocked = !state.settings.pureEval,
}),
v);
}
@ -949,10 +950,20 @@ std::optional<Fingerprint> LockedFlake::getFingerprint(ref<Store> store) const
auto fingerprint = flake.lockedRef.input.getFingerprint(store);
if (!fingerprint) return std::nullopt;
*fingerprint += fmt(";%s;%s", flake.lockedRef.subdir, lockFile);
/* Include revCount and lastModified because they're not
necessarily implied by the content fingerprint (e.g. for
tarball flakes) but can influence the evaluation result. */
if (auto revCount = flake.lockedRef.input.getRevCount())
*fingerprint += fmt(";revCount=%d", *revCount);
if (auto lastModified = flake.lockedRef.input.getLastModified())
*fingerprint += fmt(";lastModified=%d", *lastModified);
// FIXME: as an optimization, if the flake contains a lock file
// and we haven't changed it, then it's sufficient to use
// flake.sourceInfo.storePath for the fingerprint.
return hashString(HashAlgorithm::SHA256, fmt("%s;%s;%s", *fingerprint, flake.lockedRef.subdir, lockFile));
return hashString(HashAlgorithm::SHA256, *fingerprint);
}
Flake::~Flake() { }

17
src/libflake/local.mk Normal file
View file

@ -0,0 +1,17 @@
libraries += libflake
libflake_NAME = libnixflake
libflake_DIR := $(d)
libflake_SOURCES := $(wildcard $(d)/*.cc $(d)/flake/*.cc)
# Not just for this library itself, but also for downstream libraries using this library
INCLUDE_libflake := -I $(d)
libflake_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libfetchers) $(INCLUDE_libexpr) $(INCLUDE_libflake)
libflake_LDFLAGS += $(THREAD_LDFLAGS)
libflake_LIBS = libutil libstore libfetchers libexpr

75
src/libflake/meson.build Normal file
View file

@ -0,0 +1,75 @@
project('nix-flake', '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-store'),
dependency('nix-fetchers'),
dependency('nix-expr'),
]
subdir('build-utils-meson/subprojects')
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
deps_public += nlohmann_json
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',
language : 'cpp',
)
subdir('build-utils-meson/diagnostics')
sources = files(
'flake-settings.cc',
'flake/config.cc',
'flake/flake.cc',
'flake/flakeref.cc',
'flake/url-name.cc',
'flake/lockfile.cc',
)
include_dirs = [include_directories('.')]
headers = files(
'flake-settings.hh',
'flake/flake.hh',
'flake/flakeref.hh',
'flake/lockfile.hh',
'flake/url-name.hh',
)
this_library = library(
'nixflake',
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')

84
src/libflake/package.nix Normal file
View file

@ -0,0 +1,84 @@
{ lib
, stdenv
, mkMesonDerivation
, releaseTools
, meson
, ninja
, pkg-config
, nix-util
, nix-store
, nix-fetchers
, nix-expr
, nlohmann_json
, libgit2
, man
# Configuration Options
, version
}:
let
inherit (lib) fileset;
in
mkMesonDerivation (finalAttrs: {
pname = "nix-flake";
inherit version;
workDir = ./.;
fileset = fileset.unions [
../../build-utils-meson
./build-utils-meson
../../.version
./.version
./meson.build
(fileset.fileFilter (file: file.hasExt "cc") ./.)
(fileset.fileFilter (file: file.hasExt "hh") ./.)
];
outputs = [ "out" "dev" ];
nativeBuildInputs = [
meson
ninja
pkg-config
];
propagatedBuildInputs = [
nix-store
nix-util
nix-fetchers
nix-expr
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
'';
env = lib.optionalAttrs (stdenv.isLinux && !(stdenv.hostPlatform.isStatic && stdenv.system == "aarch64-linux")) {
LDFLAGS = "-fuse-ld=gold";
};
enableParallelBuilding = true;
separateDebugInfo = !stdenv.hostPlatform.isStatic;
# TODO `releaseTools.coverageAnalysis` in Nixpkgs needs to be updated
# to work with `strictDeps`.
strictDeps = true;
hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie";
meta = {
platforms = lib.platforms.unix ++ lib.platforms.windows;
};
})

1
src/libmain/.version Symbolic link
View file

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

View file

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

View file

@ -1,5 +1,6 @@
#include "common-args.hh"
#include "args/root.hh"
#include "config-global.hh"
#include "globals.hh"
#include "logging.hh"
#include "loggers.hh"

98
src/libmain/meson.build Normal file
View file

@ -0,0 +1,98 @@
project('nix-main', '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'),
]
subdir('build-utils-meson/subprojects')
pubsetbuf_test = '''
#include <iostream>
using namespace std;
char buf[1024];
int main() {
cerr.rdbuf()->pubsetbuf(buf, sizeof(buf));
}
'''
configdata.set(
'HAVE_PUBSETBUF',
cxx.compiles(pubsetbuf_test).to_int(),
description: 'Optionally used for buffering on standard error'
)
config_h = configure_file(
configuration : configdata,
output : 'config-main.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-main.hh',
language : 'cpp',
)
subdir('build-utils-meson/diagnostics')
sources = files(
'common-args.cc',
'loggers.cc',
'progress-bar.cc',
'shared.cc',
)
if host_machine.system() != 'windows'
sources += files(
'unix/stack.cc',
)
endif
include_dirs = [include_directories('.')]
headers = [config_h] + files(
'common-args.hh',
'loggers.hh',
'progress-bar.hh',
'shared.hh',
)
this_library = library(
'nixmain',
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')

79
src/libmain/package.nix Normal file
View file

@ -0,0 +1,79 @@
{ lib
, stdenv
, mkMesonDerivation
, releaseTools
, meson
, ninja
, pkg-config
, openssl
, nix-util
, nix-store
# Configuration Options
, version
}:
let
inherit (lib) fileset;
in
mkMesonDerivation (finalAttrs: {
pname = "nix-main";
inherit version;
workDir = ./.;
fileset = fileset.unions [
../../build-utils-meson
./build-utils-meson
../../.version
./.version
./meson.build
(fileset.fileFilter (file: file.hasExt "cc") ./.)
(fileset.fileFilter (file: file.hasExt "hh") ./.)
];
outputs = [ "out" "dev" ];
nativeBuildInputs = [
meson
ninja
pkg-config
];
propagatedBuildInputs = [
nix-util
nix-store
openssl
];
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
'';
env = lib.optionalAttrs (stdenv.isLinux && !(stdenv.hostPlatform.isStatic && stdenv.system == "aarch64-linux")) {
LDFLAGS = "-fuse-ld=gold";
};
enableParallelBuilding = true;
separateDebugInfo = !stdenv.hostPlatform.isStatic;
# TODO `releaseTools.coverageAnalysis` in Nixpkgs needs to be updated
# to work with `strictDeps`.
strictDeps = true;
hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie";
meta = {
platforms = lib.platforms.unix ++ lib.platforms.windows;
};
})

View file

@ -320,6 +320,10 @@ void showManPage(const std::string & name)
restoreProcessContext();
setEnv("MANPATH", settings.nixManDir.c_str());
execlp("man", "man", name.c_str(), nullptr);
if (errno == ENOENT) {
// Not SysError because we don't want to suffix the errno, aka No such file or directory.
throw Error("The '%1%' command was not found, but it is needed for '%2%' and some other '%3%' commands' help text. Perhaps you could install the '%1%' command?", "man", name.c_str(), "nix-*");
}
throw SysError("command 'man %1%' failed", name.c_str());
}

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

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

View file

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

View file

@ -0,0 +1,83 @@
project('nix-store-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'),
]
deps_public_maybe_subproject = [
dependency('nix-util-c'),
]
subdir('build-utils-meson/subprojects')
# 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-store.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',
# From C libraries, for our public, installed headers too
'-include', 'config-util.h',
'-include', 'config-store.h',
language : 'cpp',
)
subdir('build-utils-meson/diagnostics')
sources = files(
'nix_api_store.cc',
)
include_dirs = [include_directories('.')]
headers = [config_h] + files(
'nix_api_store.h',
)
# TODO don't install this once tests don't use it and/or move the header into `libstore`, non-`c`
headers += files('nix_api_store_internal.h')
subdir('build-utils-meson/export-all-symbols')
this_library = library(
'nixstorec',
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,79 @@
{ lib
, stdenv
, mkMesonDerivation
, releaseTools
, meson
, ninja
, pkg-config
, nix-util-c
, nix-store
# Configuration Options
, version
}:
let
inherit (lib) fileset;
in
mkMesonDerivation (finalAttrs: {
pname = "nix-store-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") ./.)
];
outputs = [ "out" "dev" ];
nativeBuildInputs = [
meson
ninja
pkg-config
];
propagatedBuildInputs = [
nix-util-c
nix-store
];
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";
};
enableParallelBuilding = true;
separateDebugInfo = !stdenv.hostPlatform.isStatic;
strictDeps = true;
hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie";
meta = {
platforms = lib.platforms.unix ++ lib.platforms.windows;
};
})

1
src/libstore/.version Symbolic link
View file

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

View file

@ -322,7 +322,7 @@ StorePath BinaryCacheStore::addToStoreFromDump(
if (static_cast<FileIngestionMethod>(dumpMethod) == hashMethod.getFileIngestionMethod())
caHash = hashString(HashAlgorithm::SHA256, dump2.s);
switch (dumpMethod) {
case FileSerialisationMethod::Recursive:
case FileSerialisationMethod::NixArchive:
// The dump is already NAR in this case, just use it.
nar = dump2.s;
break;
@ -339,7 +339,7 @@ StorePath BinaryCacheStore::addToStoreFromDump(
} else {
// Otherwise, we have to do th same hashing as NAR so our single
// hash will suffice for both purposes.
if (dumpMethod != FileSerialisationMethod::Recursive || hashAlgo != HashAlgorithm::SHA256)
if (dumpMethod != FileSerialisationMethod::NixArchive || hashAlgo != HashAlgorithm::SHA256)
unsupported("addToStoreFromDump");
}
StringSource narDump { nar };

View file

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

View file

@ -3,6 +3,7 @@
# include "hook-instance.hh"
#endif
#include "processes.hh"
#include "config-global.hh"
#include "worker.hh"
#include "builtins.hh"
#include "builtins/buildenv.hh"
@ -25,6 +26,10 @@
#include <fcntl.h>
#include <unistd.h>
#ifndef _WIN32 // TODO abstract over proc exit status
# include <sys/wait.h>
#endif
#include <nlohmann/json.hpp>
namespace nix {
@ -1033,7 +1038,7 @@ void DerivationGoal::buildDone()
BuildResult::Status st = BuildResult::MiscFailure;
#ifndef _WIN32
#ifndef _WIN32 // TODO abstract over proc exit status
if (hook && WIFEXITED(status) && WEXITSTATUS(status) == 101)
st = BuildResult::TimedOut;

View file

@ -19,7 +19,6 @@ Worker::Worker(Store & store, Store & evalStore)
, store(store)
, evalStore(evalStore)
{
/* Debugging: prevent recursive workers. */
nrLocalBuilds = 0;
nrSubstitutions = 0;
lastWokenUp = steady_time_point::min();
@ -530,7 +529,7 @@ bool Worker::pathContentsGood(const StorePath & path)
else {
auto current = hashPath(
{store.getFSAccessor(), CanonPath(store.printStorePath(path))},
FileIngestionMethod::Recursive, info->narHash.algo).first;
FileIngestionMethod::NixArchive, info->narHash.algo).first;
Hash nullHash(HashAlgorithm::SHA256);
res = info->narHash == nullHash || info->narHash == current;
}

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