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 #PackConstructorInitializers: BinPack
BreakBeforeBinaryOperators: NonAssignment BreakBeforeBinaryOperators: NonAssignment
AlwaysBreakBeforeMultilineStrings: true 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": "documentation":
- changed-files: - changed-files:
- any-glob-to-any-file: "doc/manual/*" - 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/systemd/nix-daemon.conf
/misc/upstart/nix-daemon.conf /misc/upstart/nix-daemon.conf
/src/resolve-system-dependencies/resolve-system-dependencies
outputs/ outputs/
*.a *.a

View file

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

View file

@ -40,20 +40,33 @@ Nix expression `builtins.nixVersion`.
#include <nix_api_expr.h> #include <nix_api_expr.h>
#include <nix_api_value.h> #include <nix_api_value.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h>
// NOTE: This example lacks all error handling. Production code must check for // NOTE: This example lacks all error handling. Production code must check for
// errors, as some return values will be undefined. // errors, as some return values will be undefined.
int main() {
void my_get_string_cb(const char * start, unsigned int n, char ** user_data)
{
*user_data = strdup(start);
}
int main()
{
nix_libexpr_init(NULL); nix_libexpr_init(NULL);
Store* store = nix_store_open(NULL, "dummy://", NULL); Store * store = nix_store_open(NULL, "dummy://", NULL);
EvalState* state = nix_state_create(NULL, NULL, store); // empty search path (NIX_PATH) EvalState * state = nix_state_create(NULL, NULL, store); // empty search path (NIX_PATH)
Value *value = nix_alloc_value(NULL, state); Value * value = nix_alloc_value(NULL, state);
nix_expr_eval_from_string(NULL, state, "builtins.nixVersion", ".", value); nix_expr_eval_from_string(NULL, state, "builtins.nixVersion", ".", value);
nix_value_force(NULL, state, value); nix_value_force(NULL, state, value);
printf("Nix version: %s\n", nix_get_string(NULL, 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_gc_decref(NULL, value);
nix_state_free(state); nix_state_free(state);
nix_store_free(store); nix_store_free(store);

View file

@ -110,6 +110,7 @@
- [Derivation](protocols/json/derivation.md) - [Derivation](protocols/json/derivation.md)
- [Serving Tarball Flakes](protocols/tarball-fetcher.md) - [Serving Tarball Flakes](protocols/tarball-fetcher.md)
- [Store Path Specification](protocols/store-path.md) - [Store Path Specification](protocols/store-path.md)
- [Nix Archive (NAR) Format](protocols/nix-archive.md)
- [Derivation "ATerm" file format](protocols/derivation-aterm.md) - [Derivation "ATerm" file format](protocols/derivation-aterm.md)
- [Glossary](glossary.md) - [Glossary](glossary.md)
- [Contributing](contributing/index.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. A build plan itself is a series of *build tasks*, together with their build inputs.
> **Important** > **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. 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. 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 --realise`](@docroot@/command-ref/nix-store/realise.md) (to build the store
derivation). derivation).
[store derivation]: ../glossary.md#gloss-store-derivation [store derivation]: @docroot@/glossary.md#gloss-store-derivation
> **Warning** > **Warning**
> >

View file

@ -49,7 +49,7 @@ authentication, you can avoid typing the passphrase with `ssh-agent`.
- `--include-outputs`\ - `--include-outputs`\
Also copy the outputs of [store derivation]s included in the closure. 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`\ - `--use-substitutes` / `-s`\
Attempt to download missing paths on the target machine using Nixs 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 derivation, a list of derivations, or a set of derivations. The paths
of the resulting store derivations are printed on standard output. 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 If *files* is the character `-`, then a Nix expression will be read from
standard input. standard input.

View file

@ -40,12 +40,12 @@ symlink.
derivations *paths*. These are the paths that will be produced when derivations *paths*. These are the paths that will be produced when
the derivation is built. the derivation is built.
[output paths]: ../../glossary.md#gloss-output-path [output paths]: @docroot@/glossary.md#gloss-output-path
- `--requisites`; `-R`\ - `--requisites`; `-R`\
Prints out the [closure] of the store path *paths*. 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: This query has one option:
@ -66,7 +66,7 @@ symlink.
*paths*, that is, their immediate dependencies. (For *all* *paths*, that is, their immediate dependencies. (For *all*
dependencies, use `--requisites`.) dependencies, use `--requisites`.)
[references]: ../../glossary.md#gloss-reference [references]: @docroot@/glossary.md#gloss-reference
- `--referrers`\ - `--referrers`\
Prints the set of *referrers* of the store paths *paths*, that is, 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. example when *paths* were substituted from a binary cache.
Use `--valid-derivers` instead to obtain valid paths only. Use `--valid-derivers` instead to obtain valid paths only.
[deriver]: ../../glossary.md#gloss-deriver [deriver]: @docroot@/glossary.md#gloss-deriver
- `--valid-derivers`\ - `--valid-derivers`\
Prints a set of derivation files (`.drv`) which are supposed produce 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 # make internal-api-html
# xdg-open ./outputs/doc/share/doc/nix/internal-api/html/index.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` - `aarch64-darwin`
- `armv6l-linux` - `armv6l-linux`
- `armv7l-linux` - `armv7l-linux`
- `riscv64-linux`
In order to build Nix for a different platform than the one you're currently 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 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 $ 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. Add more [system types](#system-type) to `crossSystems` in `flake.nix` to bootstrap Nix on unsupported platforms.
### Building for multiple platforms at once ### 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 ## 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>] <cpu>-<os>[-<abi>]

View file

@ -215,6 +215,9 @@
[output path]: #gloss-output-path [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} - [deriver]{#gloss-deriver}
The [store derivation] that produced an [output path]. 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. This is the default.
- `"recursive"`\ - `"recursive"` or `"nar"`\
The hash is computed over the NAR archive dump of the output 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 (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 this case, the output can be anything, including a directory
tree. 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} - [`__contentAddressed`]{#adv-attr-__contentAddressed}
> **Warning** > **Warning**
> This attribute is part of an [experimental feature](@docroot@/contributing/experimental-features.md). > 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), [`disallowedReferences`](#adv-attr-disallowedReferences) and [`disallowedRequisites`](#adv-attr-disallowedRequisites),
the following attributes are available: 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. - `maxClosureSize` defines the maximum size of the output's closure.
- `ignoreSelfRefs` controls whether self-references should be considered when - `ignoreSelfRefs` controls whether self-references should be considered when
checking for allowed references/requisites. 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 file or directory at *path* must exist and is copied to the [store].
> The path appears in the result as the corresponding [store path]. > The path appears in the result as the corresponding [store path].
[store path]: ../glossary.md#gloss-store-path [store path]: @docroot@/glossary.md#gloss-store-path
[store]: ../glossary.md#gloss-store [store]: @docroot@/glossary.md#gloss-store
[String and path concatenation]: #string-and-path-concatenation [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 (where `freetype` is a [derivation]), you can instead write
[derivation]: ../glossary.md#gloss-derivation [derivation]: @docroot@/glossary.md#gloss-derivation
```nix ```nix
"--with-freetype2-library=${freetype}/lib" "--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 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** > **Example**
> >

View file

@ -113,7 +113,7 @@
For example, assume you used a file path in an interpolated string during a `nix repl` session. 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. 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]. 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 ## 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`: * `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 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. 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. 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. 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 while performing various operations (including `nix develop`, `nix flake
update`, and so on). With several fixes to Nix's signal handlers, Nix update`, and so on). With several fixes to Nix's signal handlers, Nix
commands will now exit quickly after Ctrl-C is pressed. 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 [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** > **Note**
> >

View file

@ -36,13 +36,8 @@
crossSystems = [ crossSystems = [
"armv6l-unknown-linux-gnueabihf" "armv6l-unknown-linux-gnueabihf"
"armv7l-unknown-linux-gnueabihf" "armv7l-unknown-linux-gnueabihf"
"riscv64-unknown-linux-gnu"
"x86_64-unknown-netbsd" "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" "x86_64-w64-mingw32"
]; ];
@ -88,7 +83,7 @@
in { in {
inherit stdenvs native; inherit stdenvs native;
static = native.pkgsStatic; static = native.pkgsStatic;
cross = lib.genAttrs shellCrossSystems (crossSystem: make-pkgs crossSystem "stdenv"); cross = forAllCrossSystems (crossSystem: make-pkgs crossSystem "stdenv");
}); });
installScriptFor = tarballs: installScriptFor = tarballs:
@ -264,6 +259,7 @@
# Cross # Cross
self.hydraJobs.binaryTarballCross."x86_64-linux"."armv6l-unknown-linux-gnueabihf" 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"."armv7l-unknown-linux-gnueabihf"
self.hydraJobs.binaryTarballCross."x86_64-linux"."riscv64-unknown-linux-gnu"
]; ];
installerScriptForGHA = installScriptFor [ installerScriptForGHA = installScriptFor [
# Native # Native
@ -272,6 +268,7 @@
# Cross # Cross
self.hydraJobs.binaryTarballCross."x86_64-linux"."armv6l-unknown-linux-gnueabihf" 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"."armv7l-unknown-linux-gnueabihf"
self.hydraJobs.binaryTarballCross."x86_64-linux"."riscv64-unknown-linux-gnu"
]; ];
# docker image with Nix inside # docker image with Nix inside
@ -431,8 +428,8 @@
in in
(makeShells "native" nixpkgsFor.${system}.native) // (makeShells "native" nixpkgsFor.${system}.native) //
(lib.optionalAttrs (!nixpkgsFor.${system}.native.stdenv.isDarwin) (lib.optionalAttrs (!nixpkgsFor.${system}.native.stdenv.isDarwin)
(makeShells "static" nixpkgsFor.${system}.static)) // (makeShells "static" nixpkgsFor.${system}.static) //
(lib.genAttrs shellCrossSystems (crossSystem: let pkgs = nixpkgsFor.${system}.cross.${crossSystem}; in makeShell pkgs pkgs.stdenv)) // (forAllCrossSystems (crossSystem: let pkgs = nixpkgsFor.${system}.cross.${crossSystem}; in makeShell pkgs pkgs.stdenv))) //
{ {
default = self.devShells.${system}.native-stdenvPackages; 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. # Allow switch-enum to be overridden for files that do not support it, usually because of dependency headers.
ERROR_SWITCH_ENUM = -Werror=switch-enum 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))) $(eval $(call install-file-in, $(i), $(includedir)/nix, 0644)))
ifdef HOST_UNIX ifdef HOST_UNIX

