Merge remote-tracking branch 'nixos/master'

This commit is contained in:
Max Headroom 2024-04-19 16:42:33 +02:00
commit 2d0b0537fe
278 changed files with 4665 additions and 1472 deletions

View file

@ -28,3 +28,5 @@ EmptyLineBeforeAccessModifier: Leave
#PackConstructorInitializers: BinPack
BreakBeforeBinaryOperators: NonAssignment
AlwaysBreakBeforeMultilineStrings: true
IndentPPDirectives: AfterHash
PPIndentWidth: 2

13
.github/labeler.yml vendored
View file

@ -1,3 +1,16 @@
"c api":
- changed-files:
- any-glob-to-any-file: "src/lib*-c/**/*"
- any-glob-to-any-file: "test/unit/**/nix_api_*"
- any-glob-to-any-file: "doc/external-api/**/*"
"contributor-experience":
- changed-files:
- any-glob-to-any-file: "CONTRIBUTING.md"
- any-glob-to-any-file: ".github/ISSUE_TEMPLATE/*"
- any-glob-to-any-file: ".github/PULL_REQUEST_TEMPLATE.md"
- any-glob-to-any-file: "doc/manual/src/contributing/**"
"documentation":
- changed-files:
- any-glob-to-any-file: "doc/manual/*"

2
.gitignore vendored
View file

@ -118,8 +118,6 @@ perl/Makefile.config
/misc/systemd/nix-daemon.conf
/misc/upstart/nix-daemon.conf
/src/resolve-system-dependencies/resolve-system-dependencies
outputs/
*.a

View file

@ -7,6 +7,8 @@ clean-files += $(buildprefix)Makefile.config
# List makefiles
include mk/platform.mk
ifeq ($(ENABLE_BUILD), yes)
makefiles = \
mk/precompiled-headers.mk \
@ -20,8 +22,10 @@ makefiles = \
src/nix/local.mk \
src/libutil-c/local.mk \
src/libstore-c/local.mk \
src/libexpr-c/local.mk \
src/resolve-system-dependencies/local.mk \
src/libexpr-c/local.mk
ifdef HOST_UNIX
makefiles += \
scripts/local.mk \
misc/bash/local.mk \
misc/fish/local.mk \
@ -30,6 +34,7 @@ makefiles = \
misc/launchd/local.mk \
misc/upstart/local.mk
endif
endif
ifeq ($(ENABLE_UNIT_TESTS), yes)
makefiles += \
@ -43,14 +48,17 @@ makefiles += \
endif
ifeq ($(ENABLE_FUNCTIONAL_TESTS), yes)
ifdef HOST_UNIX
makefiles += \
tests/functional/local.mk \
tests/functional/ca/local.mk \
tests/functional/git-hashing/local.mk \
tests/functional/dyn-drv/local.mk \
tests/functional/local-overlay-store/local.mk \
tests/functional/test-libstoreconsumer/local.mk \
tests/functional/plugins/local.mk
endif
endif
# Some makefiles require access to built programs and must be included late.
makefiles-late =
@ -79,8 +87,6 @@ else
unexport NIX_HARDENING_ENABLE
endif
include mk/platform.mk
ifdef HOST_WINDOWS
# Windows DLLs are stricter about symbol visibility than Unix shared
# objects --- see https://gcc.gnu.org/wiki/Visibility for details.

View file

@ -40,24 +40,37 @@ Nix expression `builtins.nixVersion`.
#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.
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);
void my_get_string_cb(const char * start, unsigned int n, char ** user_data)
{
*user_data = strdup(start);
}
nix_expr_eval_from_string(NULL, state, "builtins.nixVersion", ".", value);
nix_value_force(NULL, state, value);
printf("Nix version: %s\n", nix_get_string(NULL, value));
int main()
{
nix_libexpr_init(NULL);
nix_gc_decref(NULL, value);
nix_state_free(state);
nix_store_free(store);
return 0;
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;
}
```

View file

@ -110,6 +110,7 @@
- [Derivation](protocols/json/derivation.md)
- [Serving Tarball Flakes](protocols/tarball-fetcher.md)
- [Store Path Specification](protocols/store-path.md)
- [Nix Archive (NAR) Format](protocols/nix-archive.md)
- [Derivation "ATerm" file format](protocols/derivation-aterm.md)
- [Glossary](glossary.md)
- [Contributing](contributing/index.md)

View file

@ -69,7 +69,7 @@ It can also execute build plans to produce new data, which are made available to
A build plan itself is a series of *build tasks*, together with their build inputs.
> **Important**
> A build task in Nix is called [derivation](../glossary.md#gloss-derivation).
> A build task in Nix is called [derivation](@docroot@/glossary.md#gloss-derivation).
Each build task has a special build input executed as *build instructions* in order to perform the build.
The result of a build task can be input to another build task.

View file

@ -41,7 +41,7 @@ expression to a low-level [store derivation]) and [`nix-store
--realise`](@docroot@/command-ref/nix-store/realise.md) (to build the store
derivation).
[store derivation]: ../glossary.md#gloss-store-derivation
[store derivation]: @docroot@/glossary.md#gloss-store-derivation
> **Warning**
>

View file

@ -49,7 +49,7 @@ authentication, you can avoid typing the passphrase with `ssh-agent`.
- `--include-outputs`\
Also copy the outputs of [store derivation]s included in the closure.
[store derivation]: ../glossary.md#gloss-store-derivation
[store derivation]: @docroot@/glossary.md#gloss-store-derivation
- `--use-substitutes` / `-s`\
Attempt to download missing paths on the target machine using Nixs

View file

@ -23,7 +23,7 @@ It evaluates the Nix expressions in each of *files* (which defaults to
derivation, a list of derivations, or a set of derivations. The paths
of the resulting store derivations are printed on standard output.
[store derivation]: ../glossary.md#gloss-store-derivation
[store derivation]: @docroot@/glossary.md#gloss-store-derivation
If *files* is the character `-`, then a Nix expression will be read from
standard input.

View file

@ -40,12 +40,12 @@ symlink.
derivations *paths*. These are the paths that will be produced when
the derivation is built.
[output paths]: ../../glossary.md#gloss-output-path
[output paths]: @docroot@/glossary.md#gloss-output-path
- `--requisites`; `-R`\
Prints out the [closure] of the store path *paths*.
[closure]: ../../glossary.md#gloss-closure
[closure]: @docroot@/glossary.md#gloss-closure
This query has one option:
@ -66,7 +66,7 @@ symlink.
*paths*, that is, their immediate dependencies. (For *all*
dependencies, use `--requisites`.)
[references]: ../../glossary.md#gloss-reference
[references]: @docroot@/glossary.md#gloss-reference
- `--referrers`\
Prints the set of *referrers* of the store paths *paths*, that is,
@ -90,7 +90,7 @@ symlink.
example when *paths* were substituted from a binary cache.
Use `--valid-derivers` instead to obtain valid paths only.
[deriver]: ../../glossary.md#gloss-deriver
[deriver]: @docroot@/glossary.md#gloss-deriver
- `--valid-derivers`\
Prints a set of derivation files (`.drv`) which are supposed produce

View file

@ -206,3 +206,22 @@ or inside `nix-shell` or `nix develop`:
# make internal-api-html
# xdg-open ./outputs/doc/share/doc/nix/internal-api/html/index.html
```
## C API documentation (experimental)
[C API documentation] is available online.
You can also build and view it yourself:
[C API documentation]: https://hydra.nixos.org/job/nix/master/external-api-docs/latest/download-by-type/doc/external-api-docs
```console
# nix build .#hydraJobs.external-api-docs
# xdg-open ./result/share/doc/nix/external-api/html/index.html
```
or inside `nix-shell` or `nix develop`:
```
# make external-api-html
# xdg-open ./outputs/doc/share/doc/nix/external-api/html/index.html
```

View file

@ -144,6 +144,7 @@ Nix can be built for various platforms, as specified in [`flake.nix`]:
- `aarch64-darwin`
- `armv6l-linux`
- `armv7l-linux`
- `riscv64-linux`
In order to build Nix for a different platform than the one you're currently
on, you need a way for your current Nix installation to build code for that
@ -166,7 +167,10 @@ or for Nix with the [`flakes`] and [`nix-command`] experimental features enabled
$ nix build .#packages.aarch64-linux.default
```
Cross-compiled builds are available for ARMv6 (`armv6l-linux`) and ARMv7 (`armv7l-linux`).
Cross-compiled builds are available for:
- `armv6l-linux`
- `armv7l-linux`
- `riscv64-linux`
Add more [system types](#system-type) to `crossSystems` in `flake.nix` to bootstrap Nix on unsupported platforms.
### Building for multiple platforms at once
@ -196,7 +200,7 @@ In order to facilitate this, Nix has some support for being built out of tree
## System type
Nix uses a string with he following format to identify the *system type* or *platform* it runs on:
Nix uses a string with the following format to identify the *system type* or *platform* it runs on:
```
<cpu>-<os>[-<abi>]

View file