View file

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

View file

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

View file

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

View file

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

View file

@ -69,4 +69,4 @@ else
fi fi
export PATH="$NIX_LINK/bin:@localstatedir@/nix/profiles/default/bin:$PATH" 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" #include "experimental-features.hh"
using namespace nix; using namespace nix;
using namespace nix::unix;
using std::cin; using std::cin;
static void handleAlarm(int sig) { static void handleAlarm(int sig) {

View file

@ -148,7 +148,7 @@ MixOperateOnOptions::MixOperateOnOptions()
{ {
addFlag({ addFlag({
.longName = "derivation", .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, .category = installablesCategory,
.handler = {&operateOn, OperateOn::Derivation}, .handler = {&operateOn, OperateOn::Derivation},
}); });

View file

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

View file

@ -49,7 +49,7 @@ Value * InstallableFlake::getFlakeOutputs(EvalState & state, const flake::Locked
callFlake(state, lockedFlake, *vFlake); callFlake(state, lockedFlake, *vFlake);
auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs")); auto aOutputs = vFlake->attrs()->get(state.symbols.create("outputs"));
assert(aOutputs); assert(aOutputs);
state.forceValue(*aOutputs->value, aOutputs->value->determinePos(noPos)); 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); state->autoCallFunction(*autoArgs, v1, v2);
if (v2.type() == nAttrs) { if (v2.type() == nAttrs) {
for (auto & i : *v2.attrs) { for (auto & i : *v2.attrs()) {
std::string name = state->symbols[i.name]; std::string name = state->symbols[i.name];
if (name.find(searchWord) == 0) { if (name.find(searchWord) == 0) {
if (prefix_ == "") if (prefix_ == "")
@ -538,7 +538,7 @@ ref<eval_cache::EvalCache> openEvalCache(
state.forceAttrs(*vFlake, noPos, "while parsing cached flake data"); 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); assert(aOutputs);
return aOutputs->value; return aOutputs->value;

View file

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

View file

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

View file

@ -290,7 +290,7 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
e->eval(*state, *env, v); 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?)"); 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]; std::string_view name = state->symbols[i.name];
if (name.substr(0, cur2.size()) != cur2) continue; if (name.substr(0, cur2.size()) != cur2) continue;
completions.insert(concatStrings(prev, expr, ".", name)); 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"); auto path = state->coerceToPath(noPos, v, context, "while evaluating the filename to edit");
return {path, 0}; return {path, 0};
} else if (v.isLambda()) { } 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)) if (auto path = std::get_if<SourcePath>(&pos.origin))
return {*path, pos.line}; return {*path, pos.line};
else else
@ -742,17 +742,17 @@ void NixRepl::loadFiles()
void NixRepl::addAttrsToScope(Value & attrs) void NixRepl::addAttrsToScope(Value & attrs)
{ {
state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); }, "while evaluating an attribute set to be merged in the global scope"); 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"); 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); staticEnv->vars.emplace_back(i.name, displ);
env->values[displ++] = i.value; env->values[displ++] = i.value;
varNames.emplace(state->symbols[i.name]); varNames.emplace(state->symbols[i.name]);
} }
staticEnv->sort(); staticEnv->sort();
staticEnv->deduplicate(); 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[in] arg The argument to pass to the function.
* @param[out] value The result of the function call. * @param[out] value The result of the function call.
* @return NIX_OK if the function call was successful, an error code otherwise. * @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); 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; nix::NixStringContext & ctx;
}; };
struct nix_realised_string
{
std::string str;
std::vector<StorePath> storePaths;
};
#endif // NIX_API_EXPR_INTERNAL_H #endif // NIX_API_EXPR_INTERNAL_H

View file