@ -215,6 +215,9 @@
[output path]: #gloss-output-path
- [output closure]{#gloss-output-closure}\
The [closure] of an [output path]. It only contains what is [reachable] from the output.
- [deriver]{#gloss-deriver}
The [store derivation] that produced an [output path].

View file

@ -207,12 +207,17 @@ Derivations can declare some infrequently used optional attributes.
This is the default.
- `"recursive"`\
The hash is computed over the NAR archive dump of the output
- `"recursive"` or `"nar"`\
The hash is computed over the [NAR archive](@docroot@/glossary.md#gloss-nar) dump of the output
(i.e., the result of [`nix-store --dump`](@docroot@/command-ref/nix-store/dump.md)). In
this case, the output can be anything, including a directory
tree.
`"recursive"` is the traditional way of indicating this,
and is supported since 2005 (virtually the entire history of Nix).
`"nar"` is more clear, and consistent with other parts of Nix (such as the CLI),
however support for it is only added in Nix version 2.21.
- [`__contentAddressed`]{#adv-attr-__contentAddressed}
> **Warning**
> This attribute is part of an [experimental feature](@docroot@/contributing/experimental-features.md).
@ -298,7 +303,7 @@ Derivations can declare some infrequently used optional attributes.
[`disallowedReferences`](#adv-attr-disallowedReferences) and [`disallowedRequisites`](#adv-attr-disallowedRequisites),
the following attributes are available:
- `maxSize` defines the maximum size of the resulting [store object](../glossary.md#gloss-store-object).
- `maxSize` defines the maximum size of the resulting [store object](@docroot@/glossary.md#gloss-store-object).
- `maxClosureSize` defines the maximum size of the output's closure.
- `ignoreSelfRefs` controls whether self-references should be considered when
checking for allowed references/requisites.

View file

@ -128,8 +128,8 @@ The result is a string.
> The file or directory at *path* must exist and is copied to the [store].
> The path appears in the result as the corresponding [store path].
[store path]: ../glossary.md#gloss-store-path
[store]: ../glossary.md#gloss-store
[store path]: @docroot@/glossary.md#gloss-store-path
[store]: @docroot@/glossary.md#gloss-store
[String and path concatenation]: #string-and-path-concatenation

View file

@ -20,7 +20,7 @@ Rather than writing
(where `freetype` is a [derivation]), you can instead write
[derivation]: ../glossary.md#gloss-derivation
[derivation]: @docroot@/glossary.md#gloss-derivation
```nix
"--with-freetype2-library=${freetype}/lib"
@ -107,9 +107,9 @@ An expression that is interpolated must evaluate to one of the following:
A string interpolates to itself.
A path in an interpolated expression is first copied into the Nix store, and the resulting string is the [store path] of the newly created [store object](../glossary.md#gloss-store-object).
A path in an interpolated expression is first copied into the Nix store, and the resulting string is the [store path] of the newly created [store object](@docroot@/glossary.md#gloss-store-object).
[store path]: ../glossary.md#gloss-store-path
[store path]: @docroot@/glossary.md#gloss-store-path
> **Example**
>

View file

@ -113,7 +113,7 @@
For example, assume you used a file path in an interpolated string during a `nix repl` session.
Later in the same session, after having changed the file contents, evaluating the interpolated string with the file path again might not return a new [store path], since Nix might not re-read the file contents.
[store path]: ../glossary.md#gloss-store-path
[store path]: @docroot@/glossary.md#gloss-store-path
Paths can include [string interpolation] and can themselves be [interpolated in other expressions].

View file

@ -83,7 +83,7 @@ This information is not intrinsic to the store object, but about how it is store
## Computed closure fields
These fields are not stored at all, but computed by traverising the other other fields across all the store objects in a [closure].
These fields are not stored at all, but computed by traversing the other fields across all the store objects in a [closure].
* `closureSize`:

View file

@ -0,0 +1,42 @@
# Nix Archive (NAR) format
This is the complete specification of the Nix Archive format.
The Nix Archive format closely follows the abstract specification of a [file system object] tree,
because it is designed to serialize exactly that data structure.
[file system object]: @docroot@/store/file-system-object.md
The format of this specification is close to [Extended BackusNaur form](https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form), with the exception of the `str(..)` function / parameterized rule, which length-prefixes and pads strings.
This makes the resulting binary format easier to parse.
Regular users do *not* need to know this information.
But for those interested in exactly how Nix works, e.g. if they are reimplementing it, this information can be useful.
```ebnf
nar = str("nix-archive-1"), nar-obj;
nar-obj = str("("), nar-obj-inner, str(")");
nar-obj-inner
= str("type"), str("regular") regular
| str("type"), str("symlink") symlink
| str("type"), str("directory") directory
;
regular = [ str("executable"), str("") ], str("contents"), str(contents);
symlink = str("target"), str(target);
(* side condition: directory entries must be ordered by their names *)
directory = str("type"), str("directory") { directory-entry };
directory-entry = str("entry"), str("("), str("name"), str(name), str("node"), nar-obj, str(")");
```
The `str` function / parameterized rule is defined as follows:
- `str(s)` = `int(|s|), pad(s);`
- `int(n)` = the 64-bit little endian representation of the number `n`
- `pad(s)` = the byte sequence `s`, padded with 0s to a multiple of 8 byte

View file

@ -11,7 +11,7 @@
As the choice of hash formats is no longer binary, the `--base16` flag is also added
to explicitly specify the Base16 format, which is still the default.
* The special handling of an [installable](../command-ref/new-cli/nix.md#installables) with `.drv` suffix being interpreted as all of the given [store derivation](../glossary.md#gloss-store-derivation)'s output paths is removed, and instead taken as the literal store path that it represents.
* The special handling of an [installable](../command-ref/new-cli/nix.md#installables) with `.drv` suffix being interpreted as all of the given [store derivation](@docroot@/glossary.md#gloss-store-derivation)'s output paths is removed, and instead taken as the literal store path that it represents.
The new `^` syntax for store paths introduced in Nix 2.13 allows explicitly referencing output paths of a derivation.
Using this is better and more clear than relying on the now-removed `.drv` special handling.

View file

@ -200,3 +200,9 @@
while performing various operations (including `nix develop`, `nix flake
update`, and so on). With several fixes to Nix's signal handlers, Nix
commands will now exit quickly after Ctrl-C is pressed.
- `nix copy` to a `ssh-ng` store now needs `--substitute-on-destination` (a.k.a. `-s`)
in order to substitute paths on the remote store instead of copying them.
The behavior is consistent with `nix copy` to a different kind of remote store.
Previously this behavior was controlled by the
`builders-use-substitutes` setting and `--substitute-on-destination` was ignored.

View file

@ -46,7 +46,7 @@ But if the store has a file system representation, the store directory contains
[file system objects]: ./file-system-object.md
This means a store path is not just derived from the referenced store object itself, but depends on the store the store object is in.
This means a store path is not just derived from the referenced store object itself, but depends on the store that the store object is in.
> **Note**
>

View file

@ -36,13 +36,8 @@
crossSystems = [
"armv6l-unknown-linux-gnueabihf"
"armv7l-unknown-linux-gnueabihf"
"riscv64-unknown-linux-gnu"
"x86_64-unknown-netbsd"
];
# Nix doesn't yet build on this platform, so we put it in a
# separate list. We just use this for `devShells` and
# `nixpkgsFor`, which this depends on.
shellCrossSystems = crossSystems ++ [
"x86_64-w64-mingw32"
];
@ -88,7 +83,7 @@
in {
inherit stdenvs native;
static = native.pkgsStatic;
cross = lib.genAttrs shellCrossSystems (crossSystem: make-pkgs crossSystem "stdenv");
cross = forAllCrossSystems (crossSystem: make-pkgs crossSystem "stdenv");
});
installScriptFor = tarballs:
@ -264,6 +259,7 @@
# Cross
self.hydraJobs.binaryTarballCross."x86_64-linux"."armv6l-unknown-linux-gnueabihf"
self.hydraJobs.binaryTarballCross."x86_64-linux"."armv7l-unknown-linux-gnueabihf"
self.hydraJobs.binaryTarballCross."x86_64-linux"."riscv64-unknown-linux-gnu"
];
installerScriptForGHA = installScriptFor [
# Native
@ -272,6 +268,7 @@
# Cross
self.hydraJobs.binaryTarballCross."x86_64-linux"."armv6l-unknown-linux-gnueabihf"
self.hydraJobs.binaryTarballCross."x86_64-linux"."armv7l-unknown-linux-gnueabihf"
self.hydraJobs.binaryTarballCross."x86_64-linux"."riscv64-unknown-linux-gnu"
];
# docker image with Nix inside
@ -431,8 +428,8 @@
in
(makeShells "native" nixpkgsFor.${system}.native) //
(lib.optionalAttrs (!nixpkgsFor.${system}.native.stdenv.isDarwin)
(makeShells "static" nixpkgsFor.${system}.static)) //
(lib.genAttrs shellCrossSystems (crossSystem: let pkgs = nixpkgsFor.${system}.cross.${crossSystem}; in makeShell pkgs pkgs.stdenv)) //
(makeShells "static" nixpkgsFor.${system}.static) //
(forAllCrossSystems (crossSystem: let pkgs = nixpkgsFor.${system}.cross.${crossSystem}; in makeShell pkgs pkgs.stdenv))) //
{
default = self.devShells.${system}.native-stdenvPackages;
}

View file

@ -2,7 +2,7 @@ GLOBAL_CXXFLAGS += -Wno-deprecated-declarations -Werror=switch
# Allow switch-enum to be overridden for files that do not support it, usually because of dependency headers.
ERROR_SWITCH_ENUM = -Werror=switch-enum
$(foreach i, config.h $(wildcard src/lib*/*.hh) $(wildcard src/lib*/*.h $(filter-out %_internal.h, $(wildcard src/lib*c/*.h))), \
$(foreach i, config.h $(wildcard src/lib*/*.hh) $(filter-out %_internal.h, $(wildcard src/lib*c/*.h)), \
$(eval $(call install-file-in, $(i), $(includedir)/nix, 0644)))
ifdef HOST_UNIX

View file

@ -46,11 +46,13 @@ AC_DEFUN([ENSURE_NO_GCC_BUG_80431],
]])],
[status_80431=0],
[status_80431=$?],
[
# Assume we're bug-free when cross-compiling
])
[status_80431=''])
AC_LANG_POP(C++)
AS_CASE([$status_80431],
[''],[
AC_MSG_RESULT(cannot check because cross compiling)
AC_MSG_NOTICE(assume we are bug free)
],
[0],[
AC_MSG_RESULT(yes)
],

View file

@ -171,6 +171,10 @@ eval {
downloadFile("binaryTarballCross.x86_64-linux.armv7l-unknown-linux-gnueabihf", "1");
};
warn "$@" if $@;
eval {
downloadFile("binaryTarballCross.x86_64-linux.riscv64-unknown-linux-gnu", "1");
};
warn "$@" if $@;
downloadFile("installerScript", "1");
# Upload docker images to dockerhub.

View file

@ -94,8 +94,8 @@
# Whether to build the internal/external API docs, can be done separately from
# everything else.
, enableInternalAPIDocs ? false
, enableExternalAPIDocs ? false
, enableInternalAPIDocs ? forDevShell
, enableExternalAPIDocs ? forDevShell
# Whether to install unit tests. This is useful when cross compiling
# since we cannot run them natively during the build, but can do so

View file

@ -42,19 +42,22 @@
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <netdb.h>
#include <pwd.h>
#include <signal.h>
#include <sys/resource.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>
#ifndef _WIN32
# include <grp.h>
# include <netdb.h>
# include <pwd.h>
# include <sys/resource.h>
# include <sys/select.h>
# include <sys/socket.h>
# include <sys/utsname.h>
# include <sys/wait.h>
# include <termios.h>
#endif
#include <nlohmann/json.hpp>

View file

@ -69,4 +69,4 @@ else
fi
export PATH="$NIX_LINK/bin:@localstatedir@/nix/profiles/default/bin:$PATH"
unset NIX_LINK
unset NIX_LINK NIX_LINK_NEW

View file

@ -22,6 +22,7 @@
#include "experimental-features.hh"
using namespace nix;
using namespace nix::unix;
using std::cin;
static void handleAlarm(int sig) {

View file

@ -148,7 +148,7 @@ MixOperateOnOptions::MixOperateOnOptions()
{
addFlag({
.longName = "derivation",
.description = "Operate on the [store derivation](../../glossary.md#gloss-store-derivation) rather than its outputs.",
.description = "Operate on the [store derivation](@docroot@/glossary.md#gloss-store-derivation) rather than its outputs.",
.category = installablesCategory,
.handler = {&operateOn, OperateOn::Derivation},
});

View file

@ -181,7 +181,7 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
v->mkString(arg.s);
},
[&](const AutoArgFile & arg) {
v->mkString(readFile(arg.path));
v->mkString(readFile(arg.path.string()));
},
[&](const AutoArgStdin & arg) {
v->mkString(readFile(STDIN_FILENO));

View file

@ -49,7 +49,7 @@ Value * InstallableFlake::getFlakeOutputs(EvalState & state, const flake::Locked
callFlake(state, lockedFlake, *vFlake);
auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs"));
auto aOutputs = vFlake->attrs()->get(state.symbols.create("outputs"));
assert(aOutputs);
state.forceValue(*aOutputs->value, aOutputs->value->determinePos(noPos));

View file

@ -354,7 +354,7 @@ void SourceExprCommand::completeInstallable(AddCompletions & completions, std::s
state->autoCallFunction(*autoArgs, v1, v2);
if (v2.type() == nAttrs) {
for (auto & i : *v2.attrs) {
for (auto & i : *v2.attrs()) {
std::string name = state->symbols[i.name];
if (name.find(searchWord) == 0) {
if (prefix_ == "")
@ -538,7 +538,7 @@ ref<eval_cache::EvalCache> openEvalCache(
state.forceAttrs(*vFlake, noPos, "while parsing cached flake data");
auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs"));
auto aOutputs = vFlake->attrs()->get(state.symbols.create("outputs"));
assert(aOutputs);
return aOutputs->value;

View file

@ -3,9 +3,9 @@
#include "finally.hh"
#include "terminal.hh"
#include <sys/queue.h>
#if HAVE_LOWDOWN
#include <lowdown.h>
# include <sys/queue.h>
# include <lowdown.h>
#endif
namespace nix {

View file

@ -137,6 +137,7 @@ static constexpr const char * promptForType(ReplPromptType promptType)
bool ReadlineLikeInteracter::getLine(std::string & input, ReplPromptType promptType)
{
#ifndef _WIN32 // TODO use more signals.hh for this
struct sigaction act, old;
sigset_t savedSignalMask, set;
@ -161,9 +162,12 @@ bool ReadlineLikeInteracter::getLine(std::string & input, ReplPromptType promptT
};
setupSignals();
#endif
char * s = readline(promptForType(promptType));
Finally doFree([&]() { free(s); });
#ifndef _WIN32 // TODO use more signals.hh for this
restoreSignals();
#endif
if (g_signal_received) {
g_signal_received = 0;

View file

@ -290,7 +290,7 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
e->eval(*state, *env, v);
state->forceAttrs(v, noPos, "while evaluating an attrset for the purpose of completion (this error should not be displayed; file an issue?)");
for (auto & i : *v.attrs) {
for (auto & i : *v.attrs()) {
std::string_view name = state->symbols[i.name];
if (name.substr(0, cur2.size()) != cur2) continue;
completions.insert(concatStrings(prev, expr, ".", name));
@ -490,7 +490,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
auto path = state->coerceToPath(noPos, v, context, "while evaluating the filename to edit");
return {path, 0};
} else if (v.isLambda()) {
auto pos = state->positions[v.lambda.fun->pos];
auto pos = state->positions[v.payload.lambda.fun->pos];
if (auto path = std::get_if<SourcePath>(&pos.origin))
return {*path, pos.line};
else
@ -742,17 +742,17 @@ void NixRepl::loadFiles()
void NixRepl::addAttrsToScope(Value & attrs)
{
state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); }, "while evaluating an attribute set to be merged in the global scope");
if (displ + attrs.attrs->size() >= envSize)
if (displ + attrs.attrs()->size() >= envSize)
throw Error("environment full; cannot add more variables");
for (auto & i : *attrs.attrs) {
for (auto & i : *attrs.attrs()) {
staticEnv->vars.emplace_back(i.name, displ);
env->values[displ++] = i.value;
varNames.emplace(state->symbols[i.name]);
}
staticEnv->sort();
staticEnv->deduplicate();
notice("Added %1% variables.", attrs.attrs->size());
notice("Added %1% variables.", attrs.attrs()->size());
}

View file

@ -93,6 +93,8 @@ nix_err nix_expr_eval_from_string(
* @param[in] arg The argument to pass to the function.
* @param[out] value The result of the function call.
* @return NIX_OK if the function call was successful, an error code otherwise.
* @see nix_init_apply() for a similar function that does not performs the call immediately, but stores it as a thunk.
* Note the different argument order.
*/
nix_err nix_value_call(nix_c_context * context, EvalState * state, Value * fn, Value * arg, Value * value);

View file

@ -35,4 +35,10 @@ struct nix_string_context
nix::NixStringContext & ctx;
};
struct nix_realised_string
{
std::string str;
std::vector<StorePath> storePaths;
};
#endif // NIX_API_EXPR_INTERNAL_H

View file

@ -2,6 +2,7 @@
#include "config.hh"
#include "eval.hh"
#include "globals.hh"
#include "path.hh"
#include "primops.hh"
#include "value.hh"
@ -9,12 +10,14 @@
#include "nix_api_expr_internal.h"
#include "nix_api_util.h"
#include "nix_api_util_internal.h"
#include "nix_api_store_internal.h"
#include "nix_api_value.h"
#include "value/context.hh"
#ifdef HAVE_BOEHMGC
# include "gc/gc.h"
# define GC_INCLUDE_NEW 1
# include "gc_cpp.h"
# include "gc/gc.h"
# define GC_INCLUDE_NEW 1
# include "gc_cpp.h"
#endif
// Helper function to throw an exception if value is null
@ -158,21 +161,21 @@ bool nix_get_bool(nix_c_context * context, const Value * value)
try {
auto & v = check_value_not_null(value);
assert(v.type() == nix::nBool);
return v.boolean;
return v.boolean();
}
NIXC_CATCH_ERRS_RES(false);
}
const char * nix_get_string(nix_c_context * context, const Value * value)
nix_err nix_get_string(nix_c_context * context, const Value * value, nix_get_string_callback callback, void * user_data)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
assert(v.type() == nix::nString);
return v.c_str();
call_nix_get_string_callback(v.c_str(), callback, user_data);
}
NIXC_CATCH_ERRS_NULL
NIXC_CATCH_ERRS
}
const char * nix_get_path_string(nix_c_context * context, const Value * value)
@ -189,7 +192,7 @@ const char * nix_get_path_string(nix_c_context * context, const Value * value)
// We could use v.path().to_string().c_str(), but I'm concerned this
// crashes. Looks like .path() allocates a CanonPath with a copy of the
// string, then it gets the underlying data from that.
return v._path.path;
return v.payload.path.path;
}
NIXC_CATCH_ERRS_NULL
}
@ -213,7 +216,7 @@ unsigned int nix_get_attrs_size(nix_c_context * context, const Value * value)
try {
auto & v = check_value_not_null(value);
assert(v.type() == nix::nAttrs);
return v.attrs->size();
return v.attrs()->size();
}
NIXC_CATCH_ERRS_RES(0);
}
@ -225,7 +228,7 @@ double nix_get_float(nix_c_context * context, const Value * value)
try {
auto & v = check_value_not_null(value);
assert(v.type() == nix::nFloat);
return v.fpoint;
return v.fpoint();
}
NIXC_CATCH_ERRS_RES(0.0);
}
@ -237,7 +240,7 @@ int64_t nix_get_int(nix_c_context * context, const Value * value)
try {
auto & v = check_value_not_null(value);
assert(v.type() == nix::nInt);
return v.integer;
return v.integer();
}
NIXC_CATCH_ERRS_RES(0);
}
@ -249,7 +252,7 @@ ExternalValue * nix_get_external(nix_c_context * context, Value * value)
try {
auto & v = check_value_not_null(value);
assert(v.type() == nix::nExternal);
return (ExternalValue *) v.external;
return (ExternalValue *) v.external();
}
NIXC_CATCH_ERRS_NULL;
}
@ -278,7 +281,7 @@ Value * nix_get_attr_byname(nix_c_context * context, const Value * value, EvalSt
auto & v = check_value_not_null(value);
assert(v.type() == nix::nAttrs);
nix::Symbol s = state->state.symbols.create(name);
auto attr = v.attrs->get(s);
auto attr = v.attrs()->get(s);
if (attr) {
nix_gc_incref(nullptr, attr->value);
state->state.forceValue(*attr->value, nix::noPos);
@ -298,7 +301,7 @@ bool nix_has_attr_byname(nix_c_context * context, const Value * value, EvalState
auto & v = check_value_not_null(value);
assert(v.type() == nix::nAttrs);
nix::Symbol s = state->state.symbols.create(name);
auto attr = v.attrs->get(s);
auto attr = v.attrs()->get(s);
if (attr)
return true;
return false;
@ -313,7 +316,7 @@ nix_get_attr_byidx(nix_c_context * context, const Value * value, EvalState * sta
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
const nix::Attr & a = (*v.attrs)[i];
const nix::Attr & a = (*v.attrs())[i];
*name = ((const std::string &) (state->state.symbols[a.name])).c_str();
nix_gc_incref(nullptr, a.value);
state->state.forceValue(*a.value, nix::noPos);
@ -328,7 +331,7 @@ const char * nix_get_attr_name_byidx(nix_c_context * context, const Value * valu
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
const nix::Attr & a = (*v.attrs)[i];
const nix::Attr & a = (*v.attrs())[i];
return ((const std::string &) (state->state.symbols[a.name])).c_str();
}
NIXC_CATCH_ERRS_NULL
@ -401,6 +404,19 @@ 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)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
auto & f = check_value_not_null(fn);
auto & a = check_value_not_null(arg);
v.mkApp(&f, &a);
}
NIXC_CATCH_ERRS
}
nix_err nix_init_external(nix_c_context * context, Value * value, ExternalValue * val)
{
if (context)
@ -528,3 +544,52 @@ void nix_bindings_builder_free(BindingsBuilder * bb)
delete (nix::BindingsBuilder *) bb;
#endif
}
nix_realised_string * nix_string_realise(nix_c_context * context, EvalState * state, Value * value, bool isIFD)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
nix::NixStringContext stringContext;
auto rawStr = state->state.coerceToString(nix::noPos, v, stringContext, "while realising a string").toOwned();
nix::StorePathSet storePaths;
auto rewrites = state->state.realiseContext(stringContext, &storePaths);
auto s = nix::rewriteStrings(rawStr, rewrites);
// Convert to the C API StorePath type and convert to vector for index-based access
std::vector<StorePath> vec;
for (auto & sp : storePaths) {
vec.push_back(StorePath{sp});
}
return new nix_realised_string{.str = s, .storePaths = vec};
}
NIXC_CATCH_ERRS_NULL
}
void nix_realised_string_free(nix_realised_string * s)
{
delete s;
}
size_t nix_realised_string_get_buffer_size(nix_realised_string * s)
{
return s->str.size();
}
const char * nix_realised_string_get_buffer_start(nix_realised_string * s)
{
return s->str.data();
}
size_t nix_realised_string_get_store_path_count(nix_realised_string * s)
{
return s->storePaths.size();
}
const StorePath * nix_realised_string_get_store_path(nix_realised_string * s, size_t i)
{
return &s->storePaths[i];
}

View file

@ -9,6 +9,7 @@
*/
#include "nix_api_util.h"
#include "nix_api_store.h"
#include "stdbool.h"
#include "stddef.h"
#include "stdint.h"
@ -69,6 +70,10 @@ typedef struct PrimOp PrimOp;
*/
typedef struct ExternalValue ExternalValue;
/** @brief String without placeholders, and realised store paths
*/
typedef struct nix_realised_string nix_realised_string;
/** @defgroup primops
* @brief Create your own primops
* @{
@ -167,13 +172,19 @@ const char * nix_get_typename(nix_c_context * context, const Value * value);
*/
bool nix_get_bool(nix_c_context * context, const Value * value);
/** @brief Get string
/** @brief Get the raw string
*
* This may contain placeholders.
*
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @param[in] callback Called with the string value.
* @param[in] user_data optional, arbitrary data, passed to the callback when it's called.
* @return string
* @return NULL in case of error.
* @return error code, NIX_OK on success.
*/
const char * nix_get_string(nix_c_context * context, const Value * value);
nix_err
nix_get_string(nix_c_context * context, const Value * value, nix_get_string_callback callback, void * user_data);
/** @brief Get path as string
* @param[out] context Optional, stores error information
@ -331,8 +342,24 @@ nix_err nix_init_int(nix_c_context * context, Value * value, int64_t i);
* @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);
/** @brief Set the value to a thunk that will perform a function application when needed.
*
* Thunks may be put into attribute sets and lists to perform some computation lazily; on demand.
* However, note that in some places, a thunk must not be returned, such as in the return value of a PrimOp.
* In such cases, you may use nix_value_call() instead (but note the different argument order).
*
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @param[in] fn function to call
* @param[in] arg argument to pass
* @return error code, NIX_OK on successful initialization.
* @see nix_value_call() for a similar function that performs the call immediately and only stores the return value.
* Note the different argument order.
*/
nix_err nix_init_apply(nix_c_context * context, Value * value, Value * fn, Value * arg);
/** @brief Set an external value
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
@ -410,7 +437,7 @@ BindingsBuilder * nix_make_bindings_builder(nix_c_context * context, EvalState *
/** @brief Insert bindings into a builder
* @param[out] context Optional, stores error information
* @param[in] builder BindingsBuilder to insert into
* @param[in] name attribute name, copied into the symbol store
* @param[in] name attribute name, only used for the duration of the call.
* @param[in] value value to give the binding
* @return error code, NIX_OK on success.
*/
@ -425,6 +452,55 @@ nix_bindings_builder_insert(nix_c_context * context, BindingsBuilder * builder,
void nix_bindings_builder_free(BindingsBuilder * builder);
/**@}*/
/** @brief Realise a string context.
*
* This will
* - realise the store paths referenced by the string's context, and
* - perform the replacement of placeholders.
* - create temporary garbage collection roots for the store paths, for
* the lifetime of the current process.
* - log to stderr
*
* @param[out] context Optional, stores error information
* @param[in] value Nix value, which must be a string
* @param[in] state Nix evaluator state
* @param[in] isIFD If true, disallow derivation outputs if setting `allow-import-from-derivation` is false.
You should set this to true when this call is part of a primop.
You should set this to false when building for your application's purpose.
* @return NULL if failed, are a new nix_realised_string, which must be freed with nix_realised_string_free
*/
nix_realised_string * nix_string_realise(nix_c_context * context, EvalState * state, Value * value, bool isIFD);
/** @brief Start of the string
* @param[in] realised_string
* @return pointer to the start of the string. It may not be null-terminated.
*/
const char * nix_realised_string_get_buffer_start(nix_realised_string * realised_string);
/** @brief Length of the string
* @param[in] realised_string
* @return length of the string in bytes
*/
size_t nix_realised_string_get_buffer_size(nix_realised_string * realised_string);
/** @brief Number of realised store paths
* @param[in] realised_string
* @return number of realised store paths that were referenced by the string via its context
*/
size_t nix_realised_string_get_store_path_count(nix_realised_string * realised_string);
/** @brief Get a store path. The store paths are stored in an arbitrary order.
* @param[in] realised_string
* @param[in] index index of the store path, must be less than the count
* @return store path
*/
const StorePath * nix_realised_string_get_store_path(nix_realised_string * realised_string, size_t index);
/** @brief Free a realised string
* @param[in] realised_string
*/
void nix_realised_string_free(nix_realised_string * realised_string);
// cffi end
#ifdef __cplusplus
}

View file

@ -72,10 +72,10 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
if (attr.empty())
throw Error("empty attribute name in selection path '%1%'", attrPath);
Bindings::iterator a = v->attrs->find(state.symbols.create(attr));
if (a == v->attrs->end()) {
auto a = v->attrs()->get(state.symbols.create(attr));
if (!a) {
std::set<std::string> attrNames;
for (auto & attr : *v->attrs)
for (auto & attr : *v->attrs())
attrNames.insert(state.symbols[attr.name]);
auto suggestions = Suggestions::bestMatches(attrNames, attr);

View file

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

View file

@ -65,24 +65,26 @@ public:
typedef Attr * iterator;
typedef const Attr * const_iterator;
void push_back(const Attr & attr)
{
assert(size_ < capacity_);
attrs[size_++] = attr;
}
iterator find(Symbol name)
const_iterator find(Symbol name) const
{
Attr key(name, 0);
iterator i = std::lower_bound(begin(), end(), key);
const_iterator i = std::lower_bound(begin(), end(), key);
if (i != end() && i->name == name) return i;
return end();
}
Attr * get(Symbol name)
const Attr * get(Symbol name) const
{
Attr key(name, 0);
iterator i = std::lower_bound(begin(), end(), key);
const_iterator i = std::lower_bound(begin(), end(), key);
if (i != end() && i->name == name) return &*i;
return nullptr;
}
@ -90,14 +92,22 @@ public:
iterator begin() { return &attrs[0]; }
iterator end() { return &attrs[size_]; }
const_iterator begin() const { return &attrs[0]; }
const_iterator end() const { return &attrs[size_]; }
Attr & operator[](size_t pos)
{
return attrs[pos];
}
const Attr & operator[](size_t pos) const
{
return attrs[pos];
}
void sort();
size_t capacity() { return capacity_; }
size_t capacity() const { return capacity_; }
/**
* Returns the attributes in lexicographically sorted order.
@ -166,6 +176,20 @@ public:
{
return bindings;
}
size_t capacity()
{
return bindings->capacity();
}
void grow(Bindings * newBindings)
{
for (auto & i : *bindings)
newBindings->push_back(i);
bindings = newBindings;
}
friend struct ExprAttrs;
};
}

View file

@ -387,7 +387,7 @@ Value & AttrCursor::getValue()
if (parent) {
auto & vParent = parent->first->getValue();
root->state.forceAttrs(vParent, noPos, "while searching for an attribute");
auto attr = vParent.attrs->get(parent->second);
auto attr = vParent.attrs()->get(parent->second);
if (!attr)
throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr());
_value = allocRootValue(attr->value);
@ -448,9 +448,9 @@ Value & AttrCursor::forceValue()
cachedValue = {root->db->setString(getKey(), path.abs()), string_t{path.abs(), {}}};
}
else if (v.type() == nBool)
cachedValue = {root->db->setBool(getKey(), v.boolean), v.boolean};
cachedValue = {root->db->setBool(getKey(), v.boolean()), v.boolean()};
else if (v.type() == nInt)
cachedValue = {root->db->setInt(getKey(), v.integer), int_t{v.integer}};
cachedValue = {root->db->setInt(getKey(), v.integer()), int_t{v.integer()}};
else if (v.type() == nAttrs)
; // FIXME: do something?
else
@ -510,7 +510,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
return nullptr;
//error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
auto attr = v.attrs->get(name);
auto attr = v.attrs()->get(name);
if (!attr) {
if (root->db) {
@ -652,7 +652,7 @@ bool AttrCursor::getBool()
if (v.type() != nBool)
root->state.error<TypeError>("'%s' is not a Boolean", getAttrPathStr()).debugThrow();
return v.boolean;
return v.boolean();
}
NixInt AttrCursor::getInt()
@ -674,7 +674,7 @@ NixInt AttrCursor::getInt()
if (v.type() != nInt)
root->state.error<TypeError>("'%s' is not an integer", getAttrPathStr()).debugThrow();
return v.integer;
return v.integer();
}
std::vector<std::string> AttrCursor::getListOfStrings()
@ -730,7 +730,7 @@ std::vector<Symbol> AttrCursor::getAttrs()
root->state.error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
std::vector<Symbol> attrs;
for (auto & attr : *getValue().attrs)
for (auto & attr : *getValue().attrs())
attrs.push_back(attr.name);
std::sort(attrs.begin(), attrs.end(), [&](Symbol a, Symbol b) {
std::string_view sa = root->state.symbols[a], sb = root->state.symbols[b];

View file

@ -85,8 +85,8 @@ Env & EvalState::allocEnv(size_t size)
void EvalState::forceValue(Value & v, const PosIdx pos)
{
if (v.isThunk()) {
Env * env = v.thunk.env;
Expr * expr = v.thunk.expr;
Env * env = v.payload.thunk.env;
Expr * expr = v.payload.thunk.expr;
try {
v.mkBlackhole();
//checkInterrupt();
@ -98,7 +98,7 @@ void EvalState::forceValue(Value & v, const PosIdx pos)
}
}
else if (v.isApp())
callFunction(*v.app.left, *v.app.right, v, pos);
callFunction(*v.payload.app.left, *v.payload.app.right, v, pos);
}

View file

@ -33,15 +33,17 @@
#include <optional>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <fstream>
#include <functional>
#include <iostream>
#include <sys/resource.h>
#include <nlohmann/json.hpp>
#include <boost/container/small_vector.hpp>
#ifndef _WIN32 // TODO use portable implementation
# include <sys/resource.h>
#endif
#if HAVE_BOEHMGC
#define GC_INCLUDE_NEW
@ -131,7 +133,7 @@ void Value::print(EvalState & state, std::ostream & str, PrintOptions options)
const Value * getPrimOp(const Value &v) {
const Value * primOp = &v;
while (primOp->isPrimOpApp()) {
primOp = primOp->primOpApp.left;
primOp = primOp->payload.primOpApp.left;
}
assert(primOp->isPrimOp());
return primOp;
@ -163,12 +165,12 @@ std::string showType(const Value & v)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
switch (v.internalType) {
case tString: return v.string.context ? "a string with context" : "a string";
case tString: return v.payload.string.context ? "a string with context" : "a string";
case tPrimOp:
return fmt("the built-in function '%s'", std::string(v.primOp->name));
return fmt("the built-in function '%s'", std::string(v.payload.primOp->name));
case tPrimOpApp:
return fmt("the partially applied built-in function '%s'", std::string(getPrimOp(v)->primOp->name));
case tExternal: return v.external->showType();
return fmt("the partially applied built-in function '%s'", std::string(getPrimOp(v)->payload.primOp->name));
case tExternal: return v.external()->showType();
case tThunk: return v.isBlackhole() ? "a black hole" : "a thunk";
case tApp: return "a function application";
default:
@ -183,9 +185,9 @@ PosIdx Value::determinePos(const PosIdx pos) const
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
switch (internalType) {
case tAttrs: return attrs->pos;
case tLambda: return lambda.fun->pos;
case tApp: return app.left->determinePos(pos);
case tAttrs: return attrs()->pos;
case tLambda: return payload.lambda.fun->pos;
case tApp: return payload.app.left->determinePos(pos);
default: return pos;
}
#pragma GCC diagnostic pop
@ -197,10 +199,10 @@ bool Value::isTrivial() const
internalType != tApp
&& internalType != tPrimOpApp
&& (internalType != tThunk
|| (dynamic_cast<ExprAttrs *>(thunk.expr)
&& ((ExprAttrs *) thunk.expr)->dynamicAttrs.empty())
|| dynamic_cast<ExprLambda *>(thunk.expr)
|| dynamic_cast<ExprList *>(thunk.expr));
|| (dynamic_cast<ExprAttrs *>(payload.thunk.expr)
&& ((ExprAttrs *) payload.thunk.expr)->dynamicAttrs.empty())
|| dynamic_cast<ExprLambda *>(payload.thunk.expr)
|| dynamic_cast<ExprList *>(payload.thunk.expr));
}
@ -584,7 +586,7 @@ void EvalState::addConstant(const std::string & name, Value * v, Constant info)
/* Install value the base environment. */
staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
baseEnv.values[0]->attrs->push_back(Attr(symbols.create(name2), v));
baseEnv.values[0]->payload.attrs->push_back(Attr(symbols.create(name2), v));
}
}
@ -597,32 +599,30 @@ void PrimOp::check()
}
std::ostream & operator<<(std::ostream & output, PrimOp & primOp)
std::ostream & operator<<(std::ostream & output, const PrimOp & primOp)
{
output << "primop " << primOp.name;
return output;
}
PrimOp * Value::primOpAppPrimOp() const
const PrimOp * Value::primOpAppPrimOp() const
{
Value * left = primOpApp.left;
Value * left = payload.primOpApp.left;
while (left && !left->isPrimOp()) {
left = left->primOpApp.left;
left = left->payload.primOpApp.left;
}
if (!left)
return nullptr;
return left->primOp;
return left->primOp();
}
void Value::mkPrimOp(PrimOp * p)
{
p->check();
clearValue();
internalType = tPrimOp;
primOp = p;
finishValue(tPrimOp, { .primOp = p });
}
@ -650,14 +650,14 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
v->mkPrimOp(new PrimOp(primOp));
staticBaseEnv->vars.emplace_back(envName, baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
baseEnv.values[0]->attrs->push_back(Attr(symbols.create(primOp.name), v));
baseEnv.values[0]->payload.attrs->push_back(Attr(symbols.create(primOp.name), v));
return v;
}
Value & EvalState::getBuiltin(const std::string & name)
{
return *baseEnv.values[0]->attrs->find(symbols.create(name))->value;
return *baseEnv.values[0]->attrs()->find(symbols.create(name))->value;
}
@ -665,12 +665,12 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
{
if (v.isPrimOp()) {
auto v2 = &v;
if (auto * doc = v2->primOp->doc)
if (auto * doc = v2->primOp()->doc)
return Doc {
.pos = {},
.name = v2->primOp->name,
.arity = v2->primOp->arity,
.args = v2->primOp->args,
.name = v2->primOp()->name,
.arity = v2->primOp()->arity,
.args = v2->primOp()->args,
.doc = doc,
};
}
@ -694,8 +694,8 @@ void printWithBindings(const SymbolTable & st, const Env & env)
if (!env.values[0]->isThunk()) {
std::cout << "with: ";
std::cout << ANSI_MAGENTA;
Bindings::iterator j = env.values[0]->attrs->begin();
while (j != env.values[0]->attrs->end()) {
auto j = env.values[0]->attrs()->begin();
while (j != env.values[0]->attrs()->end()) {
std::cout << st[j->name] << " ";
++j;
}
@ -749,11 +749,8 @@ void mapStaticEnvBindings(const SymbolTable & st, const StaticEnv & se, const En
if (se.isWith && !env.values[0]->isThunk()) {
// add 'with' bindings.
Bindings::iterator j = env.values[0]->attrs->begin();
while (j != env.values[0]->attrs->end()) {
vm[st[j->name]] = j->value;
++j;
}
for (auto & j : *env.values[0]->attrs())
vm[st[j.name]] = j.value;
} else {
// iterate through staticenv bindings and add them.
for (auto & i : se.vars)
@ -873,28 +870,28 @@ void Value::mkString(std::string_view s)
}
static void copyContextToValue(Value & v, const NixStringContext & context)
static const char * * encodeContext(const NixStringContext & context)
{
if (!context.empty()) {
size_t n = 0;
v.string.context = (const char * *)
auto ctx = (const char * *)
allocBytes((context.size() + 1) * sizeof(char *));
for (auto & i : context)
v.string.context[n++] = dupString(i.to_string().c_str());
v.string.context[n] = 0;
}
ctx[n++] = dupString(i.to_string().c_str());
ctx[n] = 0;
return ctx;
} else
return nullptr;
}
void Value::mkString(std::string_view s, const NixStringContext & context)
{
mkString(s);
copyContextToValue(*this, context);
mkString(makeImmutableString(s), encodeContext(context));
}
void Value::mkStringMove(const char * s, const NixStringContext & context)
{
mkString(s);
copyContextToValue(*this, context);
mkString(s, encodeContext(context));
}
void Value::mkPath(const SourcePath & path)
@ -917,8 +914,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
auto * fromWith = var.fromWith;
while (1) {
forceAttrs(*env->values[0], fromWith->pos, "while evaluating the first subexpression of a with expression");
Bindings::iterator j = env->values[0]->attrs->find(var.name);
if (j != env->values[0]->attrs->end()) {
if (auto j = env->values[0]->attrs()->get(var.name)) {
if (countCalls) attrSelects[j->pos]++;
return j->value;
}
@ -1166,7 +1162,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos, std::stri
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).withFrame(env, *e).debugThrow();
return v.boolean;
return v.boolean();
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
@ -1234,8 +1230,9 @@ Env * ExprAttrs::buildInheritFromEnv(EvalState & state, Env & up)
void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
{
v.mkAttrs(state.buildBindings(attrs.size() + dynamicAttrs.size()).finish());
auto bindings = state.buildBindings(attrs.size() + dynamicAttrs.size());
auto dynamicEnv = &env;
bool sort = false;
if (recursive) {
/* Create a new environment that contains the attributes in
@ -1260,7 +1257,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
} else
vAttr = i.second.e->maybeThunk(state, *i.second.chooseByKind(&env2, &env, inheritEnv));
env2.values[displ++] = vAttr;
v.attrs->push_back(Attr(i.first, vAttr, i.second.pos));
bindings.insert(i.first, vAttr, i.second.pos);
}
/* If the rec contains an attribute called `__overrides', then
@ -1272,32 +1269,28 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
been substituted into the bodies of the other attributes.
Hence we need __overrides.) */
if (hasOverrides) {
Value * vOverrides = (*v.attrs)[overrides->second.displ].value;
Value * vOverrides = (*bindings.bindings)[overrides->second.displ].value;
state.forceAttrs(*vOverrides, [&]() { return vOverrides->determinePos(noPos); }, "while evaluating the `__overrides` attribute");
Bindings * newBnds = state.allocBindings(v.attrs->capacity() + vOverrides->attrs->size());
for (auto & i : *v.attrs)
newBnds->push_back(i);
for (auto & i : *vOverrides->attrs) {
bindings.grow(state.allocBindings(bindings.capacity() + vOverrides->attrs()->size()));
for (auto & i : *vOverrides->attrs()) {
AttrDefs::iterator j = attrs.find(i.name);
if (j != attrs.end()) {
(*newBnds)[j->second.displ] = i;
(*bindings.bindings)[j->second.displ] = i;
env2.values[j->second.displ] = i.value;
} else
newBnds->push_back(i);
bindings.push_back(i);
}
newBnds->sort();
v.attrs = newBnds;
sort = true;
}
}
else {
Env * inheritEnv = inheritFromExprs ? buildInheritFromEnv(state, env) : nullptr;
for (auto & i : attrs) {
v.attrs->push_back(Attr(
i.first,
i.second.e->maybeThunk(state, *i.second.chooseByKind(&env, &env, inheritEnv)),
i.second.pos));
}
for (auto & i : attrs)
bindings.insert(
i.first,
i.second.e->maybeThunk(state, *i.second.chooseByKind(&env, &env, inheritEnv)),
i.second.pos);
}
/* Dynamic attrs apply *after* rec and __overrides. */
@ -1309,17 +1302,21 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
continue;
state.forceStringNoCtx(nameVal, i.pos, "while evaluating the name of a dynamic attribute");
auto nameSym = state.symbols.create(nameVal.string_view());
Bindings::iterator j = v.attrs->find(nameSym);
if (j != v.attrs->end())
if (sort)
// FIXME: inefficient
bindings.bindings->sort();
if (auto j = bindings.bindings->get(nameSym))
state.error<EvalError>("dynamic attribute '%1%' already defined at %2%", state.symbols[nameSym], state.positions[j->pos]).atPos(i.pos).withFrame(env, *this).debugThrow();
i.valueExpr->setName(nameSym);
/* Keep sorted order so find can catch duplicates */
v.attrs->push_back(Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), i.pos));
v.attrs->sort(); // FIXME: inefficient
bindings.insert(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), i.pos);
sort = true;
}
v.attrs->pos = pos;
bindings.bindings->pos = pos;
v.mkAttrs(sort ? bindings.finish() : bindings.alreadySorted());
}
@ -1425,21 +1422,21 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
for (auto & i : attrPath) {
state.nrLookups++;
Bindings::iterator j;
const Attr * j;
auto name = getName(i, state, env);
if (def) {
state.forceValue(*vAttrs, pos);
if (vAttrs->type() != nAttrs ||
(j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
!(j = vAttrs->attrs()->get(name)))
{
def->eval(state, env, v);
return;
}
} else {
state.forceAttrs(*vAttrs, pos, "while selecting an attribute");
if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) {
if (!(j = vAttrs->attrs()->get(name))) {
std::set<std::string> allAttrNames;
for (auto & attr : *vAttrs->attrs)
for (auto & attr : *vAttrs->attrs())
allAttrNames.insert(state.symbols[attr.name]);
auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]);
state.error<EvalError>("attribute '%1%' missing", state.symbols[name])
@ -1477,15 +1474,15 @@ void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v)
for (auto & i : attrPath) {
state.forceValue(*vAttrs, getPos());
Bindings::iterator j;
const Attr * j;
auto name = getName(i, state, env);
if (vAttrs->type() != nAttrs ||
(j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
if (vAttrs->type() == nAttrs &&
(j = vAttrs->attrs()->get(name)))
{
vAttrs = j->value;
} else {
v.mkBool(false);
return;
} else {
vAttrs = j->value;
}
}
@ -1537,19 +1534,19 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
}
};
Attr * functor;
const Attr * functor;
while (nrArgs > 0) {
if (vCur.isLambda()) {
ExprLambda & lambda(*vCur.lambda.fun);
ExprLambda & lambda(*vCur.payload.lambda.fun);
auto size =
(!lambda.arg ? 0 : 1) +
(lambda.hasFormals() ? lambda.formals->formals.size() : 0);
Env & env2(allocEnv(size));
env2.up = vCur.lambda.env;
env2.up = vCur.payload.lambda.env;
Displacement displ = 0;
@ -1571,7 +1568,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
argument has a default, use the default. */
size_t attrsUsed = 0;
for (auto & i : lambda.formals->formals) {
auto j = args[0]->attrs->get(i.name);
auto j = args[0]->attrs()->get(i.name);
if (!j) {
if (!i.def) {
error<TypeError>("function '%1%' called without required argument '%2%'",
@ -1579,7 +1576,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
symbols[i.name])
.atPos(lambda.pos)
.withTrace(pos, "from call site")
.withFrame(*fun.lambda.env, lambda)
.withFrame(*fun.payload.lambda.env, lambda)
.debugThrow();
}
env2.values[displ++] = i.def->maybeThunk(*this, env2);
@ -1591,10 +1588,10 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
/* Check that each actual argument is listed as a formal
argument (unless the attribute match specifies a `...'). */
if (!lambda.formals->ellipsis && attrsUsed != args[0]->attrs->size()) {
if (!lambda.formals->ellipsis && attrsUsed != args[0]->attrs()->size()) {
/* Nope, so show the first unexpected argument to the
user. */
for (auto & i : *args[0]->attrs)
for (auto & i : *args[0]->attrs())
if (!lambda.formals->has(i.name)) {
std::set<std::string> formalNames;
for (auto & formal : lambda.formals->formals)
@ -1606,7 +1603,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
.atPos(lambda.pos)
.withTrace(pos, "from call site")
.withSuggestions(suggestions)
.withFrame(*fun.lambda.env, lambda)
.withFrame(*fun.payload.lambda.env, lambda)
.debugThrow();
}
abort(); // can't happen
@ -1648,7 +1645,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
else if (vCur.isPrimOp()) {
size_t argsLeft = vCur.primOp->arity;
size_t argsLeft = vCur.primOp()->arity;
if (nrArgs < argsLeft) {
/* We don't have enough arguments, so create a tPrimOpApp chain. */
@ -1656,7 +1653,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
return;
} else {
/* We have all the arguments, so call the primop. */
auto * fn = vCur.primOp;
auto * fn = vCur.primOp();
nrPrimOpCalls++;
if (countCalls) primOpCalls[fn->name]++;
@ -1680,10 +1677,10 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
Value * primOp = &vCur;
while (primOp->isPrimOpApp()) {
argsDone++;
primOp = primOp->primOpApp.left;
primOp = primOp->payload.primOpApp.left;
}
assert(primOp->isPrimOp());
auto arity = primOp->primOp->arity;
auto arity = primOp->primOp()->arity;
auto argsLeft = arity - argsDone;
if (nrArgs < argsLeft) {
@ -1696,13 +1693,13 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
Value * vArgs[maxPrimOpArity];
auto n = argsDone;
for (Value * arg = &vCur; arg->isPrimOpApp(); arg = arg->primOpApp.left)
vArgs[--n] = arg->primOpApp.right;
for (Value * arg = &vCur; arg->isPrimOpApp(); arg = arg->payload.primOpApp.left)
vArgs[--n] = arg->payload.primOpApp.right;
for (size_t i = 0; i < argsLeft; ++i)
vArgs[argsDone + i] = args[i];
auto fn = primOp->primOp;
auto fn = primOp->primOp();
nrPrimOpCalls++;
if (countCalls) primOpCalls[fn->name]++;
@ -1723,7 +1720,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
}
}
else if (vCur.type() == nAttrs && (functor = vCur.attrs->get(sFunctor))) {
else if (vCur.type() == nAttrs && (functor = vCur.attrs()->get(sFunctor))) {
/* 'vCur' may be allocated on the stack of the calling
function, but for functors we may keep a reference, so
heap-allocate a copy and use that instead. */
@ -1798,8 +1795,8 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
forceValue(fun, pos);
if (fun.type() == nAttrs) {
auto found = fun.attrs->find(sFunctor);
if (found != fun.attrs->end()) {
auto found = fun.attrs()->find(sFunctor);
if (found != fun.attrs()->end()) {
Value * v = allocValue();
callFunction(*found->value, fun, *v, pos);
forceValue(*v, pos);
@ -1807,14 +1804,14 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
}
}
if (!fun.isLambda() || !fun.lambda.fun->hasFormals()) {
if (!fun.isLambda() || !fun.payload.lambda.fun->hasFormals()) {
res = fun;
return;
}
auto attrs = buildBindings(std::max(static_cast<uint32_t>(fun.lambda.fun->formals->formals.size()), args.size()));
auto attrs = buildBindings(std::max(static_cast<uint32_t>(fun.payload.lambda.fun->formals->formals.size()), args.size()));
if (fun.lambda.fun->formals->ellipsis) {
if (fun.payload.lambda.fun->formals->ellipsis) {
// If the formals have an ellipsis (eg the function accepts extra args) pass
// all available automatic arguments (which includes arguments specified on
// the command line via --arg/--argstr)
@ -1822,9 +1819,9 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
attrs.insert(v);
} else {
// Otherwise, only pass the arguments that the function accepts
for (auto & i : fun.lambda.fun->formals->formals) {
Bindings::iterator j = args.find(i.name);
if (j != args.end()) {
for (auto & i : fun.payload.lambda.fun->formals->formals) {
auto j = args.get(i.name);
if (j) {
attrs.insert(*j);
} else if (!i.def) {
error<MissingArgumentError>(R"(cannot evaluate a function that has an argument without a value ('%1%')
@ -1832,7 +1829,7 @@ Nix attempted to evaluate a function as a top level expression; in
this case it must have its arguments supplied either by default
values, or passed explicitly with '--arg' or '--argstr'. See
https://nixos.org/manual/nix/stable/language/constructs.html#functions.)", symbols[i.name])
.atPos(i.pos).withFrame(*fun.lambda.env, *fun.lambda.fun).debugThrow();
.atPos(i.pos).withFrame(*fun.payload.lambda.env, *fun.payload.lambda.fun).debugThrow();
}
}
}
@ -1917,17 +1914,17 @@ void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v)
state.nrOpUpdates++;
if (v1.attrs->size() == 0) { v = v2; return; }
if (v2.attrs->size() == 0) { v = v1; return; }
if (v1.attrs()->size() == 0) { v = v2; return; }
if (v2.attrs()->size() == 0) { v = v1; return; }
auto attrs = state.buildBindings(v1.attrs->size() + v2.attrs->size());
auto attrs = state.buildBindings(v1.attrs()->size() + v2.attrs()->size());
/* Merge the sets, preferring values from the second set. Make
sure to keep the resulting vector in sorted order. */
Bindings::iterator i = v1.attrs->begin();
Bindings::iterator j = v2.attrs->begin();
auto i = v1.attrs()->begin();
auto j = v2.attrs()->begin();
while (i != v1.attrs->end() && j != v2.attrs->end()) {
while (i != v1.attrs()->end() && j != v2.attrs()->end()) {
if (i->name == j->name) {
attrs.insert(*j);
++i; ++j;
@ -1938,12 +1935,12 @@ void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v)
attrs.insert(*j++);
}
while (i != v1.attrs->end()) attrs.insert(*i++);
while (j != v2.attrs->end()) attrs.insert(*j++);
while (i != v1.attrs()->end()) attrs.insert(*i++);
while (j != v2.attrs()->end()) attrs.insert(*j++);
v.mkAttrs(attrs.alreadySorted());
state.nrOpUpdateValuesCopied += v.attrs->size();
state.nrOpUpdateValuesCopied += v.attrs()->size();
}
@ -2035,19 +2032,19 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
if (firstType == nInt) {
if (vTmp.type() == nInt) {
n += vTmp.integer;
n += vTmp.integer();
} else if (vTmp.type() == nFloat) {
// Upgrade the type from int to float;
firstType = nFloat;
nf = n;
nf += vTmp.fpoint;
nf += vTmp.fpoint();
} else
state.error<EvalError>("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow();
} else if (firstType == nFloat) {
if (vTmp.type() == nInt) {
nf += vTmp.integer;
nf += vTmp.integer();
} else if (vTmp.type() == nFloat) {
nf += vTmp.fpoint;
nf += vTmp.fpoint();
} else
state.error<EvalError>("cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow();
} else {
@ -2120,11 +2117,11 @@ void EvalState::forceValueDeep(Value & v)
forceValue(v, v.determinePos(noPos));
if (v.type() == nAttrs) {
for (auto & i : *v.attrs)
for (auto & i : *v.attrs())
try {
// If the value is a thunk, we're evaling. Otherwise no trace necessary.
auto dts = debugRepl && i.value->isThunk()
? makeDebugTraceStacker(*this, *i.value->thunk.expr, *i.value->thunk.env, positions[i.pos],
? makeDebugTraceStacker(*this, *i.value->payload.thunk.expr, *i.value->payload.thunk.env, positions[i.pos],
"while evaluating the attribute '%1%'", symbols[i.name])
: nullptr;
@ -2155,13 +2152,13 @@ NixInt EvalState::forceInt(Value & v, const PosIdx pos, std::string_view errorCt
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).debugThrow();
return v.integer;
return v.integer();
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
return v.integer;
return v.integer();
}
@ -2170,14 +2167,14 @@ NixFloat EvalState::forceFloat(Value & v, const PosIdx pos, std::string_view err
try {
forceValue(v, pos);
if (v.type() == nInt)
return v.integer;
return v.integer();
else if (v.type() != nFloat)
error<TypeError>(
"expected a float but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).debugThrow();
return v.fpoint;
return v.fpoint();
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
@ -2195,19 +2192,19 @@ bool EvalState::forceBool(Value & v, const PosIdx pos, std::string_view errorCtx
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).debugThrow();
return v.boolean;
return v.boolean();
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
return v.boolean;
return v.boolean();
}
bool EvalState::isFunctor(Value & fun)
{
return fun.type() == nAttrs && fun.attrs->find(sFunctor) != fun.attrs->end();
return fun.type() == nAttrs && fun.attrs()->find(sFunctor) != fun.attrs()->end();
}
@ -2248,8 +2245,8 @@ std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string
void copyContext(const Value & v, NixStringContext & context)
{
if (v.string.context)
for (const char * * p = v.string.context; *p; ++p)
if (v.payload.string.context)
for (const char * * p = v.payload.string.context; *p; ++p)
context.insert(NixStringContextElem::parse(*p));
}
@ -2275,8 +2272,8 @@ std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos, std::s
bool EvalState::isDerivation(Value & v)
{
if (v.type() != nAttrs) return false;
Bindings::iterator i = v.attrs->find(sType);
if (i == v.attrs->end()) return false;
auto i = v.attrs()->get(sType);
if (!i) return false;
forceValue(*i->value, i->pos);
if (i->value->type() != nString) return false;
return i->value->string_view().compare("derivation") == 0;
@ -2286,8 +2283,8 @@ bool EvalState::isDerivation(Value & v)
std::optional<std::string> EvalState::tryAttrsToString(const PosIdx pos, Value & v,
NixStringContext & context, bool coerceMore, bool copyToStore)
{
auto i = v.attrs->find(sToString);
if (i != v.attrs->end()) {
auto i = v.attrs()->find(sToString);
if (i != v.attrs()->end()) {
Value v1;
callFunction(*i->value, v, v1, pos);
return coerceToString(pos, v1, context,
@ -2319,7 +2316,7 @@ BackedStringView EvalState::coerceToString(
!canonicalizePath && !copyToStore
? // FIXME: hack to preserve path literals that end in a
// slash, as in /foo/${x}.
v._path.path
v.payload.path.path
: copyToStore
? store->printStorePath(copyPathToStore(context, v.path()))
: std::string(v.path().path.abs());
@ -2329,8 +2326,8 @@ BackedStringView EvalState::coerceToString(
auto maybeString = tryAttrsToString(pos, v, context, coerceMore, copyToStore);
if (maybeString)
return std::move(*maybeString);
auto i = v.attrs->find(sOutPath);
if (i == v.attrs->end()) {
auto i = v.attrs()->find(sOutPath);
if (i == v.attrs()->end()) {
error<TypeError>(
"cannot coerce %1% to a string: %2%",
showType(v),
@ -2345,7 +2342,7 @@ BackedStringView EvalState::coerceToString(
if (v.type() == nExternal) {
try {
return v.external->coerceToString(*this, pos, context, coerceMore, copyToStore);
return v.external()->coerceToString(*this, pos, context, coerceMore, copyToStore);
} catch (Error & e) {
e.addTrace(nullptr, errorCtx);
throw;
@ -2355,10 +2352,10 @@ BackedStringView EvalState::coerceToString(
if (coerceMore) {
/* Note that `false' is represented as an empty string for
shell scripting convenience, just like `null'. */
if (v.type() == nBool && v.boolean) return "1";
if (v.type() == nBool && !v.boolean) return "";
if (v.type() == nInt) return std::to_string(v.integer);
if (v.type() == nFloat) return std::to_string(v.fpoint);
if (v.type() == nBool && v.boolean()) return "1";
if (v.type() == nBool && !v.boolean()) return "";
if (v.type() == nInt) return std::to_string(v.integer());
if (v.type() == nFloat) return std::to_string(v.fpoint());
if (v.type() == nNull) return "";
if (v.isList()) {
@ -2437,8 +2434,8 @@ SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext
/* Similarly, handle __toString where the result may be a path
value. */
if (v.type() == nAttrs) {
auto i = v.attrs->find(sToString);
if (i != v.attrs->end()) {
auto i = v.attrs()->find(sToString);
if (i != v.attrs()->end()) {
Value v1;
callFunction(*i->value, v, v1, pos);
return coerceToPath(pos, v1, context, errorCtx);
@ -2532,19 +2529,19 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
// Special case type-compatibility between float and int
if (v1.type() == nInt && v2.type() == nFloat)
return v1.integer == v2.fpoint;
return v1.integer() == v2.fpoint();
if (v1.type() == nFloat && v2.type() == nInt)
return v1.fpoint == v2.integer;
return v1.fpoint() == v2.integer();
// All other types are not compatible with each other.
if (v1.type() != v2.type()) return false;
switch (v1.type()) {
case nInt:
return v1.integer == v2.integer;
return v1.integer() == v2.integer();
case nBool:
return v1.boolean == v2.boolean;
return v1.boolean() == v2.boolean();
case nString:
return strcmp(v1.c_str(), v2.c_str()) == 0;
@ -2552,8 +2549,8 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
case nPath:
return
// FIXME: compare accessors by their fingerprint.
v1._path.accessor == v2._path.accessor
&& strcmp(v1._path.path, v2._path.path) == 0;
v1.payload.path.accessor == v2.payload.path.accessor
&& strcmp(v1.payload.path.path, v2.payload.path.path) == 0;
case nNull:
return true;
@ -2568,17 +2565,17 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
/* If both sets denote a derivation (type = "derivation"),
then compare their outPaths. */
if (isDerivation(v1) && isDerivation(v2)) {
Bindings::iterator i = v1.attrs->find(sOutPath);
Bindings::iterator j = v2.attrs->find(sOutPath);
if (i != v1.attrs->end() && j != v2.attrs->end())
auto i = v1.attrs()->get(sOutPath);
auto j = v2.attrs()->get(sOutPath);
if (i && j)
return eqValues(*i->value, *j->value, pos, errorCtx);
}
if (v1.attrs->size() != v2.attrs->size()) return false;
if (v1.attrs()->size() != v2.attrs()->size()) return false;
/* Otherwise, compare the attributes one by one. */
Bindings::iterator i, j;
for (i = v1.attrs->begin(), j = v2.attrs->begin(); i != v1.attrs->end(); ++i, ++j)
Bindings::const_iterator i, j;
for (i = v1.attrs()->begin(), j = v2.attrs()->begin(); i != v1.attrs()->end(); ++i, ++j)
if (i->name != j->name || !eqValues(*i->value, *j->value, pos, errorCtx))
return false;
@ -2590,10 +2587,10 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
return false;
case nExternal:
return *v1.external == *v2.external;
return *v1.external() == *v2.external();
case nFloat:
return v1.fpoint == v2.fpoint;
return v1.fpoint() == v2.fpoint();
case nThunk: // Must not be left by forceValue
default:
@ -2632,9 +2629,11 @@ void EvalState::maybePrintStats()
void EvalState::printStatistics()
{
#ifndef _WIN32 // TODO use portable implementation
struct rusage buf;
getrusage(RUSAGE_SELF, &buf);
float cpuTime = buf.ru_utime.tv_sec + ((float) buf.ru_utime.tv_usec / 1000000);
#endif
uint64_t bEnvs = nrEnvs * sizeof(Env) + nrValuesInEnvs * sizeof(Value *);
uint64_t bLists = nrListElems * sizeof(Value *);
@ -2651,7 +2650,9 @@ void EvalState::printStatistics()
if (outPath != "-")
fs.open(outPath, std::fstream::out);
json topObj = json::object();
#ifndef _WIN32 // TODO implement
topObj["cpuTime"] = cpuTime;
#endif
topObj["envs"] = {
{"number", nrEnvs},
{"elements", nrValuesInEnvs},

View file

@ -94,7 +94,7 @@ struct PrimOp
void check();
};
std::ostream & operator<<(std::ostream & output, PrimOp & primOp);
std::ostream & operator<<(std::ostream & output, const PrimOp & primOp);
/**
* Info about a constant
@ -161,6 +161,8 @@ struct DebugTrace {
bool isError;
};
// Don't want Windows function
#undef SearchPath
class EvalState : public std::enable_shared_from_this<EvalState>
{
@ -643,9 +645,6 @@ public:
inline Value * allocValue();
inline Env & allocEnv(size_t size);
Value * allocAttr(Value & vAttrs, Symbol name);
Value * allocAttr(Value & vAttrs, std::string_view name);
Bindings * allocBindings(size_t capacity);
BindingsBuilder buildBindings(size_t capacity)
@ -733,10 +732,12 @@ public:
bool fullGC();
/**
* Realise the given context, and return a mapping from the placeholders
* used to construct the associated value to their final store path
* Realise the given context
* @param[in] context the context to realise
* @param[out] maybePaths if not nullptr, all built or referenced store paths will be added to this set
* @return a mapping from the placeholders used to construct the associated value to their final store path.
*/
[[nodiscard]] StringMap realiseContext(const NixStringContext & context);
[[nodiscard]] StringMap realiseContext(const NixStringContext & context, StorePathSet * maybePaths = nullptr, bool isIFD = true);
/* Call the binary path filter predicate used builtins.path etc. */
bool callPathFilter(

View file

@ -111,7 +111,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
fetchers::Attrs attrs;
std::optional<std::string> url;
for (nix::Attr attr : *(value->attrs)) {
for (auto & attr : *value->attrs()) {
try {
if (attr.name == sUrl) {
expectType(state, nString, *attr.value, attr.pos);
@ -119,7 +119,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
attrs.emplace("url", *url);
} else if (attr.name == sFlake) {
expectType(state, nBool, *attr.value, attr.pos);
input.isFlake = attr.value->boolean;
input.isFlake = attr.value->boolean();
} else if (attr.name == sInputs) {
input.overrides = parseFlakeInputs(state, attr.value, attr.pos, baseDir, lockRootPath);
} else if (attr.name == sFollows) {
@ -136,10 +136,10 @@ static FlakeInput parseFlakeInput(EvalState & state,
attrs.emplace(state.symbols[attr.name], attr.value->c_str());
break;
case nBool:
attrs.emplace(state.symbols[attr.name], Explicit<bool> { attr.value->boolean });
attrs.emplace(state.symbols[attr.name], Explicit<bool> { attr.value->boolean() });
break;
case nInt:
attrs.emplace(state.symbols[attr.name], (long unsigned int) attr.value->integer);
attrs.emplace(state.symbols[attr.name], (long unsigned int) attr.value->integer());
break;
default:
if (attr.name == state.symbols.create("publicKeys")) {
@ -189,7 +189,7 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs(
expectType(state, nAttrs, *value, pos);
for (nix::Attr & inputAttr : *(*value).attrs) {
for (auto & inputAttr : *value->attrs()) {
inputs.emplace(state.symbols[inputAttr.name],
parseFlakeInput(state,
state.symbols[inputAttr.name],
@ -223,23 +223,23 @@ static Flake readFlake(
.path = flakePath,
};
if (auto description = vInfo.attrs->get(state.sDescription)) {
if (auto description = vInfo.attrs()->get(state.sDescription)) {
expectType(state, nString, *description->value, description->pos);
flake.description = description->value->c_str();
}
auto sInputs = state.symbols.create("inputs");
if (auto inputs = vInfo.attrs->get(sInputs))
if (auto inputs = vInfo.attrs()->get(sInputs))
flake.inputs = parseFlakeInputs(state, inputs->value, inputs->pos, flakePath.parent().path.abs(), lockRootPath); // FIXME
auto sOutputs = state.symbols.create("outputs");
if (auto outputs = vInfo.attrs->get(sOutputs)) {
if (auto outputs = vInfo.attrs()->get(sOutputs)) {
expectType(state, nFunction, *outputs->value, outputs->pos);
if (outputs->value->isLambda() && outputs->value->lambda.fun->hasFormals()) {
for (auto & formal : outputs->value->lambda.fun->formals->formals) {
if (outputs->value->isLambda() && outputs->value->payload.lambda.fun->hasFormals()) {
for (auto & formal : outputs->value->payload.lambda.fun->formals->formals) {
if (formal.name != state.sSelf)
flake.inputs.emplace(state.symbols[formal.name], FlakeInput {
.ref = parseFlakeRef(state.symbols[formal.name])
@ -252,10 +252,10 @@ static Flake readFlake(
auto sNixConfig = state.symbols.create("nixConfig");
if (auto nixConfig = vInfo.attrs->get(sNixConfig)) {
if (auto nixConfig = vInfo.attrs()->get(sNixConfig)) {
expectType(state, nAttrs, *nixConfig->value, nixConfig->pos);
for (auto & setting : *nixConfig->value->attrs) {
for (auto & setting : *nixConfig->value->attrs()) {
forceTrivialValue(state, *setting.value, setting.pos);
if (setting.value->type() == nString)
flake.config.settings.emplace(
@ -291,7 +291,7 @@ static Flake readFlake(
}
}
for (auto & attr : *vInfo.attrs) {
for (auto & attr : *vInfo.attrs()) {
if (attr.name != state.sDescription &&
attr.name != sInputs &&
attr.name != sOutputs &&
@ -867,10 +867,13 @@ static RegisterPrimOp r3({
Parse a flake reference, and return its exploded form.
For example:
```nix
builtins.parseFlakeRef "github:NixOS/nixpkgs/23.05?dir=lib"
```
evaluates to:
```nix
{ dir = "lib"; owner = "NixOS"; ref = "23.05"; repo = "nixpkgs"; type = "github"; }
```
@ -889,17 +892,17 @@ static void prim_flakeRefToString(
state.forceAttrs(*args[0], noPos,
"while evaluating the argument passed to builtins.flakeRefToString");
fetchers::Attrs attrs;
for (const auto & attr : *args[0]->attrs) {
for (const auto & attr : *args[0]->attrs()) {
auto t = attr.value->type();
if (t == nInt) {
attrs.emplace(state.symbols[attr.name],
(uint64_t) attr.value->integer);
(uint64_t) attr.value->integer());
} else if (t == nBool) {
attrs.emplace(state.symbols[attr.name],
Explicit<bool> { attr.value->boolean });
Explicit<bool> { attr.value->boolean() });
} else if (t == nString) {
attrs.emplace(state.symbols[attr.name],
std::string(attr.value->string_view()));
std::string(attr.value->string_view()));
} else {
state.error<EvalError>(
"flake reference attribute sets may only contain integers, Booleans, "
@ -919,12 +922,15 @@ static RegisterPrimOp r4({
Convert a flake reference from attribute set format to URL format.
For example:
```nix
builtins.flakeRefToString {
dir = "lib"; owner = "NixOS"; ref = "23.05"; repo = "nixpkgs"; type = "github";
}
```
evaluates to
```nix
"github:NixOS/nixpkgs/23.05?dir=lib"
```

View file

@ -11,7 +11,7 @@
namespace nix {
PackageInfo::PackageInfo(EvalState & state, std::string attrPath, Bindings * attrs)
PackageInfo::PackageInfo(EvalState & state, std::string attrPath, const Bindings * attrs)
: state(&state), attrs(attrs), attrPath(std::move(attrPath))
{
}
@ -69,12 +69,11 @@ std::string PackageInfo::querySystem() const
std::optional<StorePath> PackageInfo::queryDrvPath() const
{
if (!drvPath && attrs) {
Bindings::iterator i = attrs->find(state->sDrvPath);
NixStringContext context;
if (i == attrs->end())
drvPath = {std::nullopt};
else
if (auto i = attrs->get(state->sDrvPath))
drvPath = {state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the 'drvPath' attribute of a derivation")};
else
drvPath = {std::nullopt};
}
return drvPath.value_or(std::nullopt);
}
@ -91,7 +90,7 @@ StorePath PackageInfo::requireDrvPath() const
StorePath PackageInfo::queryOutPath() const
{
if (!outPath && attrs) {
Bindings::iterator i = attrs->find(state->sOutPath);
auto i = attrs->find(state->sOutPath);
NixStringContext context;
if (i != attrs->end())
outPath = state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the output path of a derivation");
@ -106,8 +105,8 @@ PackageInfo::Outputs PackageInfo::queryOutputs(bool withPaths, bool onlyOutputsT
{
if (outputs.empty()) {
/* Get the outputs list. */
Bindings::iterator i;
if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) {
const Attr * i;
if (attrs && (i = attrs->get(state->sOutputs))) {
state->forceList(*i->value, i->pos, "while evaluating the 'outputs' attribute of a derivation");
/* For each output... */
@ -116,13 +115,13 @@ PackageInfo::Outputs PackageInfo::queryOutputs(bool withPaths, bool onlyOutputsT
if (withPaths) {
/* Evaluate the corresponding set. */
Bindings::iterator out = attrs->find(state->symbols.create(output));
if (out == attrs->end()) continue; // FIXME: throw error?
auto out = attrs->get(state->symbols.create(output));
if (!out) continue; // FIXME: throw error?
state->forceAttrs(*out->value, i->pos, "while evaluating an output of a derivation");
/* And evaluate its outPath attribute. */
Bindings::iterator outPath = out->value->attrs->find(state->sOutPath);
if (outPath == out->value->attrs->end()) continue; // FIXME: throw error?
auto outPath = out->value->attrs()->get(state->sOutPath);
if (!outPath) continue; // FIXME: throw error?
NixStringContext context;
outputs.emplace(output, state->coerceToStorePath(outPath->pos, *outPath->value, context, "while evaluating an output path of a derivation"));
} else
@ -135,8 +134,8 @@ PackageInfo::Outputs PackageInfo::queryOutputs(bool withPaths, bool onlyOutputsT
if (!onlyOutputsToInstall || !attrs)
return outputs;
Bindings::iterator i;
if (attrs && (i = attrs->find(state->sOutputSpecified)) != attrs->end() && state->forceBool(*i->value, i->pos, "while evaluating the 'outputSpecified' attribute of a derivation")) {
const Attr * i;
if (attrs && (i = attrs->get(state->sOutputSpecified)) && state->forceBool(*i->value, i->pos, "while evaluating the 'outputSpecified' attribute of a derivation")) {
Outputs result;
auto out = outputs.find(queryOutputName());
if (out == outputs.end())
@ -167,21 +166,21 @@ PackageInfo::Outputs PackageInfo::queryOutputs(bool withPaths, bool onlyOutputsT
std::string PackageInfo::queryOutputName() const
{
if (outputName == "" && attrs) {
Bindings::iterator i = attrs->find(state->sOutputName);
outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value, noPos, "while evaluating the output name of a derivation") : "";
auto i = attrs->get(state->sOutputName);
outputName = i ? state->forceStringNoCtx(*i->value, noPos, "while evaluating the output name of a derivation") : "";
}
return outputName;
}
Bindings * PackageInfo::getMeta()
const Bindings * PackageInfo::getMeta()
{
if (meta) return meta;
if (!attrs) return 0;
Bindings::iterator a = attrs->find(state->sMeta);
if (a == attrs->end()) return 0;
auto a = attrs->get(state->sMeta);
if (!a) return 0;
state->forceAttrs(*a->value, a->pos, "while evaluating the 'meta' attribute of a derivation");
meta = a->value->attrs;
meta = a->value->attrs();
return meta;
}
@ -205,9 +204,8 @@ bool PackageInfo::checkMeta(Value & v)
return true;
}
else if (v.type() == nAttrs) {
Bindings::iterator i = v.attrs->find(state->sOutPath);
if (i != v.attrs->end()) return false;
for (auto & i : *v.attrs)
if (v.attrs()->get(state->sOutPath)) return false;
for (auto & i : *v.attrs())
if (!checkMeta(*i.value)) return false;
return true;
}
@ -219,8 +217,8 @@ bool PackageInfo::checkMeta(Value & v)
Value * PackageInfo::queryMeta(const std::string & name)
{
if (!getMeta()) return 0;
Bindings::iterator a = meta->find(state->symbols.create(name));
if (a == meta->end() || !checkMeta(*a->value)) return 0;
auto a = meta->get(state->symbols.create(name));
if (!a || !checkMeta(*a->value)) return 0;
return a->value;
}
@ -237,7 +235,7 @@ NixInt PackageInfo::queryMetaInt(const std::string & name, NixInt def)
{
Value * v = queryMeta(name);
if (!v) return def;
if (v->type() == nInt) return v->integer;
if (v->type() == nInt) return v->integer();
if (v->type() == nString) {
/* Backwards compatibility with before we had support for
integer meta fields. */
@ -251,7 +249,7 @@ NixFloat PackageInfo::queryMetaFloat(const std::string & name, NixFloat def)
{
Value * v = queryMeta(name);
if (!v) return def;
if (v->type() == nFloat) return v->fpoint;
if (v->type() == nFloat) return v->fpoint();
if (v->type() == nString) {
/* Backwards compatibility with before we had support for
float meta fields. */
@ -266,7 +264,7 @@ bool PackageInfo::queryMetaBool(const std::string & name, bool def)
{
Value * v = queryMeta(name);
if (!v) return def;
if (v->type() == nBool) return v->boolean;
if (v->type() == nBool) return v->boolean();
if (v->type() == nString) {
/* Backwards compatibility with before we had support for
Boolean meta fields. */
@ -292,7 +290,7 @@ void PackageInfo::setMeta(const std::string & name, Value * v)
/* Cache for already considered attrsets. */
typedef std::set<Bindings *> Done;
typedef std::set<const Bindings *> Done;
/* Evaluate value `v'. If it evaluates to a set of type `derivation',
@ -309,9 +307,9 @@ static bool getDerivation(EvalState & state, Value & v,
/* Remove spurious duplicates (e.g., a set like `rec { x =
derivation {...}; y = x;}'. */
if (!done.insert(v.attrs).second) return false;
if (!done.insert(v.attrs()).second) return false;
PackageInfo drv(state, attrPath, v.attrs);
PackageInfo drv(state, attrPath, v.attrs());
drv.queryName();
@ -361,14 +359,14 @@ static void getDerivations(EvalState & state, Value & vIn,
/* !!! undocumented hackery to support combining channels in
nix-env.cc. */
bool combineChannels = v.attrs->find(state.symbols.create("_combineChannels")) != v.attrs->end();
bool combineChannels = v.attrs()->get(state.symbols.create("_combineChannels"));
/* Consider the attributes in sorted order to get more
deterministic behaviour in nix-env operations (e.g. when
there are names clashes between derivations, the derivation
bound to the attribute with the "lower" name should take
precedence). */
for (auto & i : v.attrs->lexicographicOrder(state.symbols)) {
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;
@ -380,8 +378,8 @@ static void getDerivations(EvalState & state, Value & vIn,
should we recurse into it? => Only if it has a
`recurseForDerivations = true' attribute. */
if (i->value->type() == nAttrs) {
Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations);
if (j != i->value->attrs->end() && state.forceBool(*j->value, j->pos, "while evaluating the attribute `recurseForDerivations`"))
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);
}
}

View file

@ -33,9 +33,9 @@ private:
*/
bool failed = false;
Bindings * attrs = nullptr, * meta = nullptr;
const Bindings * attrs = nullptr, * meta = nullptr;
Bindings * getMeta();
const Bindings * getMeta();
bool checkMeta(Value & v);
@ -46,7 +46,7 @@ public:
std::string attrPath;
PackageInfo(EvalState & state) : state(&state) { };
PackageInfo(EvalState & state, std::string attrPath, Bindings * attrs);
PackageInfo(EvalState & state, std::string attrPath, const Bindings * attrs);
PackageInfo(EvalState & state, ref<Store> store, const std::string & drvPathWithOutputs);
std::string queryName() const;

View file

@ -28,12 +28,12 @@ void Expr::show(const SymbolTable & symbols, std::ostream & str) const
void ExprInt::show(const SymbolTable & symbols, std::ostream & str) const
{
str << v.integer;
str << v.integer();
}
void ExprFloat::show(const SymbolTable & symbols, std::ostream & str) const
{
str << v.fpoint;
str << v.fpoint();
}
void ExprString::show(const SymbolTable & symbols, std::ostream & str) const

View file

@ -28,7 +28,10 @@
#include <algorithm>
#include <cstring>
#include <regex>
#include <dlfcn.h>
#ifndef _WIN32
# include <dlfcn.h>
#endif
#include <cmath>
@ -39,7 +42,7 @@ namespace nix {
* Miscellaneous
*************************************************************/
StringMap EvalState::realiseContext(const NixStringContext & context)
StringMap EvalState::realiseContext(const NixStringContext & context, StorePathSet * maybePathsOut, bool isIFD)
{
std::vector<DerivedPath::Built> drvs;
StringMap res;
@ -59,21 +62,23 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
},
[&](const NixStringContextElem::Opaque & o) {
auto ctxS = store->printStorePath(o.path);
res.insert_or_assign(ctxS, ctxS);
ensureValid(o.path);
if (maybePathsOut)
maybePathsOut->emplace(o.path);
},
[&](const NixStringContextElem::DrvDeep & d) {
/* Treat same as Opaque */
auto ctxS = store->printStorePath(d.drvPath);
res.insert_or_assign(ctxS, ctxS);
ensureValid(d.drvPath);
if (maybePathsOut)
maybePathsOut->emplace(d.drvPath);
},
}, c.raw);
}
if (drvs.empty()) return {};
if (!evalSettings.enableImportFromDerivation)
if (isIFD && !evalSettings.enableImportFromDerivation)
error<EvalError>(
"cannot build '%1%' during evaluation because the option 'allow-import-from-derivation' is disabled",
drvs.begin()->to_string(*store)
@ -90,6 +95,8 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
auto outputs = resolveDerivedPath(*buildStore, drv, &*store);
for (auto & [outputName, outputPath] : outputs) {
outputsToCopyAndAllow.insert(outputPath);
if (maybePathsOut)
maybePathsOut->emplace(outputPath);
/* Get all the output paths corresponding to the placeholders we had */
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
@ -106,10 +113,13 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
}
if (store != buildStore) copyClosure(*buildStore, *store, outputsToCopyAndAllow);
for (auto & outputPath : outputsToCopyAndAllow) {
/* Add the output of this derivations to the allowed
paths. */
allowPath(outputPath);
if (isIFD) {
for (auto & outputPath : outputsToCopyAndAllow) {
/* Add the output of this derivations to the allowed
paths. */
allowPath(outputPath);
}
}
return res;
@ -216,13 +226,13 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
else {
state.forceAttrs(*vScope, pos, "while evaluating the first argument passed to builtins.scopedImport");
Env * env = &state.allocEnv(vScope->attrs->size());
Env * env = &state.allocEnv(vScope->attrs()->size());
env->up = &state.baseEnv;
auto staticEnv = std::make_shared<StaticEnv>(nullptr, state.staticBaseEnv.get(), vScope->attrs->size());
auto staticEnv = std::make_shared<StaticEnv>(nullptr, state.staticBaseEnv.get(), vScope->attrs()->size());
unsigned int displ = 0;
for (auto & attr : *vScope->attrs) {
for (auto & attr : *vScope->attrs()) {
staticEnv->vars.emplace_back(attr.name, displ);
env->values[displ++] = attr.value;
}
@ -324,6 +334,8 @@ static RegisterPrimOp primop_import({
}
});
#ifndef _WIN32 // TODO implement via DLL loading on Windows
/* Want reasonable symbol names, so extern C */
/* !!! Should we pass the Pos or the file name too? */
extern "C" typedef void (*ValueInitializer)(EvalState & state, Value & v);
@ -396,6 +408,8 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
}
}
#endif
/* Return a string representing the type of the expression. */
static void prim_typeOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
@ -411,7 +425,7 @@ static void prim_typeOf(EvalState & state, const PosIdx pos, Value * * args, Val
case nList: t = "list"; break;
case nFunction: t = "lambda"; break;
case nExternal:
t = args[0]->external->typeOf();
t = args[0]->external()->typeOf();
break;
case nFloat: t = "float"; break;
case nThunk: abort();
@ -575,9 +589,9 @@ struct CompareValues
{
try {
if (v1->type() == nFloat && v2->type() == nInt)
return v1->fpoint < v2->integer;
return v1->fpoint() < v2->integer();
if (v1->type() == nInt && v2->type() == nFloat)
return v1->integer < v2->fpoint;
return v1->integer() < v2->fpoint();
if (v1->type() != v2->type())
state.error<EvalError>("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow();
// Allow selecting a subset of enum values
@ -585,16 +599,16 @@ struct CompareValues
#pragma GCC diagnostic ignored "-Wswitch-enum"
switch (v1->type()) {
case nInt:
return v1->integer < v2->integer;
return v1->integer() < v2->integer();
case nFloat:
return v1->fpoint < v2->fpoint;
return v1->fpoint() < v2->fpoint();
case nString:
return strcmp(v1->c_str(), v2->c_str()) < 0;
case nPath:
// Note: we don't take the accessor into account
// since it's not obvious how to compare them in a
// reproducible way.
return strcmp(v1->_path.path, v2->_path.path) < 0;
return strcmp(v1->payload.path.path, v2->payload.path.path) < 0;
case nList:
// Lexicographic comparison
for (size_t i = 0;; i++) {
@ -626,13 +640,13 @@ typedef std::list<Value *> ValueList;
#endif
static Bindings::iterator getAttr(
static Bindings::const_iterator getAttr(
EvalState & state,
Symbol attrSym,
Bindings * attrSet,
const Bindings * attrSet,
std::string_view errorCtx)
{
Bindings::iterator value = attrSet->find(attrSym);
auto value = attrSet->find(attrSym);
if (value == attrSet->end()) {
state.error<TypeError>("attribute '%s' missing", state.symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow();
}
@ -644,7 +658,7 @@ static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * a
state.forceAttrs(*args[0], noPos, "while evaluating the first argument passed to builtins.genericClosure");
/* Get the start set. */
Bindings::iterator startSet = getAttr(state, state.sStartSet, args[0]->attrs, "in the attrset passed as argument to builtins.genericClosure");
auto startSet = getAttr(state, state.sStartSet, args[0]->attrs(), "in the attrset passed as argument to builtins.genericClosure");
state.forceList(*startSet->value, noPos, "while evaluating the 'startSet' attribute passed as argument to builtins.genericClosure");
@ -658,7 +672,7 @@ static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * a
}
/* Get the operator. */
Bindings::iterator op = getAttr(state, state.sOperator, args[0]->attrs, "in the attrset passed as argument to builtins.genericClosure");
auto op = getAttr(state, state.sOperator, args[0]->attrs(), "in the attrset passed as argument to builtins.genericClosure");
state.forceFunction(*op->value, noPos, "while evaluating the 'operator' attribute passed as argument to builtins.genericClosure");
/* Construct the closure by applying the operator to elements of
@ -675,7 +689,7 @@ static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * a
state.forceAttrs(*e, noPos, "while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure");
Bindings::iterator key = getAttr(state, state.sKey, e->attrs, "in one of the attrsets generated by (or initially passed to) builtins.genericClosure");
auto key = getAttr(state, state.sKey, e->attrs(), "in one of the attrsets generated by (or initially passed to) builtins.genericClosure");
state.forceValue(*key->value, noPos);
if (!doneKeys.insert(key->value).second) continue;
@ -1043,7 +1057,11 @@ static void prim_second(EvalState & state, const PosIdx pos, Value * * args, Val
* Derivations
*************************************************************/
static void derivationStrictInternal(EvalState & state, const std::string & name, Bindings * attrs, Value & v);
static void derivationStrictInternal(
EvalState & state,
const std::string & name,
const Bindings * attrs,
Value & v);
/* Construct (as a unobservable side effect) a Nix derivation
expression that performs the derivation described by the argument
@ -1056,10 +1074,10 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
{
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.derivationStrict");
Bindings * attrs = args[0]->attrs;
auto attrs = args[0]->attrs();
/* Figure out the name first (for stack backtraces). */
Bindings::iterator nameAttr = getAttr(state, state.sName, attrs, "in the attrset passed as argument to builtins.derivationStrict");
auto nameAttr = getAttr(state, state.sName, attrs, "in the attrset passed as argument to builtins.derivationStrict");
std::string drvName;
try {
@ -1098,8 +1116,11 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
}
}
static void derivationStrictInternal(EvalState & state, const std::string &
drvName, Bindings * attrs, Value & v)
static void derivationStrictInternal(
EvalState & state,
const std::string & drvName,
const Bindings * attrs,
Value & v)
{
/* Check whether attributes should be passed as a JSON file. */
using nlohmann::json;
@ -1139,18 +1160,20 @@ drvName, Bindings * attrs, Value & v)
vomit("processing attribute '%1%'", key);
auto handleHashMode = [&](const std::string_view s) {
if (s == "recursive") ingestionMethod = FileIngestionMethod::Recursive;
else if (s == "flat") ingestionMethod = FileIngestionMethod::Flat;
else if (s == "git") {
experimentalFeatureSettings.require(Xp::GitHashing);
ingestionMethod = FileIngestionMethod::Git;
} else if (s == "text") {
experimentalFeatureSettings.require(Xp::DynamicDerivations);
ingestionMethod = TextIngestionMethod {};
} else
if (s == "recursive") {
// back compat, new name is "nar"
ingestionMethod = FileIngestionMethod::Recursive;
} else try {
ingestionMethod = ContentAddressMethod::parse(s);
} catch (UsageError &) {
state.error<EvalError>(
"invalid value '%s' for 'outputHashMode' attribute", s
).atPos(v).debugThrow();
}
if (ingestionMethod == TextIngestionMethod {})
experimentalFeatureSettings.require(Xp::DynamicDerivations);
if (ingestionMethod == FileIngestionMethod::Git)
experimentalFeatureSettings.require(Xp::GitHashing);
};
auto handleOutputs = [&](const Strings & ss) {
@ -1699,11 +1722,11 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V
state.forceAttrs(*v2, pos, "while evaluating an element of the list passed to builtins.findFile");
std::string prefix;
Bindings::iterator i = v2->attrs->find(state.sPrefix);
if (i != v2->attrs->end())
auto i = v2->attrs()->find(state.sPrefix);
if (i != v2->attrs()->end())
prefix = state.forceStringNoCtx(*i->value, pos, "while evaluating the `prefix` attribute of an element of the list passed to builtins.findFile");
i = getAttr(state, state.sPath, v2->attrs, "in an element of the __nixPath");
i = getAttr(state, state.sPath, v2->attrs(), "in an element of the __nixPath");
NixStringContext context;
auto path = state.coerceToString(pos, *i->value, context,
@ -1915,11 +1938,13 @@ static RegisterPrimOp primop_outputOf({
*`derivation reference`* must be a string that may contain a regular store path to a derivation, or may be a placeholder reference. If the derivation is produced by a derivation, you must explicitly select `drv.outPath`.
This primop can be chained arbitrarily deeply.
For instance,
```nix
builtins.outputOf
(builtins.outputOf myDrv "out")
"out"
```
will return a placeholder for the output of the output of `myDrv`.
This primop corresponds to the `^` sigil for derivable paths, e.g. as part of installable syntax on the command line.
@ -2375,7 +2400,7 @@ static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to 'builtins.path'");
for (auto & attr : *args[0]->attrs) {
for (auto & attr : *args[0]->attrs()) {
auto n = state.symbols[attr.name];
if (n == "path")
path.emplace(state.coerceToPath(attr.pos, *attr.value, context, "while evaluating the 'path' attribute passed to 'builtins.path'"));
@ -2450,9 +2475,9 @@ static void prim_attrNames(EvalState & state, const PosIdx pos, Value * * args,
{
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.attrNames");
auto list = state.buildList(args[0]->attrs->size());
auto list = state.buildList(args[0]->attrs()->size());
for (const auto & [n, i] : enumerate(*args[0]->attrs))
for (const auto & [n, i] : enumerate(*args[0]->attrs()))
(list[n] = state.allocValue())->mkString(state.symbols[i.name]);
std::sort(list.begin(), list.end(),
@ -2478,9 +2503,9 @@ static void prim_attrValues(EvalState & state, const PosIdx pos, Value * * args,
{
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.attrValues");
auto list = state.buildList(args[0]->attrs->size());
auto list = state.buildList(args[0]->attrs()->size());
for (const auto & [n, i] : enumerate(*args[0]->attrs))
for (const auto & [n, i] : enumerate(*args[0]->attrs()))
list[n] = (Value *) &i;
std::sort(list.begin(), list.end(),
@ -2511,10 +2536,10 @@ void prim_getAttr(EvalState & state, const PosIdx pos, Value * * args, Value & v
{
auto attr = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.getAttr");
state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.getAttr");
Bindings::iterator i = getAttr(
auto i = getAttr(
state,
state.symbols.create(attr),
args[1]->attrs,
args[1]->attrs(),
"in the attribute set under consideration"
);
// !!! add to stack trace?
@ -2540,8 +2565,8 @@ static void prim_unsafeGetAttrPos(EvalState & state, const PosIdx pos, Value * *
{
auto attr = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.unsafeGetAttrPos");
state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.unsafeGetAttrPos");
Bindings::iterator i = args[1]->attrs->find(state.symbols.create(attr));
if (i == args[1]->attrs->end())
auto i = args[1]->attrs()->find(state.symbols.create(attr));
if (i == args[1]->attrs()->end())
v.mkNull();
else
state.mkPos(v, i->pos);
@ -2569,13 +2594,13 @@ static struct LazyPosAcessors {
PrimOp primop_lineOfPos{
.arity = 1,
.fun = [] (EvalState & state, PosIdx pos, Value * * args, Value & v) {
v.mkInt(state.positions[PosIdx(args[0]->integer)].line);
v.mkInt(state.positions[PosIdx(args[0]->integer())].line);
}
};
PrimOp primop_columnOfPos{
.arity = 1,
.fun = [] (EvalState & state, PosIdx pos, Value * * args, Value & v) {
v.mkInt(state.positions[PosIdx(args[0]->integer)].column);
v.mkInt(state.positions[PosIdx(args[0]->integer())].column);
}
};
@ -2606,7 +2631,7 @@ static void prim_hasAttr(EvalState & state, const PosIdx pos, Value * * args, Va
{
auto attr = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hasAttr");
state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.hasAttr");
v.mkBool(args[1]->attrs->find(state.symbols.create(attr)) != args[1]->attrs->end());
v.mkBool(args[1]->attrs()->find(state.symbols.create(attr)) != args[1]->attrs()->end());
}
static RegisterPrimOp primop_hasAttr({
@ -2656,9 +2681,9 @@ static void prim_removeAttrs(EvalState & state, const PosIdx pos, Value * * args
/* Copy all attributes not in that set. Note that we don't need
to sort v.attrs because it's a subset of an already sorted
vector. */
auto attrs = state.buildBindings(args[0]->attrs->size());
auto attrs = state.buildBindings(args[0]->attrs()->size());
std::set_difference(
args[0]->attrs->begin(), args[0]->attrs->end(),
args[0]->attrs()->begin(), args[0]->attrs()->end(),
names.begin(), names.end(),
std::back_inserter(attrs));
v.mkAttrs(attrs.alreadySorted());
@ -2696,13 +2721,13 @@ static void prim_listToAttrs(EvalState & state, const PosIdx pos, Value * * args
for (auto v2 : args[0]->listItems()) {
state.forceAttrs(*v2, pos, "while evaluating an element of the list passed to builtins.listToAttrs");
Bindings::iterator j = getAttr(state, state.sName, v2->attrs, "in a {name=...; value=...;} pair");
auto j = getAttr(state, state.sName, v2->attrs(), "in a {name=...; value=...;} pair");
auto name = state.forceStringNoCtx(*j->value, j->pos, "while evaluating the `name` attribute of an element of the list passed to builtins.listToAttrs");
auto sym = state.symbols.create(name);
if (seen.insert(sym).second) {
Bindings::iterator j2 = getAttr(state, state.sValue, v2->attrs, "in a {name=...; value=...;} pair");
auto j2 = getAttr(state, state.sValue, v2->attrs(), "in a {name=...; value=...;} pair");
attrs.insert(sym, j2->value, j2->pos);
}
}
@ -2746,8 +2771,8 @@ static void prim_intersectAttrs(EvalState & state, const PosIdx pos, Value * * a
state.forceAttrs(*args[0], pos, "while evaluating the first argument passed to builtins.intersectAttrs");
state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.intersectAttrs");
Bindings &left = *args[0]->attrs;
Bindings &right = *args[1]->attrs;
auto & left = *args[0]->attrs();
auto & right = *args[1]->attrs();
auto attrs = state.buildBindings(std::min(left.size(), right.size()));
@ -2791,14 +2816,14 @@ static void prim_intersectAttrs(EvalState & state, const PosIdx pos, Value * * a
if (left.size() < right.size()) {
for (auto & l : left) {
Bindings::iterator r = right.find(l.name);
auto r = right.find(l.name);
if (r != right.end())
attrs.insert(*r);
}
}
else {
for (auto & r : right) {
Bindings::iterator l = left.find(r.name);
auto l = left.find(r.name);
if (l != left.end())
attrs.insert(r);
}
@ -2829,8 +2854,7 @@ static void prim_catAttrs(EvalState & state, const PosIdx pos, Value * * args, V
for (auto v2 : args[1]->listItems()) {
state.forceAttrs(*v2, pos, "while evaluating an element in the list passed as second argument to builtins.catAttrs");
Bindings::iterator i = v2->attrs->find(attrName);
if (i != v2->attrs->end())
if (auto i = v2->attrs()->get(attrName))
res[found++] = i->value;
}
@ -2867,13 +2891,13 @@ static void prim_functionArgs(EvalState & state, const PosIdx pos, Value * * arg
if (!args[0]->isLambda())
state.error<TypeError>("'functionArgs' requires a function").atPos(pos).debugThrow();
if (!args[0]->lambda.fun->hasFormals()) {
if (!args[0]->payload.lambda.fun->hasFormals()) {
v.mkAttrs(&state.emptyBindings);
return;
}
auto attrs = state.buildBindings(args[0]->lambda.fun->formals->formals.size());
for (auto & i : args[0]->lambda.fun->formals->formals)
auto attrs = state.buildBindings(args[0]->payload.lambda.fun->formals->formals.size());
for (auto & i : args[0]->payload.lambda.fun->formals->formals)
attrs.insert(i.name, state.getBool(i.def), i.pos);
v.mkAttrs(attrs);
}
@ -2900,9 +2924,9 @@ static void prim_mapAttrs(EvalState & state, const PosIdx pos, Value * * args, V
{
state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.mapAttrs");
auto attrs = state.buildBindings(args[1]->attrs->size());
auto attrs = state.buildBindings(args[1]->attrs()->size());
for (auto & i : *args[1]->attrs) {
for (auto & i : *args[1]->attrs()) {
Value * vName = state.allocValue();
Value * vFun2 = state.allocValue();
vName->mkString(state.symbols[i.name]);
@ -2952,7 +2976,7 @@ static void prim_zipAttrsWith(EvalState & state, const PosIdx pos, Value * * arg
for (auto & vElem : listItems) {
state.forceAttrs(*vElem, noPos, "while evaluating a value of the list passed as second argument to builtins.zipAttrsWith");
for (auto & attr : *vElem->attrs)
for (auto & attr : *vElem->attrs())
attrsSeen.try_emplace(attr.name).first->second.size++;
}
@ -2960,7 +2984,7 @@ static void prim_zipAttrsWith(EvalState & state, const PosIdx pos, Value * * arg
elem.list.emplace(state.buildList(elem.size));
for (auto & vElem : listItems) {
for (auto & attr : *vElem->attrs) {
for (auto & attr : *vElem->attrs()) {
auto & item = attrsSeen.at(attr.name);
(*item.list)[item.pos++] = attr.value;
}
@ -3402,10 +3426,8 @@ static void prim_sort(EvalState & state, const PosIdx pos, Value * * args, Value
auto comparator = [&](Value * a, Value * b) {
/* Optimization: if the comparator is lessThan, bypass
callFunction. */
/* TODO: (layus) this is absurd. An optimisation like this
should be outside the lambda creation */
if (args[0]->isPrimOp()) {
auto ptr = args[0]->primOp->fun.target<decltype(&prim_lessThan)>();
auto ptr = args[0]->primOp()->fun.target<decltype(&prim_lessThan)>();
if (ptr && *ptr == prim_lessThan)
return CompareValues(state, noPos, "while evaluating the ordering function passed to builtins.sort")(a, b);
}
@ -3898,18 +3920,17 @@ static RegisterPrimOp primop_hashString({
static void prim_convertHash(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceAttrs(*args[0], pos, "while evaluating the first argument passed to builtins.convertHash");
auto &inputAttrs = args[0]->attrs;
auto inputAttrs = args[0]->attrs();
Bindings::iterator iteratorHash = getAttr(state, state.symbols.create("hash"), inputAttrs, "while locating the attribute 'hash'");
auto iteratorHash = getAttr(state, state.symbols.create("hash"), inputAttrs, "while locating the attribute 'hash'");
auto hash = state.forceStringNoCtx(*iteratorHash->value, pos, "while evaluating the attribute 'hash'");
Bindings::iterator iteratorHashAlgo = inputAttrs->find(state.symbols.create("hashAlgo"));
auto iteratorHashAlgo = inputAttrs->get(state.symbols.create("hashAlgo"));
std::optional<HashAlgorithm> ha = std::nullopt;
if (iteratorHashAlgo != inputAttrs->end()) {
if (iteratorHashAlgo)
ha = parseHashAlgo(state.forceStringNoCtx(*iteratorHashAlgo->value, pos, "while evaluating the attribute 'hashAlgo'"));
}
Bindings::iterator iteratorToHashFormat = getAttr(state, state.symbols.create("toHashFormat"), args[0]->attrs, "while locating the attribute 'toHashFormat'");
auto iteratorToHashFormat = getAttr(state, state.symbols.create("toHashFormat"), args[0]->attrs(), "while locating the attribute 'toHashFormat'");
HashFormat hf = parseHashFormat(state.forceStringNoCtx(*iteratorToHashFormat->value, pos, "while evaluating the attribute 'toHashFormat'"));
v.mkString(Hash::parseAny(hash, ha).to_string(hf, hf == HashFormat::SRI));
@ -4587,6 +4608,7 @@ void EvalState::createBaseEnv()
)",
});
#ifndef _WIN32 // TODO implement on Windows
// Miscellaneous
if (evalSettings.enableNativeCode) {
addPrimOp({
@ -4600,6 +4622,7 @@ void EvalState::createBaseEnv()
.fun = prim_exec,
});
}
#endif
addPrimOp({
.name = "__traceVerbose",
@ -4663,7 +4686,7 @@ void EvalState::createBaseEnv()
/* Now that we've added all primops, sort the `builtins' set,
because attribute lookups expect it to be sorted. */
baseEnv.values[0]->attrs->sort();
baseEnv.values[0]->payload.attrs->sort();
staticBaseEnv->sort();

View file

@ -258,7 +258,7 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
auto sPath = state.symbols.create("path");
auto sAllOutputs = state.symbols.create("allOutputs");
for (auto & i : *args[1]->attrs) {
for (auto & i : *args[1]->attrs()) {
const auto & name = state.symbols[i.name];
if (!state.store->isStorePath(name))
state.error<EvalError>(
@ -269,17 +269,16 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
if (!settings.readOnlyMode)
state.store->ensurePath(namePath);
state.forceAttrs(*i.value, i.pos, "while evaluating the value of a string context");
auto iter = i.value->attrs->find(sPath);
if (iter != i.value->attrs->end()) {
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `path` attribute of a string context"))
if (auto attr = i.value->attrs()->get(sPath)) {
if (state.forceBool(*attr->value, attr->pos, "while evaluating the `path` attribute of a string context"))
context.emplace(NixStringContextElem::Opaque {
.path = namePath,
});
}
iter = i.value->attrs->find(sAllOutputs);
if (iter != i.value->attrs->end()) {
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `allOutputs` attribute of a string context")) {
if (auto attr = i.value->attrs()->get(sAllOutputs)) {
if (state.forceBool(*attr->value, attr->pos, "while evaluating the `allOutputs` attribute of a string context")) {
if (!isDerivation(name)) {
state.error<EvalError>(
"tried to add all-outputs context of %s, which is not a derivation, to a string",
@ -292,17 +291,16 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
}
}
iter = i.value->attrs->find(state.sOutputs);
if (iter != i.value->attrs->end()) {
state.forceList(*iter->value, iter->pos, "while evaluating the `outputs` attribute of a string context");
if (iter->value->listSize() && !isDerivation(name)) {
if (auto attr = i.value->attrs()->get(state.sOutputs)) {
state.forceList(*attr->value, attr->pos, "while evaluating the `outputs` attribute of a string context");
if (attr->value->listSize() && !isDerivation(name)) {
state.error<EvalError>(
"tried to add derivation output context of %s, which is not a derivation, to a string",
name
).atPos(i.pos).debugThrow();
}
for (auto elem : iter->value->listItems()) {
auto outputName = state.forceStringNoCtx(*elem, iter->pos, "while evaluating an output name within a string context");
for (auto elem : attr->value->listItems()) {
auto outputName = state.forceStringNoCtx(*elem, attr->pos, "while evaluating an output name within a string context");
context.emplace(NixStringContextElem::Built {
.drvPath = makeConstantStorePathRef(namePath),
.output = std::string { outputName },

View file

@ -121,7 +121,7 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
std::optional<StorePathOrGap> toPath;
std::optional<bool> inputAddressedMaybe;
for (auto & attr : *args[0]->attrs) {
for (auto & attr : *args[0]->attrs()) {
const auto & attrName = state.symbols[attr.name];
auto attrHint = [&]() -> std::string {
return "while evaluating the '" + attrName + "' attribute passed to builtins.fetchClosure";

View file

@ -20,7 +20,7 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
if (args[0]->type() == nAttrs) {
for (auto & attr : *args[0]->attrs) {
for (auto & attr : *args[0]->attrs()) {
std::string_view n(state.symbols[attr.name]);
if (n == "url")
url = state.coerceToString(attr.pos, *attr.value, context,

View file

@ -97,7 +97,7 @@ static void fetchTree(
fetchers::Attrs attrs;
if (auto aType = args[0]->attrs->get(state.sType)) {
if (auto aType = args[0]->attrs()->get(state.sType)) {
if (type)
state.error<EvalError>(
"unexpected attribute 'type'"
@ -110,7 +110,7 @@ static void fetchTree(
attrs.emplace("type", type.value());
for (auto & attr : *args[0]->attrs) {
for (auto & attr : *args[0]->attrs()) {
if (attr.name == state.sType) continue;
state.forceValue(*attr.value, attr.pos);
if (attr.value->type() == nPath || attr.value->type() == nString) {
@ -121,9 +121,9 @@ static void fetchTree(
: s);
}
else if (attr.value->type() == nBool)
attrs.emplace(state.symbols[attr.name], Explicit<bool>{attr.value->boolean});
attrs.emplace(state.symbols[attr.name], Explicit<bool>{attr.value->boolean()});
else if (attr.value->type() == nInt)
attrs.emplace(state.symbols[attr.name], uint64_t(attr.value->integer));
attrs.emplace(state.symbols[attr.name], uint64_t(attr.value->integer()));
else if (state.symbols[attr.name] == "publicKeys") {
experimentalFeatureSettings.require(Xp::VerifiedFetches);
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, context).dump());
@ -422,7 +422,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
if (args[0]->type() == nAttrs) {
for (auto & attr : *args[0]->attrs) {
for (auto & attr : *args[0]->attrs()) {
std::string_view n(state.symbols[attr.name]);
if (n == "url")
url = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the url we should fetch");
@ -473,7 +473,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
auto storePath =
unpack
? fetchToStore(*state.store, fetchers::downloadTarball(*url).accessor, FetchMode::Copy, name)
: fetchers::downloadFile(state.store, *url, name, (bool) expectedHash).storePath;
: fetchers::downloadFile(state.store, *url, name).storePath;
if (expectedHash) {
auto hash = unpack
@ -650,12 +650,14 @@ static RegisterPrimOp primop_fetchGit({
The public keys against which `rev` is verified if `verifyCommit` is enabled.
Must be given as a list of attribute sets with the following form:
```nix
{
key = "<public key>";
type = "<key type>"; # optional, default: "ssh-ed25519"
}
```
Requires the [`verified-fetches` experimental feature](@docroot@/contributing/experimental-features.md#xp-feature-verified-fetches).

View file

@ -21,10 +21,10 @@ void printAmbiguous(
}
switch (v.type()) {
case nInt:
str << v.integer;
str << v.integer();
break;
case nBool:
printLiteralBool(str, v.boolean);
printLiteralBool(str, v.boolean());
break;
case nString:
printLiteralString(str, v.string_view());
@ -36,11 +36,11 @@ void printAmbiguous(
str << "null";
break;
case nAttrs: {
if (seen && !v.attrs->empty() && !seen->insert(v.attrs).second)
if (seen && !v.attrs()->empty() && !seen->insert(v.attrs()).second)
str << "«repeated»";
else {
str << "{ ";
for (auto & i : v.attrs->lexicographicOrder(symbols)) {
for (auto & i : v.attrs()->lexicographicOrder(symbols)) {
str << symbols[i->name] << " = ";
printAmbiguous(*i->value, symbols, str, seen, depth - 1);
str << "; ";
@ -87,10 +87,10 @@ void printAmbiguous(
}
break;
case nExternal:
str << *v.external;
str << *v.external();
break;
case nFloat:
str << v.fpoint;
str << v.fpoint();
break;
default:
printError("Nix evaluator internal error: printAmbiguous: invalid value type");

View file

@ -223,7 +223,7 @@ private:
{
if (options.ansiColors)
output << ANSI_CYAN;
output << v.integer;
output << v.integer();
if (options.ansiColors)
output << ANSI_NORMAL;
}
@ -232,7 +232,7 @@ private:
{
if (options.ansiColors)
output << ANSI_CYAN;
output << v.fpoint;
output << v.fpoint();
if (options.ansiColors)
output << ANSI_NORMAL;
}
@ -241,7 +241,7 @@ private:
{
if (options.ansiColors)
output << ANSI_CYAN;
printLiteralBool(output, v.boolean);
printLiteralBool(output, v.boolean());
if (options.ansiColors)
output << ANSI_NORMAL;
}
@ -271,10 +271,9 @@ private:
void printDerivation(Value & v)
{
Bindings::iterator i = v.attrs->find(state.sDrvPath);
NixStringContext context;
std::string storePath;
if (i != v.attrs->end())
if (auto i = v.attrs()->get(state.sDrvPath))
storePath = state.store->printStorePath(state.coerceToStorePath(i->pos, *i->value, context, "while evaluating the drvPath of a derivation"));
if (options.ansiColors)
@ -312,7 +311,7 @@ private:
void printAttrs(Value & v, size_t depth)
{
if (seen && !seen->insert(v.attrs).second) {
if (seen && !seen->insert(v.attrs()).second) {
printRepeated();
return;
}
@ -324,7 +323,7 @@ private:
output << "{";
AttrVec sorted;
for (auto & i : *v.attrs)
for (auto & i : *v.attrs())
sorted.emplace_back(std::pair(state.symbols[i.name], i.value));
if (options.maxAttrs == std::numeric_limits<size_t>::max())
@ -423,18 +422,18 @@ private:
if (v.isLambda()) {
output << "lambda";
if (v.lambda.fun) {
if (v.lambda.fun->name) {
output << " " << state.symbols[v.lambda.fun->name];
if (v.payload.lambda.fun) {
if (v.payload.lambda.fun->name) {
output << " " << state.symbols[v.payload.lambda.fun->name];
}
std::ostringstream s;
s << state.positions[v.lambda.fun->pos];
s << state.positions[v.payload.lambda.fun->pos];
output << " @ " << filterANSIEscapes(s.str());
}
} else if (v.isPrimOp()) {
if (v.primOp)
output << *v.primOp;
if (v.primOp())
output << *v.primOp();
else
output << "primop";
} else if (v.isPrimOpApp()) {
@ -480,7 +479,7 @@ private:
void printExternal(Value & v)
{
v.external->print(output);
v.external()->print(output);
}
void printUnknown()

View file

@ -8,6 +8,9 @@
namespace nix {
// Do not want the windows macro (alias to `SearchPathA`)
#undef SearchPath
/**
* A "search path" is a list of ways look for something, used with
* `builtins.findFile` and `< >` lookup expressions.

View file

@ -22,11 +22,11 @@ json printValueAsJSON(EvalState & state, bool strict,
switch (v.type()) {
case nInt:
out = v.integer;
out = v.integer();
break;
case nBool:
out = v.boolean;
out = v.boolean();
break;
case nString:
@ -52,24 +52,20 @@ json printValueAsJSON(EvalState & state, bool strict,
out = *maybeString;
break;
}
auto i = v.attrs->find(state.sOutPath);
if (i == v.attrs->end()) {
if (auto i = v.attrs()->get(state.sOutPath))
return printValueAsJSON(state, strict, *i->value, i->pos, context, copyToStore);
else {
out = json::object();
StringSet names;
for (auto & j : *v.attrs)
names.emplace(state.symbols[j.name]);
for (auto & j : names) {
Attr & a(*v.attrs->find(state.symbols.create(j)));
for (auto & a : v.attrs()->lexicographicOrder(state.symbols)) {
try {
out[j] = printValueAsJSON(state, strict, *a.value, a.pos, context, copyToStore);
out[state.symbols[a->name]] = printValueAsJSON(state, strict, *a->value, a->pos, context, copyToStore);
} catch (Error & e) {
e.addTrace(state.positions[a.pos],
HintFmt("while evaluating attribute '%1%'", j));
e.addTrace(state.positions[a->pos],
HintFmt("while evaluating attribute '%1%'", state.symbols[a->name]));
throw;
}
}
} else
return printValueAsJSON(state, strict, *i->value, i->pos, context, copyToStore);
}
break;
}
@ -90,11 +86,11 @@ json printValueAsJSON(EvalState & state, bool strict,
}
case nExternal:
return v.external->printValueAsJSON(state, strict, context, copyToStore);
return v.external()->printValueAsJSON(state, strict, context, copyToStore);
break;
case nFloat:
out = v.fpoint;
out = v.fpoint();
break;
case nThunk:

View file

@ -32,23 +32,18 @@ static void posToXML(EvalState & state, XMLAttrs & xmlAttrs, const Pos & pos)
static void showAttrs(EvalState & state, bool strict, bool location,
Bindings & attrs, XMLWriter & doc, NixStringContext & context, PathSet & drvsSeen)
const Bindings & attrs, XMLWriter & doc, NixStringContext & context, PathSet & drvsSeen)
{
StringSet names;
for (auto & i : attrs)
names.emplace(state.symbols[i.name]);
for (auto & i : names) {
Attr & a(*attrs.find(state.symbols.create(i)));
for (auto & a : attrs.lexicographicOrder(state.symbols)) {
XMLAttrs xmlAttrs;
xmlAttrs["name"] = i;
if (location && a.pos) posToXML(state, xmlAttrs, state.positions[a.pos]);
xmlAttrs["name"] = state.symbols[a->name];
if (location && a->pos) posToXML(state, xmlAttrs, state.positions[a->pos]);
XMLOpenElement _(doc, "attr", xmlAttrs);
printValueAsXML(state, strict, location,
*a.value, doc, context, drvsSeen, a.pos);
*a->value, doc, context, drvsSeen, a->pos);
}
}
@ -64,11 +59,11 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
switch (v.type()) {
case nInt:
doc.writeEmptyElement("int", singletonAttrs("value", fmt("%1%", v.integer)));
doc.writeEmptyElement("int", singletonAttrs("value", fmt("%1%", v.integer())));
break;
case nBool:
doc.writeEmptyElement("bool", singletonAttrs("value", v.boolean ? "true" : "false"));
doc.writeEmptyElement("bool", singletonAttrs("value", v.boolean() ? "true" : "false"));
break;
case nString:
@ -89,18 +84,14 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
if (state.isDerivation(v)) {
XMLAttrs xmlAttrs;
Bindings::iterator a = v.attrs->find(state.symbols.create("derivation"));
Path drvPath;
a = v.attrs->find(state.sDrvPath);
if (a != v.attrs->end()) {
if (auto a = v.attrs()->get(state.sDrvPath)) {
if (strict) state.forceValue(*a->value, a->pos);
if (a->value->type() == nString)
xmlAttrs["drvPath"] = drvPath = a->value->c_str();
}
a = v.attrs->find(state.sOutPath);
if (a != v.attrs->end()) {
if (auto a = v.attrs()->get(state.sOutPath)) {
if (strict) state.forceValue(*a->value, a->pos);
if (a->value->type() == nString)
xmlAttrs["outPath"] = a->value->c_str();
@ -109,14 +100,14 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
XMLOpenElement _(doc, "derivation", xmlAttrs);
if (drvPath != "" && drvsSeen.insert(drvPath).second)
showAttrs(state, strict, location, *v.attrs, doc, context, drvsSeen);
showAttrs(state, strict, location, *v.attrs(), doc, context, drvsSeen);
else
doc.writeEmptyElement("repeated");
}
else {
XMLOpenElement _(doc, "attrs");
showAttrs(state, strict, location, *v.attrs, doc, context, drvsSeen);
showAttrs(state, strict, location, *v.attrs(), doc, context, drvsSeen);
}
break;
@ -135,28 +126,28 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
break;
}
XMLAttrs xmlAttrs;
if (location) posToXML(state, xmlAttrs, state.positions[v.lambda.fun->pos]);
if (location) posToXML(state, xmlAttrs, state.positions[v.payload.lambda.fun->pos]);
XMLOpenElement _(doc, "function", xmlAttrs);
if (v.lambda.fun->hasFormals()) {
if (v.payload.lambda.fun->hasFormals()) {
XMLAttrs attrs;
if (v.lambda.fun->arg) attrs["name"] = state.symbols[v.lambda.fun->arg];
if (v.lambda.fun->formals->ellipsis) attrs["ellipsis"] = "1";
if (v.payload.lambda.fun->arg) attrs["name"] = state.symbols[v.payload.lambda.fun->arg];
if (v.payload.lambda.fun->formals->ellipsis) attrs["ellipsis"] = "1";
XMLOpenElement _(doc, "attrspat", attrs);
for (auto & i : v.lambda.fun->formals->lexicographicOrder(state.symbols))
for (auto & i : v.payload.lambda.fun->formals->lexicographicOrder(state.symbols))
doc.writeEmptyElement("attr", singletonAttrs("name", state.symbols[i.name]));
} else
doc.writeEmptyElement("varpat", singletonAttrs("name", state.symbols[v.lambda.fun->arg]));
doc.writeEmptyElement("varpat", singletonAttrs("name", state.symbols[v.payload.lambda.fun->arg]));
break;
}
case nExternal:
v.external->printValueAsXML(state, strict, location, doc, context, drvsSeen, pos);
v.external()->printValueAsXML(state, strict, location, doc, context, drvsSeen, pos);
break;
case nFloat:
doc.writeEmptyElement("float", singletonAttrs("value", fmt("%1%", v.fpoint)));
doc.writeEmptyElement("float", singletonAttrs("value", fmt("%1%", v.fpoint())));
break;
case nThunk:

View file

@ -234,14 +234,14 @@ public:
ExprLambda * fun;
};
union
using Payload = union
{
NixInt integer;
bool boolean;
StringWithContext string;
Path _path;
Path path;
Bindings * attrs;
struct {
@ -258,6 +258,8 @@ public:
NixFloat fpoint;
};
Payload payload;
/**
* Returns the normal type of a Value. This only returns nThunk if
* the Value hasn't been forceValue'd
@ -286,34 +288,25 @@ public:
abort();
}
/**
* After overwriting an app node, be sure to clear pointers in the
* Value to ensure that the target isn't kept alive unnecessarily.
*/
inline void clearValue()
inline void finishValue(InternalType newType, Payload newPayload)
{
app.left = app.right = 0;
payload = newPayload;
internalType = newType;
}
inline void mkInt(NixInt n)
{
clearValue();
internalType = tInt;
integer = n;
finishValue(tInt, { .integer = n });
}
inline void mkBool(bool b)
{
clearValue();
internalType = tBool;
boolean = b;
finishValue(tBool, { .boolean = b });
}
inline void mkString(const char * s, const char * * context = 0)
{
internalType = tString;
string.c_str = s;
string.context = context;
finishValue(tString, { .string = { .c_str = s, .context = context } });
}
void mkString(std::string_view s);
@ -332,63 +325,44 @@ public:
inline void mkPath(InputAccessor * accessor, const char * path)
{
clearValue();
internalType = tPath;
_path.accessor = accessor;
_path.path = path;
finishValue(tPath, { .path = { .accessor = accessor, .path = path } });
}
inline void mkNull()
{
clearValue();
internalType = tNull;
finishValue(tNull, {});
}
inline void mkAttrs(Bindings * a)
{
clearValue();
internalType = tAttrs;
attrs = a;
finishValue(tAttrs, { .attrs = a });
}
Value & mkAttrs(BindingsBuilder & bindings);
void mkList(const ListBuilder & builder)
{
clearValue();
if (builder.size == 1) {
smallList[0] = builder.inlineElems[0];
internalType = tList1;
} else if (builder.size == 2) {
smallList[0] = builder.inlineElems[0];
smallList[1] = builder.inlineElems[1];
internalType = tList2;
} else {
bigList.size = builder.size;
bigList.elems = builder.elems;
internalType = tListN;
}
if (builder.size == 1)
finishValue(tList1, { .smallList = { builder.inlineElems[0] } });
else if (builder.size == 2)
finishValue(tList2, { .smallList = { builder.inlineElems[0], builder.inlineElems[1] } });
else
finishValue(tListN, { .bigList = { .size = builder.size, .elems = builder.elems } });
}
inline void mkThunk(Env * e, Expr * ex)
{
internalType = tThunk;
thunk.env = e;
thunk.expr = ex;
finishValue(tThunk, { .thunk = { .env = e, .expr = ex } });
}
inline void mkApp(Value * l, Value * r)
{
internalType = tApp;
app.left = l;
app.right = r;
finishValue(tApp, { .app = { .left = l, .right = r } });
}
inline void mkLambda(Env * e, ExprLambda * f)
{
internalType = tLambda;
lambda.env = e;
lambda.fun = f;
finishValue(tLambda, { .lambda = { .env = e, .fun = f } });
}
inline void mkBlackhole();
@ -397,28 +371,22 @@ public:
inline void mkPrimOpApp(Value * l, Value * r)
{
internalType = tPrimOpApp;
primOpApp.left = l;
primOpApp.right = r;
finishValue(tPrimOpApp, { .primOpApp = { .left = l, .right = r } });
}
/**
* For a `tPrimOpApp` value, get the original `PrimOp` value.
*/
PrimOp * primOpAppPrimOp() const;
const PrimOp * primOpAppPrimOp() const;
inline void mkExternal(ExternalValueBase * e)
{
clearValue();
internalType = tExternal;
external = e;
finishValue(tExternal, { .external = e });
}
inline void mkFloat(NixFloat n)
{
clearValue();
internalType = tFloat;
fpoint = n;
finishValue(tFloat, { .fpoint = n });
}
bool isList() const
@ -428,7 +396,7 @@ public:
Value * const * listElems()
{
return internalType == tList1 || internalType == tList2 ? smallList : bigList.elems;
return internalType == tList1 || internalType == tList2 ? payload.smallList : payload.bigList.elems;
}
std::span<Value * const> listItems() const
@ -439,12 +407,12 @@ public:
Value * const * listElems() const
{
return internalType == tList1 || internalType == tList2 ? smallList : bigList.elems;
return internalType == tList1 || internalType == tList2 ? payload.smallList : payload.bigList.elems;
}
size_t listSize() const
{
return internalType == tList1 ? 1 : internalType == tList2 ? 2 : bigList.size;
return internalType == tList1 ? 1 : internalType == tList2 ? 2 : payload.bigList.size;
}
PosIdx determinePos(const PosIdx pos) const;
@ -460,26 +428,44 @@ public:
{
assert(internalType == tPath);
return SourcePath(
ref(_path.accessor->shared_from_this()),
CanonPath(CanonPath::unchecked_t(), _path.path));
ref(payload.path.accessor->shared_from_this()),
CanonPath(CanonPath::unchecked_t(), payload.path.path));
}
std::string_view string_view() const
{
assert(internalType == tString);
return std::string_view(string.c_str);
return std::string_view(payload.string.c_str);
}
const char * const c_str() const
{
assert(internalType == tString);
return string.c_str;
return payload.string.c_str;
}
const char * * context() const
{
return string.context;
return payload.string.context;
}
ExternalValueBase * external() const
{ return payload.external; }
const Bindings * attrs() const
{ return payload.attrs; }
const PrimOp * primOp() const
{ return payload.primOp; }
bool boolean() const
{ return payload.boolean; }
NixInt integer() const
{ return payload.integer; }
NixFloat fpoint() const
{ return payload.fpoint; }
};
@ -487,13 +473,12 @@ extern ExprBlackHole eBlackHole;
bool Value::isBlackhole() const
{
return internalType == tThunk && thunk.expr == (Expr*) &eBlackHole;
return internalType == tThunk && payload.thunk.expr == (Expr*) &eBlackHole;
}
void Value::mkBlackhole()
{
internalType = tThunk;
thunk.expr = (Expr*) &eBlackHole;
mkThunk(nullptr, (Expr *) &eBlackHole);
}

View file

@ -3,6 +3,7 @@
#include "input-accessor.hh"
#include "source-path.hh"
#include "fetch-to-store.hh"
#include "json-utils.hh"
#include <nlohmann/json.hpp>
@ -412,3 +413,20 @@ std::string publicKeys_to_string(const std::vector<PublicKey>& publicKeys)
}
}
namespace nlohmann {
using namespace nix;
fetchers::PublicKey adl_serializer<fetchers::PublicKey>::from_json(const json & json) {
auto type = optionalValueAt(json, "type").value_or("ssh-ed25519");
auto key = valueAt(json, "key");
return fetchers::PublicKey { getString(type), getString(key) };
}
void adl_serializer<fetchers::PublicKey>::to_json(json & json, fetchers::PublicKey p) {
json["type"] = p.type;
json["key"] = p.key;
}
}

View file

@ -4,6 +4,7 @@
#include "types.hh"
#include "hash.hh"
#include "canon-path.hh"
#include "json-impls.hh"
#include "attrs.hh"
#include "url.hh"
@ -230,8 +231,9 @@ struct PublicKey
std::string type = "ssh-ed25519";
std::string key;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(PublicKey, type, key)
std::string publicKeys_to_string(const std::vector<PublicKey>&);
}
JSON_IMPL(fetchers::PublicKey)

View file

@ -38,7 +38,7 @@ std::string FilteringInputAccessor::readLink(const CanonPath & path)
std::string FilteringInputAccessor::showPath(const CanonPath & path)
{
return next->showPath(prefix / path);
return displayPrefix + next->showPath(prefix / path) + displaySuffix;
}
void FilteringInputAccessor::checkAccess(const CanonPath & path)

View file

@ -27,7 +27,9 @@ struct FilteringInputAccessor : InputAccessor
: next(src.accessor)
, prefix(src.path)
, makeNotAllowedError(std::move(makeNotAllowedError))
{ }
{
displayPrefix.clear();
}
std::string readFile(const CanonPath & path) override;

View file

@ -24,7 +24,10 @@ ref<InputAccessor> makeStorePathAccessor(
const StorePath & storePath)
{
// FIXME: should use `store->getFSAccessor()`
return makeFSInputAccessor(std::filesystem::path { store->toRealPath(storePath) });
auto root = std::filesystem::path { store->toRealPath(storePath) };
auto accessor = makeFSInputAccessor(root);
accessor->setPathDisplay(root.string());
return accessor;
}
SourcePath getUnfilteredRootPath(CanonPath path)

View file

@ -151,11 +151,11 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
{
initLibGit2();
if (pathExists(path.native())) {
if (git_repository_open(Setter(repo), path.c_str()))
if (pathExists(path.string())) {
if (git_repository_open(Setter(repo), path.string().c_str()))
throw Error("opening Git repository '%s': %s", path, git_error_last()->message);
} else {
if (git_repository_init(Setter(repo), path.c_str(), bare))
if (git_repository_init(Setter(repo), path.string().c_str(), bare))
throw Error("creating Git repository '%s': %s", path, git_error_last()->message);
}
}
@ -198,6 +198,12 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
return git_repository_is_shallow(*this);
}
void setRemote(const std::string & name, const std::string & url) override
{
if (git_remote_set_url(*this, name.c_str(), url.c_str()))
throw Error("setting remote '%s' URL to '%s': %s", name, url, git_error_last()->message);
}
Hash resolveRef(std::string ref) override
{
Object object;
@ -210,7 +216,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
std::vector<Submodule> parseSubmodules(const std::filesystem::path & configFile)
{
GitConfig config;
if (git_config_open_ondisk(Setter(config), configFile.c_str()))
if (git_config_open_ondisk(Setter(config), configFile.string().c_str()))
throw Error("parsing .gitmodules file: %s", git_error_last()->message);
ConfigIterator it;
@ -282,7 +288,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
/* Get submodule info. */
auto modulesFile = path / ".gitmodules";
if (pathExists(modulesFile))
if (pathExists(modulesFile.string()))
info.submodules = parseSubmodules(modulesFile);
return info;
@ -302,9 +308,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
std::vector<std::tuple<Submodule, Hash>> getSubmodules(const Hash & rev, bool exportIgnore) override;
std::string resolveSubmoduleUrl(
const std::string & url,
const std::string & base) override
std::string resolveSubmoduleUrl(const std::string & url) override
{
git_buf buf = GIT_BUF_INIT;
if (git_submodule_resolve_url(&buf, *this, url.c_str()))
@ -312,10 +316,6 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
Finally cleanup = [&]() { git_buf_dispose(&buf); };
std::string res(buf.ptr);
if (!hasPrefix(res, "/") && res.find("://") == res.npos)
res = parseURL(base + "/" + res).canonicalise().to_string();
return res;
}
@ -377,10 +377,10 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
auto dir = this->path;
Strings gitArgs;
if (shallow) {
gitArgs = { "-C", dir, "fetch", "--quiet", "--force", "--depth", "1", "--", url, refspec };
gitArgs = { "-C", dir.string(), "fetch", "--quiet", "--force", "--depth", "1", "--", url, refspec };
}
else {
gitArgs = { "-C", dir, "fetch", "--quiet", "--force", "--", url, refspec };
gitArgs = { "-C", dir.string(), "fetch", "--quiet", "--force", "--", url, refspec };
}
runProgram(RunOptions {
@ -426,7 +426,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
.args = {
"-c",
"gpg.ssh.allowedSignersFile=" + allowedSignersFile,
"-C", path,
"-C", path.string(),
"verify-commit",
rev.gitRev()
},

View file

@ -32,6 +32,8 @@ struct GitRepo
/* Return the commit hash to which a ref points. */
virtual Hash resolveRef(std::string ref) = 0;
virtual void setRemote(const std::string & name, const std::string & url) = 0;
/**
* Info about a submodule.
*/
@ -69,9 +71,7 @@ struct GitRepo
*/
virtual std::vector<std::tuple<Submodule, Hash>> getSubmodules(const Hash & rev, bool exportIgnore) = 0;
virtual std::string resolveSubmoduleUrl(
const std::string & url,
const std::string & base) = 0;
virtual std::string resolveSubmoduleUrl(const std::string & url) = 0;
virtual bool hasObject(const Hash & oid) = 0;

View file

@ -357,7 +357,7 @@ struct GitHubInputScheme : GitArchiveInputScheme
auto json = nlohmann::json::parse(
readFile(
store->toRealPath(
downloadFile(store, url, "source", false, headers).storePath)));
downloadFile(store, url, "source", headers).storePath)));
return RefInfo {
.rev = Hash::parseAny(std::string { json["sha"] }, HashAlgorithm::SHA1),
@ -431,7 +431,7 @@ struct GitLabInputScheme : GitArchiveInputScheme
auto json = nlohmann::json::parse(
readFile(
store->toRealPath(
downloadFile(store, url, "source", false, headers).storePath)));
downloadFile(store, url, "source", headers).storePath)));
return RefInfo {
.rev = Hash::parseAny(std::string(json[0]["id"]), HashAlgorithm::SHA1)
@ -495,7 +495,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme
std::string refUri;
if (ref == "HEAD") {
auto file = store->toRealPath(
downloadFile(store, fmt("%s/HEAD", base_url), "source", false, headers).storePath);
downloadFile(store, fmt("%s/HEAD", base_url), "source", headers).storePath);
std::ifstream is(file);
std::string line;
getline(is, line);
@ -511,7 +511,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme
std::regex refRegex(refUri);
auto file = store->toRealPath(
downloadFile(store, fmt("%s/info/refs", base_url), "source", false, headers).storePath);
downloadFile(store, fmt("%s/info/refs", base_url), "source", headers).storePath);
std::ifstream is(file);
std::string line;

View file

@ -9,6 +9,8 @@ struct MountedInputAccessor : InputAccessor
MountedInputAccessor(std::map<CanonPath, ref<InputAccessor>> _mounts)
: mounts(std::move(_mounts))
{
displayPrefix.clear();
// Currently we require a root filesystem. This could be relaxed.
assert(mounts.contains(CanonPath::root));
@ -48,7 +50,7 @@ struct MountedInputAccessor : InputAccessor
std::string showPath(const CanonPath & path) override
{
auto [accessor, subpath] = resolve(path);
return accessor->showPath(subpath);
return displayPrefix + accessor->showPath(subpath) + displaySuffix;
}
std::pair<ref<InputAccessor>, CanonPath> resolve(CanonPath path)

View file

@ -158,7 +158,7 @@ static std::shared_ptr<Registry> getGlobalRegistry(ref<Store> store)
}
if (!hasPrefix(path, "/")) {
auto storePath = downloadFile(store, path, "flake-registry.json", false).storePath;
auto storePath = downloadFile(store, path, "flake-registry.json").storePath;
if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>())
store2->addPermRoot(storePath, getCacheDir() + "/nix/flake-registry.json");
path = store->toRealPath(storePath);

View file

@ -19,7 +19,6 @@ DownloadFileResult downloadFile(
ref<Store> store,
const std::string & url,
const std::string & name,
bool locked,
const Headers & headers)
{
// FIXME: check store
@ -101,7 +100,7 @@ DownloadFileResult downloadFile(
inAttrs,
infoAttrs,
*storePath,
locked);
false);
}
return {
@ -306,7 +305,7 @@ struct FileInputScheme : CurlInputScheme
the Nix store directly, since there is little deduplication
benefit in using the Git cache for single big files like
tarballs. */
auto file = downloadFile(store, getStrAttr(input.attrs, "url"), input.getName(), false);
auto file = downloadFile(store, getStrAttr(input.attrs, "url"), input.getName());
auto narHash = store->queryPathInfo(file.storePath)->narHash;
input.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));

View file

@ -25,7 +25,6 @@ DownloadFileResult downloadFile(
ref<Store> store,
const std::string & url,
const std::string & name,
bool locked,
const Headers & headers = {});
struct DownloadTarballResult

View file

@ -527,6 +527,9 @@ struct GitInputScheme : InputScheme
auto repo = GitRepo::openRepo(cacheDir, true, true);
// We need to set the origin so resolving submodule URLs works
repo->setRemote("origin", repoInfo.url);
Path localRefFile =
ref.compare(0, 5, "refs/") == 0
? cacheDir + "/" + ref
@ -630,7 +633,7 @@ struct GitInputScheme : InputScheme
std::map<CanonPath, nix::ref<InputAccessor>> mounts;
for (auto & [submodule, submoduleRev] : repo->getSubmodules(rev, exportIgnore)) {
auto resolved = repo->resolveSubmoduleUrl(submodule.url, repoInfo.url);
auto resolved = repo->resolveSubmoduleUrl(submodule.url);
debug("Git submodule %s: %s %s %s -> %s",
submodule.path, submodule.url, submodule.branch, submoduleRev.gitRev(), resolved);
fetchers::Attrs attrs;
@ -643,6 +646,7 @@ struct GitInputScheme : InputScheme
auto submoduleInput = fetchers::Input::fromAttrs(std::move(attrs));
auto [submoduleAccessor, submoduleInput2] =
submoduleInput.getAccessor(store);
submoduleAccessor->setPathDisplay("«" + submoduleInput.to_string() + "»");
mounts.insert_or_assign(submodule.path, submoduleAccessor);
}
@ -679,6 +683,8 @@ struct GitInputScheme : InputScheme
exportIgnore,
makeNotAllowedError(repoInfo.url));
accessor->setPathDisplay(repoInfo.url);
/* If the repo has submodules, return a mounted input accessor
consisting of the accessor for the top-level repo and the
accessors for the submodule workdirs. */
@ -695,6 +701,7 @@ struct GitInputScheme : InputScheme
auto submoduleInput = fetchers::Input::fromAttrs(std::move(attrs));
auto [submoduleAccessor, submoduleInput2] =
submoduleInput.getAccessor(store);
submoduleAccessor->setPathDisplay("«" + submoduleInput.to_string() + "»");
/* If the submodule is dirty, mark this repo dirty as
well. */

View file

@ -352,7 +352,11 @@ struct MercurialInputScheme : InputScheme
auto storePath = fetchToStore(store, input);
return {makeStorePathAccessor(store, storePath), input};
auto accessor = makeStorePathAccessor(store, storePath);
accessor->setPathDisplay("«" + input.to_string() + "»");
return {accessor, input};
}
bool isLocked(const Input & input) const override

View file

@ -108,7 +108,9 @@ std::string getArg(const std::string & opt,
return *i;
}
#ifndef _WIN32
static void sigHandler(int signo) { }
#endif
void initNix()
@ -121,6 +123,7 @@ void initNix()
initLibStore();
#ifndef _WIN32
unix::startSignalHandlerThread();
/* Reset SIGCHLD to its default. */
@ -135,6 +138,7 @@ void initNix()
/* Install a dummy SIGUSR1 handler for use with pthread_kill(). */
act.sa_handler = sigHandler;
if (sigaction(SIGUSR1, &act, 0)) throw SysError("handling SIGUSR1");
#endif
#if __APPLE__
/* HACK: on darwin, we need cant use sigprocmask with SIGWINCH.
@ -156,21 +160,26 @@ void initNix()
if (sigaction(SIGTRAP, &act, 0)) throw SysError("handling SIGTRAP");
#endif
#ifndef _WIN32
/* Register a SIGSEGV handler to detect stack overflows.
Why not initLibExpr()? initGC() is essentially that, but
detectStackOverflow is not an instance of the init function concept, as
it may have to be invoked more than once per process. */
detectStackOverflow();
#endif
/* There is no privacy in the Nix system ;-) At least not for
now. In particular, store objects should be readable by
everybody. */
umask(0022);
#ifndef _WIN32
/* Initialise the PRNG. */
struct timeval tv;
gettimeofday(&tv, 0);
srandom(tv.tv_usec);
#endif
}
@ -368,6 +377,9 @@ RunPager::RunPager()
Pipe toPager;
toPager.create();
#ifdef _WIN32 // TODO re-enable on Windows, once we can start processes.
throw Error("Commit signature verification not implemented on Windows yet");
#else
pid = startProcess([&]() {
if (dup2(toPager.readSide.get(), STDIN_FILENO) == -1)
throw SysError("dupping stdin");
@ -386,17 +398,20 @@ RunPager::RunPager()
std_out = fcntl(STDOUT_FILENO, F_DUPFD_CLOEXEC, 0);
if (dup2(toPager.writeSide.get(), STDOUT_FILENO) == -1)
throw SysError("dupping standard output");
#endif
}
RunPager::~RunPager()
{
try {
#ifndef _WIN32 // TODO re-enable on Windows, once we can start processes.
if (pid != -1) {
std::cout.flush();
dup2(std_out, STDOUT_FILENO);
pid.wait();
}
#endif
} catch (...) {
ignoreException();
}

View file

@ -1,6 +1,7 @@
#pragma once
///@file
#include "file-descriptor.hh"
#include "processes.hh"
#include "args.hh"
#include "args/root.hh"
@ -89,8 +90,10 @@ public:
~RunPager();
private:
#ifndef _WIN32 // TODO re-enable on Windows, once we can start processes.
Pid pid;
int std_out;
#endif
Descriptor std_out;
};
extern volatile ::sig_atomic_t blockInt;
@ -112,6 +115,7 @@ struct PrintFreed
};
#ifndef _WIN32
/**
* Install a SIGSEGV handler to detect stack overflows.
*/
@ -141,5 +145,6 @@ extern std::function<void(siginfo_t * info, void * ctx)> stackOverflowHandler;
* logger. Exits the process immediately after.
*/
void defaultStackOverflowHandler(siginfo_t * info, void * ctx);
#endif
}

View file

@ -56,7 +56,7 @@ void nix_store_free(Store * store)
delete store;
}
nix_err nix_store_get_uri(nix_c_context * context, Store * store, void * callback, void * user_data)
nix_err nix_store_get_uri(nix_c_context * context, Store * store, nix_get_string_callback callback, void * user_data)
{
if (context)
context->last_err_code = NIX_OK;
@ -67,7 +67,8 @@ nix_err nix_store_get_uri(nix_c_context * context, Store * store, void * callbac
NIXC_CATCH_ERRS
}
nix_err nix_store_get_version(nix_c_context * context, Store * store, void * callback, void * user_data)
nix_err
nix_store_get_version(nix_c_context * context, Store * store, nix_get_string_callback callback, void * user_data)
{
if (context)
context->last_err_code = NIX_OK;
@ -128,7 +129,18 @@ nix_err nix_store_realise(
NIXC_CATCH_ERRS
}
void nix_store_path_name(const StorePath * store_path, nix_get_string_callback callback, void * user_data)
{
std::string_view name = store_path->path.name();
callback(name.data(), name.size(), user_data);
}
void nix_store_path_free(StorePath * sp)
{
delete sp;
}
StorePath * nix_store_path_clone(const StorePath * p)
{
return new StorePath{p->path};
}

View file

@ -76,7 +76,7 @@ void nix_store_free(Store * store);
* @see nix_get_string_callback
* @return error code, NIX_OK on success.
*/
nix_err nix_store_get_uri(nix_c_context * context, Store * store, void * callback, void * user_data);
nix_err nix_store_get_uri(nix_c_context * context, Store * store, nix_get_string_callback callback, void * user_data);
// returns: owned StorePath*
/**
@ -90,6 +90,23 @@ nix_err nix_store_get_uri(nix_c_context * context, Store * store, void * callbac
*/
StorePath * nix_store_parse_path(nix_c_context * context, Store * store, const char * path);
/**
* @brief Get the path name (e.g. "name" in /nix/store/...-name)
*
* @param[in] store_path the path to get the name from
* @param[in] callback called with the name
* @param[in] user_data arbitrary data, passed to the callback when it's called.
*/
void nix_store_path_name(const StorePath * store_path, nix_get_string_callback callback, void * user_data);
/**
* @brief Copy a StorePath
*
* @param[in] p the path to copy
* @return a new StorePath
*/
StorePath * nix_store_path_clone(const StorePath * p);
/** @brief Deallocate a StorePath
*
* Does not fail.
@ -111,7 +128,10 @@ bool nix_store_is_valid_path(nix_c_context * context, Store * store, StorePath *
/**
* @brief Realise a Nix store path
*
* Blocking, calls callback once for each realised output
* Blocking, calls callback once for each realised output.
*
* @note When working with expressions, consider using e.g. nix_string_realise to get the output. `.drvPath` may not be
* accurate or available in the future. See https://github.com/NixOS/nix/issues/6507
*
* @param[out] context Optional, stores error information
* @param[in] store Nix Store reference
@ -136,7 +156,8 @@ nix_err nix_store_realise(
* @see nix_get_string_callback
* @return error code, NIX_OK on success.
*/
nix_err nix_store_get_version(nix_c_context * context, Store * store, void * callback, void * user_data);
nix_err
nix_store_get_version(nix_c_context * context, Store * store, nix_get_string_callback callback, void * user_data);
// cffi end
#ifdef __cplusplus

View file

@ -76,7 +76,11 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
throw Error("collision between '%1%' and non-directory '%2%'", srcFile, target);
if (unlink(dstFile.c_str()) == -1)
throw SysError("unlinking '%1%'", dstFile);
if (mkdir(dstFile.c_str(), 0755) == -1)
if (mkdir(dstFile.c_str()
#ifndef _WIN32 // TODO abstract mkdir perms for Windows
, 0755
#endif
) == -1)
throw SysError("creating directory '%1%'", dstFile);
createLinks(state, target, dstFile, state.priorities[dstFile]);
createLinks(state, srcFile, dstFile, priority);

View file

@ -1,5 +1,4 @@
#include "daemon.hh"
#include "monitor-fd.hh"
#include "signals.hh"
#include "worker-protocol.hh"
#include "worker-protocol-impl.hh"
@ -16,6 +15,10 @@
#include "args.hh"
#include "git.hh"
#ifndef _WIN32 // TODO need graceful async exit support on Windows?
# include "monitor-fd.hh"
#endif
namespace nix::daemon {
Sink & operator << (Sink & sink, const Logger::Fields & fields)
@ -1018,7 +1021,9 @@ void processConnection(
TrustedFlag trusted,
RecursiveFlag recursive)
{
#ifndef _WIN32 // TODO need graceful async exit support on Windows?
auto monitor = !recursive ? std::make_unique<MonitorFdHup>(from.fd) : nullptr;
#endif
/* Exchange the greeting. */
unsigned int magic = readInt(from);

View file

@ -516,10 +516,12 @@ struct curlFileTransfer : public FileTransfer
Sync<State> state_;
#ifndef _WIN32 // TODO need graceful async exit support on Windows?
/* We can't use a std::condition_variable to wake up the curl
thread, because it only monitors file descriptors. So use a
pipe instead. */
Pipe wakeupPipe;
#endif
std::thread workerThread;
@ -539,8 +541,10 @@ struct curlFileTransfer : public FileTransfer
fileTransferSettings.httpConnections.get());
#endif
#ifndef _WIN32 // TODO need graceful async exit support on Windows?
wakeupPipe.create();
fcntl(wakeupPipe.readSide.get(), F_SETFL, O_NONBLOCK);
#endif
workerThread = std::thread([&]() { workerThreadEntry(); });
}
@ -561,15 +565,19 @@ struct curlFileTransfer : public FileTransfer
auto state(state_.lock());
state->quit = true;
}
#ifndef _WIN32 // TODO need graceful async exit support on Windows?
writeFull(wakeupPipe.writeSide.get(), " ", false);
#endif
}
void workerThreadMain()
{
/* Cause this thread to be notified on SIGINT. */
#ifndef _WIN32 // TODO need graceful async exit support on Windows?
auto callback = createInterruptCallback([&]() {
stopWorkerThread();
});
#endif
#if __linux__
unshareFilesystem();
@ -607,9 +615,11 @@ struct curlFileTransfer : public FileTransfer
/* Wait for activity, including wakeup events. */
int numfds = 0;
struct curl_waitfd extraFDs[1];
#ifndef _WIN32 // TODO need graceful async exit support on Windows?
extraFDs[0].fd = wakeupPipe.readSide.get();
extraFDs[0].events = CURL_WAIT_POLLIN;
extraFDs[0].revents = 0;
#endif
long maxSleepTimeMs = items.empty() ? 10000 : 100;
auto sleepTimeMs =
nextWakeup != std::chrono::steady_clock::time_point()
@ -693,7 +703,9 @@ struct curlFileTransfer : public FileTransfer
throw nix::Error("cannot enqueue download request because the download thread is shutting down");
state->incoming.push(item);
}
#ifndef _WIN32 // TODO need graceful async exit support on Windows?
writeFull(wakeupPipe.writeSide.get(), " ");
#endif
}
#if ENABLE_S3

View file

@ -9,11 +9,14 @@
#include <map>
#include <mutex>
#include <thread>
#include <dlfcn.h>
#include <sys/utsname.h>
#include <nlohmann/json.hpp>
#ifndef _WIN32
# include <dlfcn.h>
# include <sys/utsname.h>
#endif
#ifdef __GLIBC__
# include <gnu/lib-names.h>
# include <nss.h>
@ -46,7 +49,13 @@ static GlobalConfig::Register rSettings(&settings);
Settings::Settings()
: nixPrefix(NIX_PREFIX)
, nixStore(canonPath(getEnvNonEmpty("NIX_STORE_DIR").value_or(getEnvNonEmpty("NIX_STORE").value_or(NIX_STORE_DIR))))
, nixStore(
#ifndef _WIN32
// On Windows `/nix/store` is not a canonical path, but we dont'
// want to deal with that yet.
canonPath
#endif
(getEnvNonEmpty("NIX_STORE_DIR").value_or(getEnvNonEmpty("NIX_STORE").value_or(NIX_STORE_DIR))))
, nixDataDir(canonPath(getEnvNonEmpty("NIX_DATA_DIR").value_or(NIX_DATA_DIR)))
, nixLogDir(canonPath(getEnvNonEmpty("NIX_LOG_DIR").value_or(NIX_LOG_DIR)))
, nixStateDir(canonPath(getEnvNonEmpty("NIX_STATE_DIR").value_or(NIX_STATE_DIR)))
@ -56,7 +65,9 @@ Settings::Settings()
, nixManDir(canonPath(NIX_MAN_DIR))
, nixDaemonSocketFile(canonPath(getEnvNonEmpty("NIX_DAEMON_SOCKET_PATH").value_or(nixStateDir + DEFAULT_SOCKET_PATH)))
{
#ifndef _WIN32
buildUsersGroup = isRootUser() ? "nixbld" : "";
#endif
allowSymlinkedStore = getEnv("NIX_IGNORE_SYMLINK_STORE") == "1";
auto sslOverride = getEnv("NIX_SSL_CERT_FILE").value_or(getEnv("SSL_CERT_FILE").value_or(""));
@ -239,11 +250,15 @@ StringSet Settings::getDefaultExtraPlatforms()
bool Settings::isWSL1()
{
#if __linux__
struct utsname utsbuf;
uname(&utsbuf);
// WSL1 uses -Microsoft suffix
// WSL2 uses -microsoft-standard suffix
return hasSuffix(utsbuf.release, "-Microsoft");
#else
return false;
#endif
}
Path Settings::getDefaultSSLCertFile()
@ -341,6 +356,7 @@ void initPlugins()
for (const auto & file : pluginFiles) {
/* handle is purposefully leaked as there may be state in the
DSO needed by the action of the plugin. */
#ifndef _WIN32 // TODO implement via DLL loading on Windows
void *handle =
dlopen(file.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!handle)
@ -351,6 +367,9 @@ void initPlugins()
void (*nix_plugin_entry)() = (void (*)())dlsym(handle, "nix_plugin_entry");
if (nix_plugin_entry)
nix_plugin_entry();
#else
throw Error("could not dynamically open plugin file '%s'", file);
#endif
}
}

View file

@ -666,6 +666,7 @@ public:
Setting<bool> sandboxFallback{this, true, "sandbox-fallback",
"Whether to disable sandboxing when the kernel doesn't allow it."};
#ifndef _WIN32
Setting<bool> requireDropSupplementaryGroups{this, isRootUser(), "require-drop-supplementary-groups",
R"(
Following the principle of least privilege,
@ -683,6 +684,7 @@ public:
(since `root` usually has permissions to call setgroups)
and `false` otherwise.
)"};
#endif
#if __linux__
Setting<std::string> sandboxShmSize{

View file

@ -0,0 +1,34 @@
/*
* Determine the syscall number for `fchmodat2`.
*
* On most platforms this is 452. Exceptions can be found on
* a glibc git checkout via `rg --pcre2 'define __NR_fchmodat2 (?!452)'`.
*
* The problem is that glibc 2.39 and libseccomp 2.5.5 are needed to
* get the syscall number. However, a Nix built against nixpkgs 23.11
* (glibc 2.38) should still have the issue fixed without depending
* on the build environment.
*
* To achieve that, the macros below try to determine the platform and
* set the syscall number which is platform-specific, but
* in most cases 452.
*
* TODO: remove this when 23.11 is EOL and the entire (supported) ecosystem
* is on glibc 2.39.
*/
#if HAVE_SECCOMP
# if defined(__alpha__)
# define NIX_SYSCALL_FCHMODAT2 562
# elif defined(__x86_64__) && SIZE_MAX == 0xFFFFFFFF // x32
# define NIX_SYSCALL_FCHMODAT2 1073742276
# elif defined(__mips__) && defined(__mips64) && defined(_ABIN64) // mips64/n64
# define NIX_SYSCALL_FCHMODAT2 5452
# elif defined(__mips__) && defined(__mips64) && defined(_ABIN32) // mips64/n32
# define NIX_SYSCALL_FCHMODAT2 6452
# elif defined(__mips__) && defined(_ABIO32) // mips32
# define NIX_SYSCALL_FCHMODAT2 4452
# else
# define NIX_SYSCALL_FCHMODAT2 452
# endif
#endif // HAVE_SECCOMP

View file

@ -1,18 +1,15 @@
#include "personality.hh"
#include "globals.hh"
#if __linux__
#include <sys/utsname.h>
#include <sys/personality.h>
#endif
#include <cstring>
namespace nix {
namespace nix::linux {
void setPersonality(std::string_view system)
{
#if __linux__
/* Change the personality to 32-bit if we're doing an
i686-linux build on an x86_64-linux machine. */
struct utsname utsbuf;
@ -39,7 +36,6 @@ void setPersonality(std::string_view system)
determinism. */
int cur = personality(0xffffffff);
if (cur != -1) personality(cur | ADDR_NO_RANDOMIZE);
#endif
}
}

View file

@ -3,7 +3,7 @@
#include <string>
namespace nix {
namespace nix::linux {
void setPersonality(std::string_view system);

View file

@ -33,6 +33,10 @@ struct LocalStoreAccessor : PosixSourceAccessor
std::optional<Stat> maybeLstat(const CanonPath & path) override
{
/* Handle the case where `path` is (a parent of) the store. */
if (isDirOrInDir(store->storeDir, path.abs()))
return Stat{ .type = tDirectory };
return PosixSourceAccessor::maybeLstat(toRealPath(path));
}

View file

@ -4,9 +4,15 @@ libstore_NAME = libnixstore
libstore_DIR := $(d)
libstore_SOURCES := $(wildcard $(d)/*.cc $(d)/builtins/*.cc $(d)/build/*.cc)
libstore_SOURCES := $(wildcard $(d)/*.cc $(d)/builtins/*.cc)
ifdef HOST_UNIX
libstore_SOURCES += $(wildcard $(d)/unix/*.cc)
libstore_SOURCES += $(wildcard $(d)/unix/*.cc $(d)/unix/builtins/*.cc $(d)/unix/build/*.cc)
endif
ifdef HOST_LINUX
libstore_SOURCES += $(wildcard $(d)/linux/*.cc)
endif
ifdef HOST_WINDOWS
libstore_SOURCES += $(wildcard $(d)/windows/*.cc)
endif
libstore_LIBS = libutil
@ -36,25 +42,42 @@ INCLUDE_libstore := -I $(d) -I $(d)/build
ifdef HOST_UNIX
INCLUDE_libstore += -I $(d)/unix
endif
ifdef HOST_LINUX
INCLUDE_libstore += -I $(d)/linux
endif
ifdef HOST_WINDOWS
INCLUDE_libstore += -I $(d)/windows
endif
ifdef HOST_WINDOWS
NIX_ROOT = N:\\\\
else
NIX_ROOT =
endif
# Prefix all but `NIX_STORE_DIR`, since we aren't doing a local store
# yet so a "logical" store dir that is the same as unix is prefered.
#
# Also, it keeps the unit tests working.
libstore_CXXFLAGS += \
$(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libstore) \
-DNIX_PREFIX=\"$(prefix)\" \
-DNIX_PREFIX=\"$(NIX_ROOT)$(prefix)\" \
-DNIX_STORE_DIR=\"$(storedir)\" \
-DNIX_DATA_DIR=\"$(datadir)\" \
-DNIX_STATE_DIR=\"$(localstatedir)/nix\" \
-DNIX_LOG_DIR=\"$(localstatedir)/log/nix\" \
-DNIX_CONF_DIR=\"$(sysconfdir)/nix\" \
-DNIX_BIN_DIR=\"$(bindir)\" \
-DNIX_MAN_DIR=\"$(mandir)\" \
-DLSOF=\"$(lsof)\"
-DNIX_DATA_DIR=\"$(NIX_ROOT)$(datadir)\" \
-DNIX_STATE_DIR=\"$(NIX_ROOT)$(localstatedir)/nix\" \
-DNIX_LOG_DIR=\"$(NIX_ROOT)$(localstatedir)/log/nix\" \
-DNIX_CONF_DIR=\"$(NIX_ROOT)$(sysconfdir)/nix\" \
-DNIX_BIN_DIR=\"$(NIX_ROOT)$(bindir)\" \
-DNIX_MAN_DIR=\"$(NIX_ROOT)$(mandir)\" \
-DLSOF=\"$(NIX_ROOT)$(lsof)\"
ifeq ($(embedded_sandbox_shell),yes)
libstore_CXXFLAGS += -DSANDBOX_SHELL=\"__embedded_sandbox_shell__\"
$(d)/build/local-derivation-goal.cc: $(d)/embedded-sandbox-shell.gen.hh
$(d)/unix/build/local-derivation-goal.cc: $(d)/unix/embedded-sandbox-shell.gen.hh
$(d)/embedded-sandbox-shell.gen.hh: $(sandbox_shell)
$(d)/unix/embedded-sandbox-shell.gen.hh: $(sandbox_shell)
$(trace-gen) hexdump -v -e '1/1 "0x%x," "\n"' < $< > $@.tmp
@mv $@.tmp $@
else
@ -63,11 +86,11 @@ else
endif
endif
$(d)/local-store.cc: $(d)/schema.sql.gen.hh $(d)/ca-specific-schema.sql.gen.hh
$(d)/unix/local-store.cc: $(d)/unix/schema.sql.gen.hh $(d)/unix/ca-specific-schema.sql.gen.hh
$(d)/build.cc:
$(d)/unix/build.cc:
clean-files += $(d)/schema.sql.gen.hh $(d)/ca-specific-schema.sql.gen.hh
clean-files += $(d)/unix/schema.sql.gen.hh $(d)/unix/ca-specific-schema.sql.gen.hh
$(eval $(call install-file-in, $(buildprefix)$(d)/nix-store.pc, $(libdir)/pkgconfig, 0644))

View file

@ -1,7 +1,6 @@
#include "derivations.hh"
#include "parsed-derivations.hh"
#include "globals.hh"
#include "local-store.hh"
#include "store-api.hh"
#include "thread-pool.hh"
#include "realisation.hh"

View file

@ -186,7 +186,7 @@ std::optional<nlohmann::json> ParsedDerivation::prepareStructuredAttrs(Store & s
for (auto i = e->begin(); i != e->end(); ++i) {
StorePathSet storePaths;
for (auto & p : *i)
storePaths.insert(store.parseStorePath(p.get<std::string>()));
storePaths.insert(store.toStorePath(p.get<std::string>()).first);
json[i.key()] = pathInfoToJSON(store,
store.exportReferences(storePaths, inputPaths));
}

View file

@ -63,7 +63,18 @@ StorePath StorePath::random(std::string_view name)
StorePath StoreDirConfig::parseStorePath(std::string_view path) const
{
auto p = canonPath(std::string(path));
// On Windows, `/nix/store` is not a canonical path. More broadly it
// is unclear whether this function should be using the native
// notion of a canonical path at all. For example, it makes to
// support remote stores whose store dir is a non-native path (e.g.
// Windows <-> Unix ssh-ing).
auto p =
#ifdef _WIN32
path
#else
canonPath(std::string(path))
#endif
;
if (dirOf(p) != storeDir)
throw BadStorePath("path '%s' is not in the Nix store", p);
return StorePath(baseNameOf(p));

View file

@ -6,69 +6,9 @@
#include <cerrno>
#include <cstdlib>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
namespace nix {
AutoCloseFD openLockFile(const Path & path, bool create)
{
AutoCloseFD fd;
fd = open(path.c_str(), O_CLOEXEC | O_RDWR | (create ? O_CREAT : 0), 0600);
if (!fd && (create || errno != ENOENT))
throw SysError("opening lock file '%1%'", path);
return fd;
}
void deleteLockFile(const Path & path, int fd)
{
/* Get rid of the lock file. Have to be careful not to introduce
races. Write a (meaningless) token to the file to indicate to
other processes waiting on this lock that the lock is stale
(deleted). */
unlink(path.c_str());
writeFull(fd, "d");
/* Note that the result of unlink() is ignored; removing the lock
file is an optimisation, not a necessity. */
}
bool lockFile(int fd, LockType lockType, bool wait)
{
int type;
if (lockType == ltRead) type = LOCK_SH;
else if (lockType == ltWrite) type = LOCK_EX;
else if (lockType == ltNone) type = LOCK_UN;
else abort();
if (wait) {
while (flock(fd, type) != 0) {
checkInterrupt();
if (errno != EINTR)
throw SysError("acquiring/releasing lock");
else
return false;
}
} else {
while (flock(fd, type | LOCK_NB) != 0) {
checkInterrupt();
if (errno == EWOULDBLOCK) return false;
if (errno != EINTR)
throw SysError("acquiring/releasing lock");
}
}
return true;
}
PathLocks::PathLocks()
: deletePaths(false)
{
@ -82,68 +22,6 @@ PathLocks::PathLocks(const PathSet & paths, const std::string & waitMsg)
}
bool PathLocks::lockPaths(const PathSet & paths,
const std::string & waitMsg, bool wait)
{
assert(fds.empty());
/* Note that `fds' is built incrementally so that the destructor
will only release those locks that we have already acquired. */
/* Acquire the lock for each path in sorted order. This ensures
that locks are always acquired in the same order, thus
preventing deadlocks. */
for (auto & path : paths) {
checkInterrupt();
Path lockPath = path + ".lock";
debug("locking path '%1%'", path);
AutoCloseFD fd;
while (1) {
/* Open/create the lock file. */
fd = openLockFile(lockPath, true);
/* Acquire an exclusive lock. */
if (!lockFile(fd.get(), ltWrite, false)) {
if (wait) {
if (waitMsg != "") printError(waitMsg);
lockFile(fd.get(), ltWrite, true);
} else {
/* Failed to lock this path; release all other
locks. */
unlock();
return false;
}
}
debug("lock acquired on '%1%'", lockPath);
/* Check that the lock file hasn't become stale (i.e.,
hasn't been unlinked). */
struct stat st;
if (fstat(fd.get(), &st) == -1)
throw SysError("statting lock file '%1%'", lockPath);
if (st.st_size != 0)
/* This lock file has been unlinked, so we're holding
a lock on a deleted file. This means that other
processes may create and acquire a lock on
`lockPath', and proceed. So we must retry. */
debug("open lock file '%1%' has become stale", lockPath);
else
break;
}
/* Use borrow so that the descriptor isn't closed. */
fds.push_back(FDPair(fd.release(), lockPath));
}
return true;
}
PathLocks::~PathLocks()
{
try {
@ -154,40 +32,10 @@ PathLocks::~PathLocks()
}
void PathLocks::unlock()
{
for (auto & i : fds) {
if (deletePaths) deleteLockFile(i.second, i.first);
if (close(i.first) == -1)
printError(
"error (ignored): cannot close lock file on '%1%'",
i.second);
debug("lock released on '%1%'", i.second);
}
fds.clear();
}
void PathLocks::setDeletion(bool deletePaths)
{
this->deletePaths = deletePaths;
}
FdLock::FdLock(int fd, LockType lockType, bool wait, std::string_view waitMsg)
: fd(fd)
{
if (wait) {
if (!lockFile(fd, lockType, false)) {
printInfo("%s", waitMsg);
acquired = lockFile(fd, lockType, true);
}
} else
acquired = lockFile(fd, lockType, false);
}
}

View file

@ -5,22 +5,6 @@
namespace nix {
/**
* Open (possibly create) a lock file and return the file descriptor.
* -1 is returned if create is false and the lock could not be opened
* because it doesn't exist. Any other error throws an exception.
*/
AutoCloseFD openLockFile(const Path & path, bool create);
/**
* Delete an open lock file.
*/
void deleteLockFile(const Path & path, int fd);
enum LockType { ltRead, ltWrite, ltNone };
bool lockFile(int fd, LockType lockType, bool wait);
class PathLocks
{
private:
@ -40,18 +24,6 @@ public:
void setDeletion(bool deletePaths);
};
struct FdLock
{
int fd;
bool acquired = false;
FdLock(int fd, LockType lockType, bool wait, std::string_view waitMsg);
~FdLock()
{
if (acquired)
lockFile(fd, ltNone, false);
}
};
}
#include "pathlocks-impl.hh"

View file

@ -8,6 +8,7 @@
#include "types.hh"
#include "pathlocks.hh"
#include <optional>
#include <time.h>

View file

@ -71,11 +71,15 @@ std::pair<ref<SourceAccessor>, CanonPath> RemoteFSAccessor::fetch(const CanonPat
auto narAccessor = makeLazyNarAccessor(listing,
[cacheFile](uint64_t offset, uint64_t length) {
AutoCloseFD fd = open(cacheFile.c_str(), O_RDONLY | O_CLOEXEC);
AutoCloseFD fd = toDescriptor(open(cacheFile.c_str(), O_RDONLY
#ifndef _WIN32
| O_CLOEXEC
#endif
));
if (!fd)
throw SysError("opening NAR cache file '%s'", cacheFile);
if (lseek(fd.get(), offset, SEEK_SET) != (off_t) offset)
if (lseek(fromDescriptorReadOnly(fd.get()), offset, SEEK_SET) != (off_t) offset)
throw SysError("seeking in '%s'", cacheFile);
std::string buf(length, 0);

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