@ -2,6 +2,7 @@
#include "config.hh" #include "config.hh"
#include "eval.hh" #include "eval.hh"
#include "globals.hh" #include "globals.hh"
#include "path.hh"
#include "primops.hh" #include "primops.hh"
#include "value.hh" #include "value.hh"
@ -9,7 +10,9 @@
#include "nix_api_expr_internal.h" #include "nix_api_expr_internal.h"
#include "nix_api_util.h" #include "nix_api_util.h"
#include "nix_api_util_internal.h" #include "nix_api_util_internal.h"
#include "nix_api_store_internal.h"
#include "nix_api_value.h" #include "nix_api_value.h"
#include "value/context.hh"
#ifdef HAVE_BOEHMGC #ifdef HAVE_BOEHMGC
# include "gc/gc.h" # include "gc/gc.h"
@ -158,21 +161,21 @@ bool nix_get_bool(nix_c_context * context, const Value * value)
try { try {
auto & v = check_value_not_null(value); auto & v = check_value_not_null(value);
assert(v.type() == nix::nBool); assert(v.type() == nix::nBool);
return v.boolean; return v.boolean();
} }
NIXC_CATCH_ERRS_RES(false); 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) if (context)
context->last_err_code = NIX_OK; context->last_err_code = NIX_OK;
try { try {
auto & v = check_value_not_null(value); auto & v = check_value_not_null(value);
assert(v.type() == nix::nString); 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) 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 // 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 // crashes. Looks like .path() allocates a CanonPath with a copy of the
// string, then it gets the underlying data from that. // string, then it gets the underlying data from that.
return v._path.path; return v.payload.path.path;
} }
NIXC_CATCH_ERRS_NULL NIXC_CATCH_ERRS_NULL
} }
@ -213,7 +216,7 @@ unsigned int nix_get_attrs_size(nix_c_context * context, const Value * value)
try { try {
auto & v = check_value_not_null(value); auto & v = check_value_not_null(value);
assert(v.type() == nix::nAttrs); assert(v.type() == nix::nAttrs);
return v.attrs->size(); return v.attrs()->size();
} }
NIXC_CATCH_ERRS_RES(0); NIXC_CATCH_ERRS_RES(0);
} }
@ -225,7 +228,7 @@ double nix_get_float(nix_c_context * context, const Value * value)
try { try {
auto & v = check_value_not_null(value); auto & v = check_value_not_null(value);
assert(v.type() == nix::nFloat); assert(v.type() == nix::nFloat);
return v.fpoint; return v.fpoint();
} }
NIXC_CATCH_ERRS_RES(0.0); NIXC_CATCH_ERRS_RES(0.0);
} }
@ -237,7 +240,7 @@ int64_t nix_get_int(nix_c_context * context, const Value * value)
try { try {
auto & v = check_value_not_null(value); auto & v = check_value_not_null(value);
assert(v.type() == nix::nInt); assert(v.type() == nix::nInt);
return v.integer; return v.integer();
} }
NIXC_CATCH_ERRS_RES(0); NIXC_CATCH_ERRS_RES(0);
} }
@ -249,7 +252,7 @@ ExternalValue * nix_get_external(nix_c_context * context, Value * value)
try { try {
auto & v = check_value_not_null(value); auto & v = check_value_not_null(value);
assert(v.type() == nix::nExternal); assert(v.type() == nix::nExternal);
return (ExternalValue *) v.external; return (ExternalValue *) v.external();
} }
NIXC_CATCH_ERRS_NULL; 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); auto & v = check_value_not_null(value);
assert(v.type() == nix::nAttrs); assert(v.type() == nix::nAttrs);
nix::Symbol s = state->state.symbols.create(name); nix::Symbol s = state->state.symbols.create(name);
auto attr = v.attrs->get(s); auto attr = v.attrs()->get(s);
if (attr) { if (attr) {
nix_gc_incref(nullptr, attr->value); nix_gc_incref(nullptr, attr->value);
state->state.forceValue(*attr->value, nix::noPos); 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); auto & v = check_value_not_null(value);
assert(v.type() == nix::nAttrs); assert(v.type() == nix::nAttrs);
nix::Symbol s = state->state.symbols.create(name); nix::Symbol s = state->state.symbols.create(name);
auto attr = v.attrs->get(s); auto attr = v.attrs()->get(s);
if (attr) if (attr)
return true; return true;
return false; 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; context->last_err_code = NIX_OK;
try { try {
auto & v = check_value_not_null(value); 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(); *name = ((const std::string &) (state->state.symbols[a.name])).c_str();
nix_gc_incref(nullptr, a.value); nix_gc_incref(nullptr, a.value);
state->state.forceValue(*a.value, nix::noPos); 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; context->last_err_code = NIX_OK;
try { try {
auto & v = check_value_not_null(value); 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(); return ((const std::string &) (state->state.symbols[a.name])).c_str();
} }
NIXC_CATCH_ERRS_NULL NIXC_CATCH_ERRS_NULL
@ -401,6 +404,19 @@ nix_err nix_init_null(nix_c_context * context, Value * value)
NIXC_CATCH_ERRS 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) nix_err nix_init_external(nix_c_context * context, Value * value, ExternalValue * val)
{ {
if (context) if (context)
@ -528,3 +544,52 @@ void nix_bindings_builder_free(BindingsBuilder * bb)
delete (nix::BindingsBuilder *) bb; delete (nix::BindingsBuilder *) bb;
#endif #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_util.h"
#include "nix_api_store.h"
#include "stdbool.h" #include "stdbool.h"
#include "stddef.h" #include "stddef.h"
#include "stdint.h" #include "stdint.h"
@ -69,6 +70,10 @@ typedef struct PrimOp PrimOp;
*/ */
typedef struct ExternalValue ExternalValue; typedef struct ExternalValue ExternalValue;
/** @brief String without placeholders, and realised store paths
*/
typedef struct nix_realised_string nix_realised_string;
/** @defgroup primops /** @defgroup primops
* @brief Create your own 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); 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[out] context Optional, stores error information
* @param[in] value Nix value to inspect * @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 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 /** @brief Get path as string
* @param[out] context Optional, stores error information * @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 * @param[out] value Nix value to modify
* @return error code, NIX_OK on success. * @return error code, NIX_OK on success.
*/ */
nix_err nix_init_null(nix_c_context * context, Value * value); nix_err nix_init_null(nix_c_context * context, 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 /** @brief Set an external value
* @param[out] context Optional, stores error information * @param[out] context Optional, stores error information
* @param[out] value Nix value to modify * @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 /** @brief Insert bindings into a builder
* @param[out] context Optional, stores error information * @param[out] context Optional, stores error information
* @param[in] builder BindingsBuilder to insert into * @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 * @param[in] value value to give the binding
* @return error code, NIX_OK on success. * @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); 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 // cffi end
#ifdef __cplusplus #ifdef __cplusplus
} }

View file

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

View file

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

View file

@ -94,7 +94,7 @@ struct PrimOp
void check(); void check();
}; };
std::ostream & operator<<(std::ostream & output, PrimOp & primOp); std::ostream & operator<<(std::ostream & output, const PrimOp & primOp);
/** /**
* Info about a constant * Info about a constant
@ -161,6 +161,8 @@ struct DebugTrace {
bool isError; bool isError;
}; };
// Don't want Windows function
#undef SearchPath
class EvalState : public std::enable_shared_from_this<EvalState> class EvalState : public std::enable_shared_from_this<EvalState>
{ {
@ -643,9 +645,6 @@ public:
inline Value * allocValue(); inline Value * allocValue();
inline Env & allocEnv(size_t size); 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); Bindings * allocBindings(size_t capacity);
BindingsBuilder buildBindings(size_t capacity) BindingsBuilder buildBindings(size_t capacity)
@ -733,10 +732,12 @@ public:
bool fullGC(); bool fullGC();
/** /**
* Realise the given context, and return a mapping from the placeholders * Realise the given context
* used to construct the associated value to their final store path * @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. */ /* Call the binary path filter predicate used builtins.path etc. */
bool callPathFilter( bool callPathFilter(

View file

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

View file

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

View file

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

View file

@ -28,7 +28,10 @@
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
#include <regex> #include <regex>
#include <dlfcn.h>
#ifndef _WIN32
# include <dlfcn.h>
#endif
#include <cmath> #include <cmath>
@ -39,7 +42,7 @@ namespace nix {
* Miscellaneous * Miscellaneous
*************************************************************/ *************************************************************/
StringMap EvalState::realiseContext(const NixStringContext & context) StringMap EvalState::realiseContext(const NixStringContext & context, StorePathSet * maybePathsOut, bool isIFD)
{ {
std::vector<DerivedPath::Built> drvs; std::vector<DerivedPath::Built> drvs;
StringMap res; StringMap res;
@ -59,21 +62,23 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
}, },
[&](const NixStringContextElem::Opaque & o) { [&](const NixStringContextElem::Opaque & o) {
auto ctxS = store->printStorePath(o.path); auto ctxS = store->printStorePath(o.path);
res.insert_or_assign(ctxS, ctxS);
ensureValid(o.path); ensureValid(o.path);
if (maybePathsOut)
maybePathsOut->emplace(o.path);
}, },
[&](const NixStringContextElem::DrvDeep & d) { [&](const NixStringContextElem::DrvDeep & d) {
/* Treat same as Opaque */ /* Treat same as Opaque */
auto ctxS = store->printStorePath(d.drvPath); auto ctxS = store->printStorePath(d.drvPath);
res.insert_or_assign(ctxS, ctxS);
ensureValid(d.drvPath); ensureValid(d.drvPath);
if (maybePathsOut)
maybePathsOut->emplace(d.drvPath);
}, },
}, c.raw); }, c.raw);
} }
if (drvs.empty()) return {}; if (drvs.empty()) return {};
if (!evalSettings.enableImportFromDerivation) if (isIFD && !evalSettings.enableImportFromDerivation)
error<EvalError>( error<EvalError>(
"cannot build '%1%' during evaluation because the option 'allow-import-from-derivation' is disabled", "cannot build '%1%' during evaluation because the option 'allow-import-from-derivation' is disabled",
drvs.begin()->to_string(*store) drvs.begin()->to_string(*store)
@ -90,6 +95,8 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
auto outputs = resolveDerivedPath(*buildStore, drv, &*store); auto outputs = resolveDerivedPath(*buildStore, drv, &*store);
for (auto & [outputName, outputPath] : outputs) { for (auto & [outputName, outputPath] : outputs) {
outputsToCopyAndAllow.insert(outputPath); outputsToCopyAndAllow.insert(outputPath);
if (maybePathsOut)
maybePathsOut->emplace(outputPath);
/* Get all the output paths corresponding to the placeholders we had */ /* Get all the output paths corresponding to the placeholders we had */
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) { if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
@ -106,11 +113,14 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
} }
if (store != buildStore) copyClosure(*buildStore, *store, outputsToCopyAndAllow); if (store != buildStore) copyClosure(*buildStore, *store, outputsToCopyAndAllow);
if (isIFD) {
for (auto & outputPath : outputsToCopyAndAllow) { for (auto & outputPath : outputsToCopyAndAllow) {
/* Add the output of this derivations to the allowed /* Add the output of this derivations to the allowed
paths. */ paths. */
allowPath(outputPath); allowPath(outputPath);
} }
}
return res; return res;
} }
@ -216,13 +226,13 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
else { else {
state.forceAttrs(*vScope, pos, "while evaluating the first argument passed to builtins.scopedImport"); 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; 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; unsigned int displ = 0;
for (auto & attr : *vScope->attrs) { for (auto & attr : *vScope->attrs()) {
staticEnv->vars.emplace_back(attr.name, displ); staticEnv->vars.emplace_back(attr.name, displ);
env->values[displ++] = attr.value; 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 */ /* Want reasonable symbol names, so extern C */
/* !!! Should we pass the Pos or the file name too? */ /* !!! Should we pass the Pos or the file name too? */
extern "C" typedef void (*ValueInitializer)(EvalState & state, Value & v); 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. */ /* Return a string representing the type of the expression. */
static void prim_typeOf(EvalState & state, const PosIdx pos, Value * * args, Value & v) 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 nList: t = "list"; break;
case nFunction: t = "lambda"; break; case nFunction: t = "lambda"; break;
case nExternal: case nExternal:
t = args[0]->external->typeOf(); t = args[0]->external()->typeOf();
break; break;
case nFloat: t = "float"; break; case nFloat: t = "float"; break;
case nThunk: abort(); case nThunk: abort();
@ -575,9 +589,9 @@ struct CompareValues
{ {
try { try {
if (v1->type() == nFloat && v2->type() == nInt) if (v1->type() == nFloat && v2->type() == nInt)
return v1->fpoint < v2->integer; return v1->fpoint() < v2->integer();
if (v1->type() == nInt && v2->type() == nFloat) if (v1->type() == nInt && v2->type() == nFloat)
return v1->integer < v2->fpoint; return v1->integer() < v2->fpoint();
if (v1->type() != v2->type()) if (v1->type() != v2->type())
state.error<EvalError>("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow(); state.error<EvalError>("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow();
// Allow selecting a subset of enum values // Allow selecting a subset of enum values
@ -585,16 +599,16 @@ struct CompareValues
#pragma GCC diagnostic ignored "-Wswitch-enum" #pragma GCC diagnostic ignored "-Wswitch-enum"
switch (v1->type()) { switch (v1->type()) {
case nInt: case nInt:
return v1->integer < v2->integer; return v1->integer() < v2->integer();
case nFloat: case nFloat:
return v1->fpoint < v2->fpoint; return v1->fpoint() < v2->fpoint();
case nString: case nString:
return strcmp(v1->c_str(), v2->c_str()) < 0; return strcmp(v1->c_str(), v2->c_str()) < 0;
case nPath: case nPath:
// Note: we don't take the accessor into account // Note: we don't take the accessor into account
// since it's not obvious how to compare them in a // since it's not obvious how to compare them in a
// reproducible way. // reproducible way.
return strcmp(v1->_path.path, v2->_path.path) < 0; return strcmp(v1->payload.path.path, v2->payload.path.path) < 0;
case nList: case nList:
// Lexicographic comparison // Lexicographic comparison
for (size_t i = 0;; i++) { for (size_t i = 0;; i++) {
@ -626,13 +640,13 @@ typedef std::list<Value *> ValueList;
#endif #endif
static Bindings::iterator getAttr( static Bindings::const_iterator getAttr(
EvalState & state, EvalState & state,
Symbol attrSym, Symbol attrSym,
Bindings * attrSet, const Bindings * attrSet,
std::string_view errorCtx) std::string_view errorCtx)
{ {
Bindings::iterator value = attrSet->find(attrSym); auto value = attrSet->find(attrSym);
if (value == attrSet->end()) { if (value == attrSet->end()) {
state.error<TypeError>("attribute '%s' missing", state.symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow(); 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"); state.forceAttrs(*args[0], noPos, "while evaluating the first argument passed to builtins.genericClosure");
/* Get the start set. */ /* 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"); 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. */ /* 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"); 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 /* 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"); 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); state.forceValue(*key->value, noPos);
if (!doneKeys.insert(key->value).second) continue; if (!doneKeys.insert(key->value).second) continue;
@ -1043,7 +1057,11 @@ static void prim_second(EvalState & state, const PosIdx pos, Value * * args, Val
* Derivations * 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 /* Construct (as a unobservable side effect) a Nix derivation
expression that performs the derivation described by the argument 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"); 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). */ /* 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; std::string drvName;
try { try {
@ -1098,8 +1116,11 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
} }
} }
static void derivationStrictInternal(EvalState & state, const std::string & static void derivationStrictInternal(
drvName, Bindings * attrs, Value & v) EvalState & state,
const std::string & drvName,
const Bindings * attrs,
Value & v)
{ {
/* Check whether attributes should be passed as a JSON file. */ /* Check whether attributes should be passed as a JSON file. */
using nlohmann::json; using nlohmann::json;
@ -1139,18 +1160,20 @@ drvName, Bindings * attrs, Value & v)
vomit("processing attribute '%1%'", key); vomit("processing attribute '%1%'", key);
auto handleHashMode = [&](const std::string_view s) { auto handleHashMode = [&](const std::string_view s) {
if (s == "recursive") ingestionMethod = FileIngestionMethod::Recursive; if (s == "recursive") {
else if (s == "flat") ingestionMethod = FileIngestionMethod::Flat; // back compat, new name is "nar"
else if (s == "git") { ingestionMethod = FileIngestionMethod::Recursive;
experimentalFeatureSettings.require(Xp::GitHashing); } else try {
ingestionMethod = FileIngestionMethod::Git; ingestionMethod = ContentAddressMethod::parse(s);
} else if (s == "text") { } catch (UsageError &) {
experimentalFeatureSettings.require(Xp::DynamicDerivations);
ingestionMethod = TextIngestionMethod {};
} else
state.error<EvalError>( state.error<EvalError>(
"invalid value '%s' for 'outputHashMode' attribute", s "invalid value '%s' for 'outputHashMode' attribute", s
).atPos(v).debugThrow(); ).atPos(v).debugThrow();
}
if (ingestionMethod == TextIngestionMethod {})
experimentalFeatureSettings.require(Xp::DynamicDerivations);
if (ingestionMethod == FileIngestionMethod::Git)
experimentalFeatureSettings.require(Xp::GitHashing);
}; };
auto handleOutputs = [&](const Strings & ss) { 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"); state.forceAttrs(*v2, pos, "while evaluating an element of the list passed to builtins.findFile");
std::string prefix; std::string prefix;
Bindings::iterator i = v2->attrs->find(state.sPrefix); auto i = v2->attrs()->find(state.sPrefix);
if (i != v2->attrs->end()) 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"); 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; NixStringContext context;
auto path = state.coerceToString(pos, *i->value, 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`. *`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. This primop can be chained arbitrarily deeply.
For instance, For instance,
```nix ```nix
builtins.outputOf builtins.outputOf
(builtins.outputOf myDrv "out") (builtins.outputOf myDrv "out")
"out" "out"
``` ```
will return a placeholder for the output of the output of `myDrv`. 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. 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'"); 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]; auto n = state.symbols[attr.name];
if (n == "path") if (n == "path")
path.emplace(state.coerceToPath(attr.pos, *attr.value, context, "while evaluating the 'path' attribute passed to 'builtins.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"); 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]); (list[n] = state.allocValue())->mkString(state.symbols[i.name]);
std::sort(list.begin(), list.end(), 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"); 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; list[n] = (Value *) &i;
std::sort(list.begin(), list.end(), 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"); 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"); state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.getAttr");
Bindings::iterator i = getAttr( auto i = getAttr(
state, state,
state.symbols.create(attr), state.symbols.create(attr),
args[1]->attrs, args[1]->attrs(),
"in the attribute set under consideration" "in the attribute set under consideration"
); );
// !!! add to stack trace? // !!! 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"); 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"); 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)); auto i = args[1]->attrs()->find(state.symbols.create(attr));
if (i == args[1]->attrs->end()) if (i == args[1]->attrs()->end())
v.mkNull(); v.mkNull();
else else
state.mkPos(v, i->pos); state.mkPos(v, i->pos);
@ -2569,13 +2594,13 @@ static struct LazyPosAcessors {
PrimOp primop_lineOfPos{ PrimOp primop_lineOfPos{
.arity = 1, .arity = 1,
.fun = [] (EvalState & state, PosIdx pos, Value * * args, Value & v) { .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{ PrimOp primop_columnOfPos{
.arity = 1, .arity = 1,
.fun = [] (EvalState & state, PosIdx pos, Value * * args, Value & v) { .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"); 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"); 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({ 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 /* 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 to sort v.attrs because it's a subset of an already sorted
vector. */ vector. */
auto attrs = state.buildBindings(args[0]->attrs->size()); auto attrs = state.buildBindings(args[0]->attrs()->size());
std::set_difference( std::set_difference(
args[0]->attrs->begin(), args[0]->attrs->end(), args[0]->attrs()->begin(), args[0]->attrs()->end(),
names.begin(), names.end(), names.begin(), names.end(),
std::back_inserter(attrs)); std::back_inserter(attrs));
v.mkAttrs(attrs.alreadySorted()); 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()) { for (auto v2 : args[0]->listItems()) {
state.forceAttrs(*v2, pos, "while evaluating an element of the list passed to builtins.listToAttrs"); 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 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); auto sym = state.symbols.create(name);
if (seen.insert(sym).second) { 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); 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[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"); state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.intersectAttrs");
Bindings &left = *args[0]->attrs; auto & left = *args[0]->attrs();
Bindings &right = *args[1]->attrs; auto & right = *args[1]->attrs();
auto attrs = state.buildBindings(std::min(left.size(), right.size())); 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()) { if (left.size() < right.size()) {
for (auto & l : left) { for (auto & l : left) {
Bindings::iterator r = right.find(l.name); auto r = right.find(l.name);
if (r != right.end()) if (r != right.end())
attrs.insert(*r); attrs.insert(*r);
} }
} }
else { else {
for (auto & r : right) { for (auto & r : right) {
Bindings::iterator l = left.find(r.name); auto l = left.find(r.name);
if (l != left.end()) if (l != left.end())
attrs.insert(r); 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()) { for (auto v2 : args[1]->listItems()) {
state.forceAttrs(*v2, pos, "while evaluating an element in the list passed as second argument to builtins.catAttrs"); 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 (auto i = v2->attrs()->get(attrName))
if (i != v2->attrs->end())
res[found++] = i->value; res[found++] = i->value;
} }
@ -2867,13 +2891,13 @@ static void prim_functionArgs(EvalState & state, const PosIdx pos, Value * * arg
if (!args[0]->isLambda()) if (!args[0]->isLambda())
state.error<TypeError>("'functionArgs' requires a function").atPos(pos).debugThrow(); 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); v.mkAttrs(&state.emptyBindings);
return; return;
} }
auto attrs = state.buildBindings(args[0]->lambda.fun->formals->formals.size()); auto attrs = state.buildBindings(args[0]->payload.lambda.fun->formals->formals.size());
for (auto & i : args[0]->lambda.fun->formals->formals) for (auto & i : args[0]->payload.lambda.fun->formals->formals)
attrs.insert(i.name, state.getBool(i.def), i.pos); attrs.insert(i.name, state.getBool(i.def), i.pos);
v.mkAttrs(attrs); 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"); 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 * vName = state.allocValue();
Value * vFun2 = state.allocValue(); Value * vFun2 = state.allocValue();
vName->mkString(state.symbols[i.name]); 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) { for (auto & vElem : listItems) {
state.forceAttrs(*vElem, noPos, "while evaluating a value of the list passed as second argument to builtins.zipAttrsWith"); 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++; 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)); elem.list.emplace(state.buildList(elem.size));
for (auto & vElem : listItems) { for (auto & vElem : listItems) {
for (auto & attr : *vElem->attrs) { for (auto & attr : *vElem->attrs()) {
auto & item = attrsSeen.at(attr.name); auto & item = attrsSeen.at(attr.name);
(*item.list)[item.pos++] = attr.value; (*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) { auto comparator = [&](Value * a, Value * b) {
/* Optimization: if the comparator is lessThan, bypass /* Optimization: if the comparator is lessThan, bypass
callFunction. */ callFunction. */
/* TODO: (layus) this is absurd. An optimisation like this
should be outside the lambda creation */
if (args[0]->isPrimOp()) { 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) if (ptr && *ptr == prim_lessThan)
return CompareValues(state, noPos, "while evaluating the ordering function passed to builtins.sort")(a, b); 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) 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"); 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'"); 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; std::optional<HashAlgorithm> ha = std::nullopt;
if (iteratorHashAlgo != inputAttrs->end()) { if (iteratorHashAlgo)
ha = parseHashAlgo(state.forceStringNoCtx(*iteratorHashAlgo->value, pos, "while evaluating the attribute 'hashAlgo'")); 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'")); 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)); 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 // Miscellaneous
if (evalSettings.enableNativeCode) { if (evalSettings.enableNativeCode) {
addPrimOp({ addPrimOp({
@ -4600,6 +4622,7 @@ void EvalState::createBaseEnv()
.fun = prim_exec, .fun = prim_exec,
}); });
} }
#endif
addPrimOp({ addPrimOp({
.name = "__traceVerbose", .name = "__traceVerbose",
@ -4663,7 +4686,7 @@ void EvalState::createBaseEnv()
/* Now that we've added all primops, sort the `builtins' set, /* Now that we've added all primops, sort the `builtins' set,
because attribute lookups expect it to be sorted. */ because attribute lookups expect it to be sorted. */
baseEnv.values[0]->attrs->sort(); baseEnv.values[0]->payload.attrs->sort();
staticBaseEnv->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 sPath = state.symbols.create("path");
auto sAllOutputs = state.symbols.create("allOutputs"); 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]; const auto & name = state.symbols[i.name];
if (!state.store->isStorePath(name)) if (!state.store->isStorePath(name))
state.error<EvalError>( state.error<EvalError>(
@ -269,17 +269,16 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
if (!settings.readOnlyMode) if (!settings.readOnlyMode)
state.store->ensurePath(namePath); state.store->ensurePath(namePath);
state.forceAttrs(*i.value, i.pos, "while evaluating the value of a string context"); 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 (auto attr = i.value->attrs()->get(sPath)) {
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `path` attribute of a string context")) if (state.forceBool(*attr->value, attr->pos, "while evaluating the `path` attribute of a string context"))
context.emplace(NixStringContextElem::Opaque { context.emplace(NixStringContextElem::Opaque {
.path = namePath, .path = namePath,
}); });
} }
iter = i.value->attrs->find(sAllOutputs); if (auto attr = i.value->attrs()->get(sAllOutputs)) {
if (iter != i.value->attrs->end()) { if (state.forceBool(*attr->value, attr->pos, "while evaluating the `allOutputs` attribute of a string context")) {
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `allOutputs` attribute of a string context")) {
if (!isDerivation(name)) { if (!isDerivation(name)) {
state.error<EvalError>( state.error<EvalError>(
"tried to add all-outputs context of %s, which is not a derivation, to a string", "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 (auto attr = i.value->attrs()->get(state.sOutputs)) {
if (iter != i.value->attrs->end()) { state.forceList(*attr->value, attr->pos, "while evaluating the `outputs` attribute of a string context");
state.forceList(*iter->value, iter->pos, "while evaluating the `outputs` attribute of a string context"); if (attr->value->listSize() && !isDerivation(name)) {
if (iter->value->listSize() && !isDerivation(name)) {
state.error<EvalError>( state.error<EvalError>(
"tried to add derivation output context of %s, which is not a derivation, to a string", "tried to add derivation output context of %s, which is not a derivation, to a string",
name name
).atPos(i.pos).debugThrow(); ).atPos(i.pos).debugThrow();
} }
for (auto elem : iter->value->listItems()) { for (auto elem : attr->value->listItems()) {
auto outputName = state.forceStringNoCtx(*elem, iter->pos, "while evaluating an output name within a string context"); auto outputName = state.forceStringNoCtx(*elem, attr->pos, "while evaluating an output name within a string context");
context.emplace(NixStringContextElem::Built { context.emplace(NixStringContextElem::Built {
.drvPath = makeConstantStorePathRef(namePath), .drvPath = makeConstantStorePathRef(namePath),
.output = std::string { outputName }, .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<StorePathOrGap> toPath;
std::optional<bool> inputAddressedMaybe; std::optional<bool> inputAddressedMaybe;
for (auto & attr : *args[0]->attrs) { for (auto & attr : *args[0]->attrs()) {
const auto & attrName = state.symbols[attr.name]; const auto & attrName = state.symbols[attr.name];
auto attrHint = [&]() -> std::string { auto attrHint = [&]() -> std::string {
return "while evaluating the '" + attrName + "' attribute passed to builtins.fetchClosure"; 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) { 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]); std::string_view n(state.symbols[attr.name]);
if (n == "url") if (n == "url")
url = state.coerceToString(attr.pos, *attr.value, context, url = state.coerceToString(attr.pos, *attr.value, context,

View file

@ -97,7 +97,7 @@ static void fetchTree(
fetchers::Attrs attrs; fetchers::Attrs attrs;
if (auto aType = args[0]->attrs->get(state.sType)) { if (auto aType = args[0]->attrs()->get(state.sType)) {
if (type) if (type)
state.error<EvalError>( state.error<EvalError>(
"unexpected attribute 'type'" "unexpected attribute 'type'"
@ -110,7 +110,7 @@ static void fetchTree(
attrs.emplace("type", type.value()); attrs.emplace("type", type.value());
for (auto & attr : *args[0]->attrs) { for (auto & attr : *args[0]->attrs()) {
if (attr.name == state.sType) continue; if (attr.name == state.sType) continue;
state.forceValue(*attr.value, attr.pos); state.forceValue(*attr.value, attr.pos);
if (attr.value->type() == nPath || attr.value->type() == nString) { if (attr.value->type() == nPath || attr.value->type() == nString) {
@ -121,9 +121,9 @@ static void fetchTree(
: s); : s);
} }
else if (attr.value->type() == nBool) 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) 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") { else if (state.symbols[attr.name] == "publicKeys") {
experimentalFeatureSettings.require(Xp::VerifiedFetches); experimentalFeatureSettings.require(Xp::VerifiedFetches);
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, context).dump()); 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) { 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]); std::string_view n(state.symbols[attr.name]);
if (n == "url") if (n == "url")
url = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the url we should fetch"); 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 = auto storePath =
unpack unpack
? fetchToStore(*state.store, fetchers::downloadTarball(*url).accessor, FetchMode::Copy, name) ? 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) { if (expectedHash) {
auto hash = unpack auto hash = unpack
@ -650,12 +650,14 @@ static RegisterPrimOp primop_fetchGit({
The public keys against which `rev` is verified if `verifyCommit` is enabled. 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: Must be given as a list of attribute sets with the following form:
```nix ```nix
{ {
key = "<public key>"; key = "<public key>";
type = "<key type>"; # optional, default: "ssh-ed25519" type = "<key type>"; # optional, default: "ssh-ed25519"
} }
``` ```
Requires the [`verified-fetches` experimental feature](@docroot@/contributing/experimental-features.md#xp-feature-verified-fetches). 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()) { switch (v.type()) {
case nInt: case nInt:
str << v.integer; str << v.integer();
break; break;
case nBool: case nBool:
printLiteralBool(str, v.boolean); printLiteralBool(str, v.boolean());
break; break;
case nString: case nString:
printLiteralString(str, v.string_view()); printLiteralString(str, v.string_view());
@ -36,11 +36,11 @@ void printAmbiguous(
str << "null"; str << "null";
break; break;
case nAttrs: { case nAttrs: {
if (seen && !v.attrs->empty() && !seen->insert(v.attrs).second) if (seen && !v.attrs()->empty() && !seen->insert(v.attrs()).second)
str << "«repeated»"; str << "«repeated»";
else { else {
str << "{ "; str << "{ ";
for (auto & i : v.attrs->lexicographicOrder(symbols)) { for (auto & i : v.attrs()->lexicographicOrder(symbols)) {
str << symbols[i->name] << " = "; str << symbols[i->name] << " = ";
printAmbiguous(*i->value, symbols, str, seen, depth - 1); printAmbiguous(*i->value, symbols, str, seen, depth - 1);
str << "; "; str << "; ";
@ -87,10 +87,10 @@ void printAmbiguous(
} }
break; break;
case nExternal: case nExternal:
str << *v.external; str << *v.external();
break; break;
case nFloat: case nFloat:
str << v.fpoint; str << v.fpoint();
break; break;
default: default:
printError("Nix evaluator internal error: printAmbiguous: invalid value type"); printError("Nix evaluator internal error: printAmbiguous: invalid value type");

View file

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

View file

@ -8,6 +8,9 @@
namespace nix { 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 * A "search path" is a list of ways look for something, used with
* `builtins.findFile` and `< >` lookup expressions. * `builtins.findFile` and `< >` lookup expressions.

View file

@ -22,11 +22,11 @@ json printValueAsJSON(EvalState & state, bool strict,
switch (v.type()) { switch (v.type()) {
case nInt: case nInt:
out = v.integer; out = v.integer();
break; break;
case nBool: case nBool:
out = v.boolean; out = v.boolean();
break; break;
case nString: case nString:
@ -52,24 +52,20 @@ json printValueAsJSON(EvalState & state, bool strict,
out = *maybeString; out = *maybeString;
break; break;
} }
auto i = v.attrs->find(state.sOutPath); if (auto i = v.attrs()->get(state.sOutPath))
if (i == v.attrs->end()) { return printValueAsJSON(state, strict, *i->value, i->pos, context, copyToStore);
else {
out = json::object(); out = json::object();
StringSet names; for (auto & a : v.attrs()->lexicographicOrder(state.symbols)) {
for (auto & j : *v.attrs)
names.emplace(state.symbols[j.name]);
for (auto & j : names) {
Attr & a(*v.attrs->find(state.symbols.create(j)));
try { 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) { } catch (Error & e) {
e.addTrace(state.positions[a.pos], e.addTrace(state.positions[a->pos],
HintFmt("while evaluating attribute '%1%'", j)); HintFmt("while evaluating attribute '%1%'", state.symbols[a->name]));
throw; throw;
} }
} }
} else }
return printValueAsJSON(state, strict, *i->value, i->pos, context, copyToStore);
break; break;
} }
@ -90,11 +86,11 @@ json printValueAsJSON(EvalState & state, bool strict,
} }
case nExternal: case nExternal:
return v.external->printValueAsJSON(state, strict, context, copyToStore); return v.external()->printValueAsJSON(state, strict, context, copyToStore);
break; break;
case nFloat: case nFloat:
out = v.fpoint; out = v.fpoint();
break; break;
case nThunk: 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, 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; StringSet names;
for (auto & i : attrs) for (auto & a : attrs.lexicographicOrder(state.symbols)) {
names.emplace(state.symbols[i.name]);
for (auto & i : names) {
Attr & a(*attrs.find(state.symbols.create(i)));
XMLAttrs xmlAttrs; XMLAttrs xmlAttrs;
xmlAttrs["name"] = i; xmlAttrs["name"] = state.symbols[a->name];
if (location && a.pos) posToXML(state, xmlAttrs, state.positions[a.pos]); if (location && a->pos) posToXML(state, xmlAttrs, state.positions[a->pos]);
XMLOpenElement _(doc, "attr", xmlAttrs); XMLOpenElement _(doc, "attr", xmlAttrs);
printValueAsXML(state, strict, location, 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()) { switch (v.type()) {
case nInt: case nInt:
doc.writeEmptyElement("int", singletonAttrs("value", fmt("%1%", v.integer))); doc.writeEmptyElement("int", singletonAttrs("value", fmt("%1%", v.integer())));
break; break;
case nBool: case nBool:
doc.writeEmptyElement("bool", singletonAttrs("value", v.boolean ? "true" : "false")); doc.writeEmptyElement("bool", singletonAttrs("value", v.boolean() ? "true" : "false"));
break; break;
case nString: case nString:
@ -89,18 +84,14 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
if (state.isDerivation(v)) { if (state.isDerivation(v)) {
XMLAttrs xmlAttrs; XMLAttrs xmlAttrs;
Bindings::iterator a = v.attrs->find(state.symbols.create("derivation"));
Path drvPath; Path drvPath;
a = v.attrs->find(state.sDrvPath); if (auto a = v.attrs()->get(state.sDrvPath)) {
if (a != v.attrs->end()) {
if (strict) state.forceValue(*a->value, a->pos); if (strict) state.forceValue(*a->value, a->pos);
if (a->value->type() == nString) if (a->value->type() == nString)
xmlAttrs["drvPath"] = drvPath = a->value->c_str(); xmlAttrs["drvPath"] = drvPath = a->value->c_str();
} }
a = v.attrs->find(state.sOutPath); if (auto a = v.attrs()->get(state.sOutPath)) {
if (a != v.attrs->end()) {
if (strict) state.forceValue(*a->value, a->pos); if (strict) state.forceValue(*a->value, a->pos);
if (a->value->type() == nString) if (a->value->type() == nString)
xmlAttrs["outPath"] = a->value->c_str(); xmlAttrs["outPath"] = a->value->c_str();
@ -109,14 +100,14 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
XMLOpenElement _(doc, "derivation", xmlAttrs); XMLOpenElement _(doc, "derivation", xmlAttrs);
if (drvPath != "" && drvsSeen.insert(drvPath).second) 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 else
doc.writeEmptyElement("repeated"); doc.writeEmptyElement("repeated");
} }
else { else {
XMLOpenElement _(doc, "attrs"); XMLOpenElement _(doc, "attrs");
showAttrs(state, strict, location, *v.attrs, doc, context, drvsSeen); showAttrs(state, strict, location, *v.attrs(), doc, context, drvsSeen);
} }
break; break;
@ -135,28 +126,28 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
break; break;
} }
XMLAttrs xmlAttrs; 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); XMLOpenElement _(doc, "function", xmlAttrs);
if (v.lambda.fun->hasFormals()) { if (v.payload.lambda.fun->hasFormals()) {
XMLAttrs attrs; XMLAttrs attrs;
if (v.lambda.fun->arg) attrs["name"] = state.symbols[v.lambda.fun->arg]; if (v.payload.lambda.fun->arg) attrs["name"] = state.symbols[v.payload.lambda.fun->arg];
if (v.lambda.fun->formals->ellipsis) attrs["ellipsis"] = "1"; if (v.payload.lambda.fun->formals->ellipsis) attrs["ellipsis"] = "1";
XMLOpenElement _(doc, "attrspat", attrs); 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])); doc.writeEmptyElement("attr", singletonAttrs("name", state.symbols[i.name]));
} else } 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; break;
} }
case nExternal: case nExternal:
v.external->printValueAsXML(state, strict, location, doc, context, drvsSeen, pos); v.external()->printValueAsXML(state, strict, location, doc, context, drvsSeen, pos);
break; break;
case nFloat: case nFloat:
doc.writeEmptyElement("float", singletonAttrs("value", fmt("%1%", v.fpoint))); doc.writeEmptyElement("float", singletonAttrs("value", fmt("%1%", v.fpoint())));
break; break;
case nThunk: case nThunk:

View file

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

View file

@ -3,6 +3,7 @@
#include "input-accessor.hh" #include "input-accessor.hh"
#include "source-path.hh" #include "source-path.hh"
#include "fetch-to-store.hh" #include "fetch-to-store.hh"
#include "json-utils.hh"
#include <nlohmann/json.hpp> #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 "types.hh"
#include "hash.hh" #include "hash.hh"
#include "canon-path.hh" #include "canon-path.hh"
#include "json-impls.hh"
#include "attrs.hh" #include "attrs.hh"
#include "url.hh" #include "url.hh"
@ -230,8 +231,9 @@ struct PublicKey
std::string type = "ssh-ed25519"; std::string type = "ssh-ed25519";
std::string key; std::string key;
}; };
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(PublicKey, type, key)
std::string publicKeys_to_string(const std::vector<PublicKey>&); 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) 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) void FilteringInputAccessor::checkAccess(const CanonPath & path)

View file

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

View file

@ -24,7 +24,10 @@ ref<InputAccessor> makeStorePathAccessor(
const StorePath & storePath) const StorePath & storePath)
{ {
// FIXME: should use `store->getFSAccessor()` // 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) SourcePath getUnfilteredRootPath(CanonPath path)

View file

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

View file

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

View file

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

View file

@ -9,6 +9,8 @@ struct MountedInputAccessor : InputAccessor
MountedInputAccessor(std::map<CanonPath, ref<InputAccessor>> _mounts) MountedInputAccessor(std::map<CanonPath, ref<InputAccessor>> _mounts)
: mounts(std::move(_mounts)) : mounts(std::move(_mounts))
{ {
displayPrefix.clear();
// Currently we require a root filesystem. This could be relaxed. // Currently we require a root filesystem. This could be relaxed.
assert(mounts.contains(CanonPath::root)); assert(mounts.contains(CanonPath::root));
@ -48,7 +50,7 @@ struct MountedInputAccessor : InputAccessor
std::string showPath(const CanonPath & path) override std::string showPath(const CanonPath & path) override
{ {
auto [accessor, subpath] = resolve(path); auto [accessor, subpath] = resolve(path);
return accessor->showPath(subpath); return displayPrefix + accessor->showPath(subpath) + displaySuffix;
} }
std::pair<ref<InputAccessor>, CanonPath> resolve(CanonPath path) 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, "/")) { 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>()) if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>())
store2->addPermRoot(storePath, getCacheDir() + "/nix/flake-registry.json"); store2->addPermRoot(storePath, getCacheDir() + "/nix/flake-registry.json");
path = store->toRealPath(storePath); path = store->toRealPath(storePath);

View file

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

View file

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

View file

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

View file

@ -352,7 +352,11 @@ struct MercurialInputScheme : InputScheme
auto storePath = fetchToStore(store, input); 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 bool isLocked(const Input & input) const override

View file

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

View file

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

View file

@ -56,7 +56,7 @@ void nix_store_free(Store * store)
delete 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) if (context)
context->last_err_code = NIX_OK; 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 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) if (context)
context->last_err_code = NIX_OK; context->last_err_code = NIX_OK;
@ -128,7 +129,18 @@ nix_err nix_store_realise(
NIXC_CATCH_ERRS 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) void nix_store_path_free(StorePath * sp)
{ {
delete 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 * @see nix_get_string_callback
* @return error code, NIX_OK on success. * @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* // 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); 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 /** @brief Deallocate a StorePath
* *
* Does not fail. * 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 * @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[out] context Optional, stores error information
* @param[in] store Nix Store reference * @param[in] store Nix Store reference
@ -136,7 +156,8 @@ nix_err nix_store_realise(
* @see nix_get_string_callback * @see nix_get_string_callback
* @return error code, NIX_OK on success. * @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 // cffi end
#ifdef __cplusplus #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); throw Error("collision between '%1%' and non-directory '%2%'", srcFile, target);
if (unlink(dstFile.c_str()) == -1) if (unlink(dstFile.c_str()) == -1)
throw SysError("unlinking '%1%'", dstFile); 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); throw SysError("creating directory '%1%'", dstFile);
createLinks(state, target, dstFile, state.priorities[dstFile]); createLinks(state, target, dstFile, state.priorities[dstFile]);
createLinks(state, srcFile, dstFile, priority); createLinks(state, srcFile, dstFile, priority);

View file

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

View file

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

View file

@ -9,11 +9,14 @@
#include <map> #include <map>
#include <mutex> #include <mutex>
#include <thread> #include <thread>
#include <dlfcn.h>
#include <sys/utsname.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#ifndef _WIN32
# include <dlfcn.h>
# include <sys/utsname.h>
#endif
#ifdef __GLIBC__ #ifdef __GLIBC__
# include <gnu/lib-names.h> # include <gnu/lib-names.h>
# include <nss.h> # include <nss.h>
@ -46,7 +49,13 @@ static GlobalConfig::Register rSettings(&settings);
Settings::Settings() Settings::Settings()
: nixPrefix(NIX_PREFIX) : 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))) , nixDataDir(canonPath(getEnvNonEmpty("NIX_DATA_DIR").value_or(NIX_DATA_DIR)))
, nixLogDir(canonPath(getEnvNonEmpty("NIX_LOG_DIR").value_or(NIX_LOG_DIR))) , nixLogDir(canonPath(getEnvNonEmpty("NIX_LOG_DIR").value_or(NIX_LOG_DIR)))
, nixStateDir(canonPath(getEnvNonEmpty("NIX_STATE_DIR").value_or(NIX_STATE_DIR))) , nixStateDir(canonPath(getEnvNonEmpty("NIX_STATE_DIR").value_or(NIX_STATE_DIR)))
@ -56,7 +65,9 @@ Settings::Settings()
, nixManDir(canonPath(NIX_MAN_DIR)) , nixManDir(canonPath(NIX_MAN_DIR))
, nixDaemonSocketFile(canonPath(getEnvNonEmpty("NIX_DAEMON_SOCKET_PATH").value_or(nixStateDir + DEFAULT_SOCKET_PATH))) , nixDaemonSocketFile(canonPath(getEnvNonEmpty("NIX_DAEMON_SOCKET_PATH").value_or(nixStateDir + DEFAULT_SOCKET_PATH)))
{ {
#ifndef _WIN32
buildUsersGroup = isRootUser() ? "nixbld" : ""; buildUsersGroup = isRootUser() ? "nixbld" : "";
#endif
allowSymlinkedStore = getEnv("NIX_IGNORE_SYMLINK_STORE") == "1"; allowSymlinkedStore = getEnv("NIX_IGNORE_SYMLINK_STORE") == "1";
auto sslOverride = getEnv("NIX_SSL_CERT_FILE").value_or(getEnv("SSL_CERT_FILE").value_or("")); 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() bool Settings::isWSL1()
{ {
#if __linux__
struct utsname utsbuf; struct utsname utsbuf;
uname(&utsbuf); uname(&utsbuf);
// WSL1 uses -Microsoft suffix // WSL1 uses -Microsoft suffix
// WSL2 uses -microsoft-standard suffix // WSL2 uses -microsoft-standard suffix
return hasSuffix(utsbuf.release, "-Microsoft"); return hasSuffix(utsbuf.release, "-Microsoft");
#else
return false;
#endif
} }
Path Settings::getDefaultSSLCertFile() Path Settings::getDefaultSSLCertFile()
@ -341,6 +356,7 @@ void initPlugins()
for (const auto & file : pluginFiles) { for (const auto & file : pluginFiles) {
/* handle is purposefully leaked as there may be state in the /* handle is purposefully leaked as there may be state in the
DSO needed by the action of the plugin. */ DSO needed by the action of the plugin. */
#ifndef _WIN32 // TODO implement via DLL loading on Windows
void *handle = void *handle =
dlopen(file.c_str(), RTLD_LAZY | RTLD_LOCAL); dlopen(file.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!handle) if (!handle)
@ -351,6 +367,9 @@ void initPlugins()
void (*nix_plugin_entry)() = (void (*)())dlsym(handle, "nix_plugin_entry"); void (*nix_plugin_entry)() = (void (*)())dlsym(handle, "nix_plugin_entry");
if (nix_plugin_entry) if (nix_plugin_entry)
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", Setting<bool> sandboxFallback{this, true, "sandbox-fallback",
"Whether to disable sandboxing when the kernel doesn't allow it."}; "Whether to disable sandboxing when the kernel doesn't allow it."};
#ifndef _WIN32
Setting<bool> requireDropSupplementaryGroups{this, isRootUser(), "require-drop-supplementary-groups", Setting<bool> requireDropSupplementaryGroups{this, isRootUser(), "require-drop-supplementary-groups",
R"( R"(
Following the principle of least privilege, Following the principle of least privilege,
@ -683,6 +684,7 @@ public:
(since `root` usually has permissions to call setgroups) (since `root` usually has permissions to call setgroups)
and `false` otherwise. and `false` otherwise.
)"}; )"};
#endif
#if __linux__ #if __linux__
Setting<std::string> sandboxShmSize{ 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 "personality.hh"
#include "globals.hh" #include "globals.hh"
#if __linux__
#include <sys/utsname.h> #include <sys/utsname.h>
#include <sys/personality.h> #include <sys/personality.h>
#endif
#include <cstring> #include <cstring>
namespace nix { namespace nix::linux {
void setPersonality(std::string_view system) void setPersonality(std::string_view system)
{ {
#if __linux__
/* Change the personality to 32-bit if we're doing an /* Change the personality to 32-bit if we're doing an
i686-linux build on an x86_64-linux machine. */ i686-linux build on an x86_64-linux machine. */
struct utsname utsbuf; struct utsname utsbuf;
@ -39,7 +36,6 @@ void setPersonality(std::string_view system)
determinism. */ determinism. */
int cur = personality(0xffffffff); int cur = personality(0xffffffff);
if (cur != -1) personality(cur | ADDR_NO_RANDOMIZE); if (cur != -1) personality(cur | ADDR_NO_RANDOMIZE);
#endif
} }
} }

View file

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

View file

@ -33,6 +33,10 @@ struct LocalStoreAccessor : PosixSourceAccessor
std::optional<Stat> maybeLstat(const CanonPath & path) override 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)); return PosixSourceAccessor::maybeLstat(toRealPath(path));
} }

View file

@ -4,9 +4,15 @@ libstore_NAME = libnixstore
libstore_DIR := $(d) 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 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 endif
libstore_LIBS = libutil libstore_LIBS = libutil
@ -36,25 +42,42 @@ INCLUDE_libstore := -I $(d) -I $(d)/build
ifdef HOST_UNIX ifdef HOST_UNIX
INCLUDE_libstore += -I $(d)/unix INCLUDE_libstore += -I $(d)/unix
endif 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 += \ libstore_CXXFLAGS += \
$(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libstore) \ $(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libstore) \
-DNIX_PREFIX=\"$(prefix)\" \ -DNIX_PREFIX=\"$(NIX_ROOT)$(prefix)\" \
-DNIX_STORE_DIR=\"$(storedir)\" \ -DNIX_STORE_DIR=\"$(storedir)\" \
-DNIX_DATA_DIR=\"$(datadir)\" \ -DNIX_DATA_DIR=\"$(NIX_ROOT)$(datadir)\" \
-DNIX_STATE_DIR=\"$(localstatedir)/nix\" \ -DNIX_STATE_DIR=\"$(NIX_ROOT)$(localstatedir)/nix\" \
-DNIX_LOG_DIR=\"$(localstatedir)/log/nix\" \ -DNIX_LOG_DIR=\"$(NIX_ROOT)$(localstatedir)/log/nix\" \
-DNIX_CONF_DIR=\"$(sysconfdir)/nix\" \ -DNIX_CONF_DIR=\"$(NIX_ROOT)$(sysconfdir)/nix\" \
-DNIX_BIN_DIR=\"$(bindir)\" \ -DNIX_BIN_DIR=\"$(NIX_ROOT)$(bindir)\" \
-DNIX_MAN_DIR=\"$(mandir)\" \ -DNIX_MAN_DIR=\"$(NIX_ROOT)$(mandir)\" \
-DLSOF=\"$(lsof)\" -DLSOF=\"$(NIX_ROOT)$(lsof)\"
ifeq ($(embedded_sandbox_shell),yes) ifeq ($(embedded_sandbox_shell),yes)
libstore_CXXFLAGS += -DSANDBOX_SHELL=\"__embedded_sandbox_shell__\" 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 $(trace-gen) hexdump -v -e '1/1 "0x%x," "\n"' < $< > $@.tmp
@mv $@.tmp $@ @mv $@.tmp $@
else else
@ -63,11 +86,11 @@ else
endif endif
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)) $(eval $(call install-file-in, $(buildprefix)$(d)/nix-store.pc, $(libdir)/pkgconfig, 0644))

View file

@ -1,7 +1,6 @@
#include "derivations.hh" #include "derivations.hh"
#include "parsed-derivations.hh" #include "parsed-derivations.hh"
#include "globals.hh" #include "globals.hh"
#include "local-store.hh"
#include "store-api.hh" #include "store-api.hh"
#include "thread-pool.hh" #include "thread-pool.hh"
#include "realisation.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) { for (auto i = e->begin(); i != e->end(); ++i) {
StorePathSet storePaths; StorePathSet storePaths;
for (auto & p : *i) 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, json[i.key()] = pathInfoToJSON(store,
store.exportReferences(storePaths, inputPaths)); 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 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) if (dirOf(p) != storeDir)
throw BadStorePath("path '%s' is not in the Nix store", p); throw BadStorePath("path '%s' is not in the Nix store", p);
return StorePath(baseNameOf(p)); return StorePath(baseNameOf(p));

View file

@ -6,69 +6,9 @@
#include <cerrno> #include <cerrno>
#include <cstdlib> #include <cstdlib>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
namespace nix { 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() PathLocks::PathLocks()
: deletePaths(false) : 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() PathLocks::~PathLocks()
{ {
try { 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) void PathLocks::setDeletion(bool deletePaths)
{ {
this->deletePaths = 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 { 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 class PathLocks
{ {
private: private:
@ -40,18 +24,6 @@ public:
void setDeletion(bool deletePaths); 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 "types.hh"
#include "pathlocks.hh" #include "pathlocks.hh"
#include <optional>
#include <time.h> #include <time.h>

View file

@ -71,11 +71,15 @@ std::pair<ref<SourceAccessor>, CanonPath> RemoteFSAccessor::fetch(const CanonPat
auto narAccessor = makeLazyNarAccessor(listing, auto narAccessor = makeLazyNarAccessor(listing,
[cacheFile](uint64_t offset, uint64_t length) { [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) if (!fd)
throw SysError("opening NAR cache file '%s'", cacheFile); 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); throw SysError("seeking in '%s'", cacheFile);
std::string buf(length, 0); std::string buf(length, 0);

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