Compare commits

..
Author SHA1 Message Date
Jade Lovelace 5bf891b15a release: 2.90.0 "Vanilla Ice Cream"
Release produced with releng/create_release.xsh

Change-Id: Ice53891cdeafff04bd6537be66e8cd6b7dd9678c
2024-07-10 08:40:42 -07:00
Raito Bezariusandjade f72153a510 doc/release-notes: add date for major release
Change-Id: I93aab93c069bb3989c3f8d17e0862899e6f76865
Signed-off-by: Raito Bezarius <raito@lix.systems>
2024-07-10 15:35:00 +00:00
Raito Bezarius 8da08ab551 version: finalize 2.90.0
Tested by Raito, pennae and time. It looks in good shape.

Change-Id: I32492557361888c28fe40553c699833876a1deab
Signed-off-by: Raito Bezarius <raito@lix.systems>
2024-07-10 15:32:19 +02:00
Jade Lovelace 98d0249d5c releng: fix broken manifest from 2.90-rc1
Well that is embarrassing. I think the proper thing is to just quickly
ship a -rc2, so I will open a backport of this.

    $ curl https://releases.lix.systems/manifest.nix
    # This file was generated by releng/create_release.xsh in Lix
    {
        aarch64-linux = "/nix/store/mrbknq000af7iaqhk53bnpk1fvfrc1xp-lix-2.90.0-rc1";
      aarch64-darwin = "/nix/store/z1bdccwsk34iv491aygh0mm1lgpf7yy1-lix-2.90.0-rc1";
      x86_64-darwin = "/nix/store/xqvfpdhzck44v6kyhgi9f8v0xybksb6a-lix-2.90.0-rc1";
      x86_64-linux = "/nix/store/h2ml0nx4477r84y82jgm8y80jpr72gqw-lix-release-tarballs";
    }

Change-Id: I9cf007c850c2faf995a3a9d92457517b8501d1a1
2024-06-16 19:06:45 -07:00
191 changed files with 3568 additions and 5904 deletions
+6 -2
View File
@@ -1,5 +1,9 @@
# shellcheck shell=bash
source_env_if_exists .envrc.local
# Use native-clangStdenvPackages to get clangd by default.
use flake ".#${LIX_SHELL_VARIANT:-native-clangStdenvPackages}" "${LIX_SHELL_EXTRA_ARGS[@]}"
# TODO: `use flake .#native-clangStdenvPackages` on macOS?
use flake ".#${LIX_SHELL_VARIANT:-default}" "${LIX_SHELL_EXTRA_ARGS[@]}"
export MAKEFLAGS="$MAKEFLAGS -e"
if [[ -n "$NIX_BUILD_CORES" ]]; then
export MAKEFLAGS="$MAKEFLAGS -j $NIX_BUILD_CORES"
fi
export GTEST_BRIEF=1
-1
View File
@@ -1 +0,0 @@
This is a file used by the dev shell shellHook in package.nix to check that this is actually a Lix repo before installing git hooks. Its contents have no meaning.
+54
View File
@@ -0,0 +1,54 @@
diff --git a/pthread_stop_world.c b/pthread_stop_world.c
index 2b45489..0e6d8ef 100644
--- a/pthread_stop_world.c
+++ b/pthread_stop_world.c
@@ -776,6 +776,8 @@ STATIC void GC_restart_handler(int sig)
/* world is stopped. Should not fail if it isn't. */
GC_INNER void GC_push_all_stacks(void)
{
+ size_t stack_limit;
+ pthread_attr_t pattr;
GC_bool found_me = FALSE;
size_t nthreads = 0;
int i;
@@ -868,6 +870,40 @@ GC_INNER void GC_push_all_stacks(void)
hi = p->altstack + p->altstack_size;
# endif
/* FIXME: Need to scan the normal stack too, but how ? */
+ } else {
+ #ifdef HAVE_PTHREAD_ATTR_GET_NP
+ if (pthread_attr_init(&pattr) != 0) {
+ ABORT("GC_push_all_stacks: pthread_attr_init failed!");
+ }
+ if (pthread_attr_get_np(p->id, &pattr) != 0) {
+ ABORT("GC_push_all_stacks: pthread_attr_get_np failed!");
+ }
+ #else
+ if (pthread_getattr_np(p->id, &pattr)) {
+ ABORT("GC_push_all_stacks: pthread_getattr_np failed!");
+ }
+ #endif
+ if (pthread_attr_getstacksize(&pattr, &stack_limit)) {
+ ABORT("GC_push_all_stacks: pthread_attr_getstacksize failed!");
+ }
+ if (pthread_attr_destroy(&pattr)) {
+ ABORT("GC_push_all_stacks: pthread_attr_destroy failed!");
+ }
+ // When a thread goes into a coroutine, we lose its original sp until
+ // control flow returns to the thread.
+ // While in the coroutine, the sp points outside the thread stack,
+ // so we can detect this and push the entire thread stack instead,
+ // as an approximation.
+ // We assume that the coroutine has similarly added its entire stack.
+ // This could be made accurate by cooperating with the application
+ // via new functions and/or callbacks.
+ #ifndef STACK_GROWS_UP
+ if (lo >= hi || lo < hi - stack_limit) { // sp outside stack
+ lo = hi - stack_limit;
+ }
+ #else
+ #error "STACK_GROWS_UP not supported in boost_coroutine2 (as of june 2021), so we don't support it in Nix."
+ #endif
}
# ifdef STACKPTR_CORRECTOR_AVAILABLE
if (GC_sp_corrector != 0)
-8
View File
@@ -3,10 +3,6 @@
#
# It's used for crediting people accurately in release notes. The release notes
# script will link to forgejo, then to GitHub if forgejo is not present.
#
# When adding someone from outside the Lix project, you generally want to simply link their GitHub profile without adding a display name unless they are well-known in the community by that display name.
#
# See doc/manual/src/contributing/hacking.md for more documentation on this file's format and typical usage.
9999years:
display_name: wiggles
forgejo: rbt
@@ -64,10 +60,6 @@ jade:
forgejo: jade
github: lf-
kloenk:
forgejo: kloenk
github: kloenk
lovesegfault:
github: lovesegfault
-4
View File
@@ -110,7 +110,6 @@ manual = custom_target(
builtins_md,
builtin_constants_md,
rl_next_generated,
nix,
],
output : [
'manual',
@@ -187,7 +186,6 @@ foreach command : nix_nested_manpages
],
input : [
manual_md,
nix,
],
output : command[0] + '-' + page + '.1',
install : true,
@@ -300,7 +298,6 @@ foreach page : nix3_manpages
input : [
'render-manpage.sh',
manual_md,
nix,
],
output : page + '.1',
install : true,
@@ -344,7 +341,6 @@ foreach entry : nix_manpages
'render-manpage.sh',
manual_md,
entry.get(3, []),
nix,
],
output : '@0@.@1@'.format(entry[0], entry[1]),
install : true,
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/sh
set -euo pipefail
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/sh
set -euo pipefail
-12
View File
@@ -1,12 +0,0 @@
---
synopsis: "Add a `build-dir` setting to set the backing directory for builds"
cls: 1514
credits: [roberth, tomberek]
category: Improvements
---
`build-dir` can now be set in the Nix configuration to choose the backing directory for the build sandbox.
This can be useful on systems with `/tmp` on tmpfs, or simply to relocate large builds to another disk.
Also, `XDG_RUNTIME_DIR` is no longer considered when selecting the default temporary directory,
as it's not intended to be used for large amounts of data.
@@ -1,14 +0,0 @@
---
synopsis: Add log formats `multiline` and `multiline-with-logs`
cls: [1369]
credits: [kloenk]
category: Improvements
---
Added two new log formats (`multiline` and `multiline-with-logs`) that display
current activities below each other for better visibility.
These formats attempt to use the maximum available lines
(defaulting to 25 if unable to determine) and print up to that many lines.
The status bar is displayed as the first line, with each subsequent
activity on its own line.
-12
View File
@@ -1,12 +0,0 @@
---
synopsis: "`nix copy` is now several times faster at `querying info about /nix/store/...`"
cls: [1462]
issues: [fj#366]
credits: [jade]
category: Fixes
---
We fixed a locking bug that serialized `querying info about /nix/store/...`
onto just one thread such that it was eating `O(paths to copy * latency)` time
while setting up to copy paths to s3 and other stores. It is now `nproc` times
faster.
@@ -1,21 +0,0 @@
---
synopsis: "Lix no longer speaks the Nix remote-build worker protocol to clients or servers older than CppNix 2.3"
cls: [1207, 1208, 1206, 1205, 1204, 1203, 1479]
issues: [fj#325]
credits: [jade]
category: Breaking Changes
---
CppNix 2.3 was released in 2019, and is the new oldest supported version. We
will increase our support baseline in the future up to a final version of CppNix
2.18 (which may happen soon given that it is the only still-packaged and thus
still-tested >2.3 version), but this step already removes a significant amount
of dead, untested, code paths.
Lix speaks the same version of the protocol as CppNix 2.18 and that fact will
never change in the future; the Lix plans to replace the protocol for evolution
will entail a complete incompatible replacement that will be supported in
parallel with the old protocol. Lix will thus retain remote build compatibility
with CppNix as long as CppNix maintains protocol compatibility with 2.18, and
as long as Lix retains legacy protocol support (which will likely be a long
time given that we plan to convert it to a frozen-in-time shim).
@@ -1,8 +0,0 @@
---
synopsis: "`nix repl` now allows tab-completing the special repl :colon commands"
cls: 1367
credits: Qyriad
category: Improvements
---
The REPL (`nix repl`) supports pressing `<TAB>` to complete a partial expression, but now also supports completing the special :colon commands as well (`:b`, `:edit`, `:doc`, etc), if the line starts with a colon.
-10
View File
@@ -1,10 +0,0 @@
---
synopsis: "Lix now supports building with UndefinedBehaviorSanitizer"
cls: [1483]
credits: [jade]
category: Development
---
You can now build Lix with the configuration option `-Db_sanitize=undefined` and it will both work and pass tests. AddressSanitizer support is also coming soon.
For a list of undefined behaviour fixed by sanitizer usage, see [the gerrit topic "undefined-behaviour"](https://gerrit.lix.systems/q/topic:%22undefined-behaviour%22).
+1 -1
View File
@@ -197,7 +197,7 @@
- [Release Notes](release-notes/release-notes.md)
- [Upcoming release](release-notes/rl-next.md)
<!-- RELENG-AUTO-INSERTION-MARKER (see releng/release_notes.py) -->
- [Lix 2.90 (FIXME date)](release-notes/rl-2.90.md)
- [Lix 2.90 (2024-07-10)](release-notes/rl-2.90.md)
- [Nix 2.18 (2023-09-20)](release-notes/rl-2.18.md)
- [Nix 2.17 (2023-07-24)](release-notes/rl-2.17.md)
- [Nix 2.16 (2023-05-31)](release-notes/rl-2.16.md)
-10
View File
@@ -78,16 +78,6 @@ Most commands in Lix accept the following command-line options:
Display the raw logs, with the progress bar at the bottom.
- `multiline`
Display a progress bar during the builds and in the lines below that one line per activity.
- `multiline-with-logs`
Displayes the raw logs, with a progress bar and activities each in a new line at the bottom.
- <span id="opt-no-build-output">[`--no-build-output`](#opt-no-build-output)</span> / `-Q`
By default, output written by builders to standard output and standard error is echoed to the Lix command's standard error.
+25 -73
View File
@@ -39,19 +39,17 @@ $ nix-shell -A native-clangStdenvPackages
### Building from the development shell
You can build and test Lix with just:
As always you may run [stdenv's phases by name](https://nixos.org/manual/nixpkgs/unstable/#sec-building-stdenv-package-in-nix-shell), e.g.:
```bash
$ just setup
$ just build
$ just test --suite=check
$ just install
$ just test --suite=installcheck
$ configurePhase
$ buildPhase
$ checkPhase
$ installPhase
$ installCheckPhase
```
(Check and installcheck may both be done after install, allowing you to omit the --suite argument entirely, but this is the order package.nix runs them in.)
You can also build Lix manually:
To build manually, however, use the following:
```bash
$ meson setup ./build "--prefix=$out" $mesonFlags
@@ -66,7 +64,9 @@ $ meson install -C build
$ meson test -C build --suite=installcheck
```
In both cases, Lix will be installed to `$PWD/outputs`, the `/bin` of which is prepended to PATH in the development shells.
(Check and installcheck may both be done after install, allowing you to omit the --suite argument entirely, but this is the order package.nix runs them in.)
This will install Lix to `$PWD/outputs`, the `/bin` of which is prepended to PATH in the development shells.
If the tests fail and Meson helpfully has no output for why, use the `--print-error-logs` option to `meson test`.
@@ -168,26 +168,8 @@ or for Nix with the [`flakes`] and [`nix-command`] experimental features enabled
$ nix build .#packages.aarch64-linux.default
```
### Cross compiling using the Lix flake
Lix can also be easily cross compiled to the following arbitrarily-chosen system doubles, which can be useful for bootstrapping Lix on new platforms.
These are specified in `crossSystems` in `flake.nix`; feel free to submit changes to add new ones if they are useful to you.
- `armv6l-linux`
- `armv7l-linux`
- `riscv64-linux`
For example, to cross-compile Lix for `armv6l-linux` from another Linux, use the following:
```console
$ nix build .#nix-armv6l-linux
```
It's also possible to cross-compile a tarball of binaries suitable for the Lix installer, for example, for `riscv64-linux`:
```console
$ nix build .#nix-riscv64-linux.passthru.binaryTarball
```
Cross-compiled builds are available for ARMv6 (`armv6l-linux`) and ARMv7 (`armv7l-linux`).
Add more [system types](#system-type) to `crossSystems` in `flake.nix` to bootstrap Nix on unsupported platforms.
### Building for multiple platforms at once
@@ -300,8 +282,9 @@ Regular markdown files used for the manual have a base path of their own and the
## API documentation
Doxygen API documentation will be available online in the future ([tracking issue](https://git.lix.systems/lix-project/lix/issues/422)).
You can also build and view it yourself:
Doxygen API documentation is [available
online](https://hydra.nixos.org/job/nix/master/internal-api-docs/latest/download-by-type/doc/internal-api-docs). You
can also build and view it yourself:
```console
# nix build .#hydraJobs.internal-api-docs
@@ -311,50 +294,44 @@ You can also build and view it yourself:
or inside a `nix develop` shell by running:
```bash
$ meson configure build -Dinternal-api-docs=enabled
$ meson compile -C build internal-api-docs
$ xdg-open ./outputs/doc/share/doc/nix/internal-api/html/index.html
```
## Coverage analysis
A coverage analysis report will be available online in the future (FIXME(lix-hydra)).
You can build it yourself:
A coverage analysis report is [available
online](https://hydra.nixos.org/job/nix/master/coverage/latest/download-by-type/report/coverage). You
can build it yourself:
```
# nix build .#hydraJobs.coverage
# xdg-open ./result/coverage/index.html
```
Metrics about the change in line/function coverage over time will be available in the future (FIXME(lix-hydra)).
Metrics about the change in line/function coverage over time are also
[available](https://hydra.nixos.org/job/nix/master/coverage#tabs-charts).
## Add a release note
`doc/manual/rl-next` contains release notes entries for all unreleased changes.
User-visible changes should come with a release note.
Developer-facing changes should have a release note in the Development category if they are significant and if developers should know about them.
### Add an entry
Here's what a complete entry looks like.
The file name is not incorporated in the final document, and is generally a super brief summary of the change synopsis.
Here's what a complete entry looks like. The file name is not incorporated in the document.
```markdown
```
---
synopsis: Basically a title
# 1234 or gh#1234 will refer to CppNix GitHub, fj#1234 will refer to a Lix forgejo issue.
issues: [1234, fj#1234]
# Use this *only* if there is a CppNix pull request associated with this change.
# Use this *only* if there is a CppNix pull request associated with this change
prs: 1238
# List of Lix Gerrit changelist numbers.
# If there is an associated Lix GitHub PR, just put in the Gerrit CL number.
# List of Lix Gerrit changelist numbers; if there is an associated Lix GitHub
# PR, just put in the Gerrit CL number.
cls: [123]
# Heading that this release note will appear under.
category: Breaking Changes
# Add a credit mention in the bottom of the release note.
# your-name is used as a key into doc/manual/change-authors.yml for metadata
credits: [your-name]
---
Here's one or more paragraphs that describe the change.
@@ -369,31 +346,6 @@ Significant changes should add the following header, which moves them to the top
significance: significant
```
The following categories of release notes are supported (see `maintainers/build-release-notes.py`):
- Breaking Changes
- Features
- Improvements
- Fixes
- Packaging
- Development
- Miscellany
The `credits` field, if present, gives credit to the author of the patch in the release notes with a message like "Many thanks to (your-name) for this" and linking to GitHub or Forgejo profiles if listed.
If you are forward-porting a change from CppNix, please credit the original author, and optionally credit yourself.
When adding credits metadata for people external to the project and deciding whether to put in a `display_name`, consider what they are generally known as in the community; even if you know their full name (e.g. from their GitHub profile), we suggest only adding it as a display name if that is what they go by in the community.
There are multiple reasons we follow this practice, but it boils down to privacy and consent: we would rather not capture full names that are not widely used in the community without the consent of the parties involved, even if they are publicly available.
As of this writing, the entries with full names as `display_name` are either members of the CppNix team or people who added them themselves.
The names specified in `credits` are used as keys to look up the authorship info in `doc/manual/change-authors.yml`.
The only mandatory part is that every key appearing in `credits` has an entry present in `change-authors.yml`.
All of the following properties are optional; you can specify `{}` as the metadata if you want a simple non-hyperlinked mention.
The following properties are supported:
- `display_name`: display name used in place of the key when showing names, if present.
- `forgejo`: Forgejo username. The name in the release notes will be a link to this, if present.
- `github`: GitHub username, used if `forgejo` is not set, again making a link.
### Build process
Releases have a precomputed `rl-MAJOR.MINOR.md`, and no `rl-next.md`.
+2 -2
View File
@@ -1,7 +1,7 @@
# Lix 2.90 "Vanilla Ice Cream" (FIXME date)
# Lix 2.90 "Vanilla Ice Cream" (2024-07-10)
# Lix 2.90.0 (FIXME date)
# Lix 2.90.0 (2024-07-10)
## Breaking Changes
- Deprecate the online flake registries and vendor the default registry [fj#183](https://git.lix.systems/lix-project/lix/issues/183) [fj#110](https://git.lix.systems/lix-project/lix/issues/110) [fj#116](https://git.lix.systems/lix-project/lix/issues/116) [#8953](https://github.com/NixOS/nix/issues/8953) [#9087](https://github.com/NixOS/nix/issues/9087) [cl/1127](https://gerrit.lix.systems/c/lix/+/1127)
+7 -21
View File
@@ -59,7 +59,7 @@
(Run `touch .nocontribmsg` to hide this message.)
'';
officialRelease = false;
officialRelease = true;
# Set to true to build the release notes for the next release.
buildUnreleasedNotes = true;
@@ -84,8 +84,6 @@
];
systems = linuxSystems ++ darwinSystems;
# If you add something here, please update the list in doc/manual/src/contributing/hacking.md.
# Thanks~
crossSystems = [
"armv6l-linux"
"armv7l-linux"
@@ -99,8 +97,7 @@
stdenvs = [
"gccStdenv"
"clangStdenv"
# FIXME: gcc 12 (default in 23.11) has bugs that break the nar parser.
"gcc13Stdenv" # "stdenv"
"stdenv"
"libcxxStdenv"
"ccacheStdenv"
];
@@ -146,7 +143,7 @@
];
};
stdenvs = forAllStdenvs (make-pkgs null);
native = stdenvs.gcc13StdenvPackages;
native = stdenvs.stdenvPackages;
in
{
inherit stdenvs native;
@@ -198,19 +195,14 @@
busybox-sandbox-shell = final.busybox-sandbox-shell or final.default-busybox-sandbox-shell;
};
pegtl = final.nix.passthru.pegtl;
# Export the patched version of boehmgc that Lix uses into the overlay
# for consumers of this flake.
boehmgc-nix = final.nix.passthru.boehmgc-nix;
boehmgc-nix = final.nix.boehmgc-nix;
# And same thing for our build-release-notes package.
build-release-notes = final.nix.passthru.build-release-notes;
build-release-notes = final.nix.build-release-notes;
};
in
{
# for repl debugging
inherit self;
# A Nixpkgs overlay that overrides the 'nix' and
# 'nix.perl-bindings' packages.
overlays.default = overlayFor (p: p.stdenv);
@@ -241,11 +233,6 @@
}
);
# Completion tests for the Nix REPL.
repl-completion = forAllSystems (
system: nixpkgsFor.${system}.native.callPackage ./tests/repl-completion.nix { }
);
# Perl bindings for various platforms.
perlBindings = forAllSystems (system: nixpkgsFor.${system}.native.nix.passthru.perl-bindings);
@@ -343,7 +330,6 @@
rl-next = self.hydraJobs.rl-next.${system}.user;
# Will be empty attr set on i686-linux, and filtered out by forAvailableSystems.
pre-commit = self.hydraJobs.pre-commit.${system};
repl-completion = self.hydraJobs.repl-completion.${system};
}
// (lib.optionalAttrs (builtins.elem system linux64BitSystems)) {
dockerImage = self.hydraJobs.dockerImage.${system};
@@ -393,7 +379,7 @@
nix = pkgs.callPackage ./package.nix {
inherit stdenv officialRelease versionSuffix;
busybox-sandbox-shell = pkgs.busybox-sandbox-shell or pkgs.default-busybox-sandbox;
internalApiDocs = false;
internalApiDocs = true;
};
pre-commit = self.hydraJobs.pre-commit.${pkgs.system} or { };
in
@@ -421,7 +407,7 @@
makeShell pkgs pkgs.stdenv
))
// {
default = self.devShells.${system}.native-gcc13StdenvPackages;
default = self.devShells.${system}.native-stdenvPackages;
}
);
};
+2 -2
View File
@@ -9,8 +9,8 @@ clean:
rm -rf build
# Prepare meson for building
setup *OPTIONS:
meson setup build --prefix="$PWD/outputs/out" $mesonFlags {{ OPTIONS }}
setup:
meson setup build --prefix="$PWD/outputs/out"
# Build lix
build *OPTIONS:
-2
View File
@@ -20,8 +20,6 @@ SIGNIFICANCECES = {
# This is just hardcoded for better validation. If you think there should be
# more of them, feel free to add more.
#
# Please update doc/manual/src/contributing/hacking.md if you do. Thanks~
CATEGORIES = [
'Breaking Changes',
'Features',
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/sh
# Generates a report of build time based on a meson build using -ftime-trace in
# Clang.
+11 -37
View File
@@ -142,16 +142,6 @@ else
cpp_pch = []
endif
# gcc 12 is known to miscompile some coroutine-based code quite horribly,
# causing (among other things) copies of move-only objects and the double
# frees one would expect when the objects are unique_ptrs. these problems
# often show up as memory corruption when nesting generators (since we do
# treat generators like owned memory) and will cause inexplicable crashs.
assert(
cxx.get_id() != 'gcc' or cxx.version().version_compare('>=13'),
'GCC 12 and earlier are known to miscompile lix coroutines, use GCC 13 or clang.'
)
# Translate some historical and Mesony CPU names to Lixy CPU names.
# FIXME(Qyriad): the 32-bit x86 code is not tested right now, because cross compilation for Lix
@@ -177,7 +167,6 @@ message('canonical Nix system name:', host_system)
is_linux = host_machine.system() == 'linux'
is_darwin = host_machine.system() == 'darwin'
is_freebsd = host_machine.system() == 'freebsd'
is_x64 = host_machine.cpu_family() == 'x86_64'
# Per-platform arguments that you should probably pass to shared_module() invocations.
@@ -204,7 +193,7 @@ configdata += {
'HAVE_BOEHMGC': boehm.found().to_int(),
}
boost = dependency('boost', required : true, modules : ['container'])
boost = dependency('boost', required : true, modules : ['context', 'coroutine', 'container'])
# cpuid only makes sense on x86_64
cpuid_required = is_x64 ? get_option('cpuid') : false
@@ -233,8 +222,7 @@ brotli = [
openssl = dependency('libcrypto', 'openssl', required : true)
# FIXME: confirm we actually support such old versions of aws-sdk-cpp
aws_sdk = dependency('aws-cpp-sdk-core', required : false, version : '>=1.8')
aws_sdk = dependency('aws-cpp-sdk-core', required : false)
aws_sdk_transfer = dependency('aws-cpp-sdk-transfer', required : aws_sdk.found(), fallback : ['aws_sdk', 'aws_cpp_sdk_transfer_dep'])
if aws_sdk.found()
# The AWS pkg-config adds -std=c++11.
@@ -246,6 +234,12 @@ if aws_sdk.found()
links : true,
sources : true,
)
s = aws_sdk.version().split('.')
configdata += {
'AWS_VERSION_MAJOR': s[0].to_int(),
'AWS_VERSION_MINOR': s[1].to_int(),
'AWS_VERSION_PATCH': s[2].to_int(),
}
aws_sdk_transfer = aws_sdk_transfer.partial_dependency(
compile_args : false,
includes : true,
@@ -297,14 +291,6 @@ gtest = [
toml11 = dependency('toml11', version : '>=3.7.0', required : true, method : 'cmake')
pegtl = dependency(
'pegtl',
version : '>=3.2.7',
required : true,
method : 'cmake',
modules : [ 'taocpp::pegtl' ],
)
nlohmann_json = dependency('nlohmann_json', required : true)
# lix-doc is a Rust project provided via buildInputs and unfortunately doesn't have any way to be detected.
@@ -353,6 +339,8 @@ endif
# that busybox sh won't run busybox applets as builtins (which would break our sandbox).
lsof = find_program('lsof', native : true)
bison = find_program('bison', native : true)
flex = find_program('flex', native : true)
# This is how Nix does generated headers...
# other instances of header generation use a very similar command.
@@ -442,7 +430,6 @@ add_project_arguments(
'-Wimplicit-fallthrough',
'-Werror=switch',
'-Werror=switch-enum',
'-Werror=unused-result',
'-Wdeprecated-copy',
'-Wignored-qualifiers',
# Enable assertions in libstdc++ by default. Harmless on libc++. Benchmarked
@@ -454,9 +441,7 @@ add_project_arguments(
language : 'cpp',
)
# We turn off the production UBSan if the slower dev UBSan is requested, to
# give better diagnostics.
if cxx.get_id() in ['gcc', 'clang'] and 'undefined' not in get_option('b_sanitize')
if cxx.get_id() in ['gcc', 'clang']
# 2024-03-24: jade benchmarked the default sanitize reporting in clang and got
# a regression of about 10% on hackage-packages.nix with clang. So we are trapping instead.
#
@@ -471,23 +456,12 @@ if cxx.get_id() in ['gcc', 'clang'] and 'undefined' not in get_option('b_sanitiz
add_project_arguments(sanitize_args, language: 'cpp')
add_project_link_arguments(sanitize_args, language: 'cpp')
endif
# Clang's default of -no-shared-libsan on Linux causes link errors; on macOS it defaults to shared.
# GCC defaults to shared libsan so is fine.
if cxx.get_id() == 'clang' and get_option('b_sanitize') != ''
add_project_link_arguments('-shared-libsan', language : 'cpp')
endif
add_project_link_arguments('-pthread', language : 'cpp')
if cxx.get_linker_id() in ['ld.bfd', 'ld.gold']
add_project_link_arguments('-Wl,--no-copy-dt-needed-entries', language : 'cpp')
endif
if is_freebsd
# FreeBSD's `environ` is defined in `crt1.o`, not `libc.so`,
# so the linker thinks it's undefined
add_project_link_arguments('-Wl,-z,undefs', language: 'cpp')
endif
# Generate Chromium tracing files for each compiled file, which enables
# maintainers/buildtime_report.sh BUILD-DIR to simply work in clang builds.
#
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Meson will call this with an absolute path to Bash.
# The shebang is just for convenience.
# The parser and lexer tab are generated via custom Meson targets in src/libexpr/meson.build,
# but Meson doesn't support marking only part of a target for install. The generation creates
# both headers (parser-tab.hh, lexer-tab.hh) and source files (parser-tab.cc, lexer-tab.cc),
# and we definitely want the former installed, but not the latter. This script is added to
# Meson's install steps to correct this, as the logic for it is just complex enough to
# warrant separate and careful handling, because both Meson's configured include directory
# may or may not be an absolute path, and DESTDIR may or may not be set at all, but can't be
# manipulated in Meson logic.
set -euo pipefail
echo "cleanup-install: removing Meson-placed C++ sources from dest includedir"
if [[ "${1/--help/}" != "$1" ]]; then
echo "cleanup-install: this script should only be called from the Meson build system"
exit 1
fi
# Ensure the includedir was passed as the first argument
# (set -u will make this fail otherwise).
includedir="$1"
# And then ensure that first argument is a directory that exists.
if ! [[ -d "$1" ]]; then
echo "cleanup-install: this script should only be called from the Meson build system"
echo "argv[1] (${1@Q}) is not a directory"
exit 2
fi
# If DESTDIR environment variable is set, prepend it to the include dir.
# Unfortunately, we cannot do this on the Meson side. We do have an environment variable
# `MESON_INSTALL_DESTDIR_PREFIX`, but that will not refer to the include directory if
# includedir has been set separately, which Lix's split-output derivation does.
# We also cannot simply do an inline bash conditional like "${DESTDIR:=}" or similar,
# because we need to specifically *join* DESTDIR and includedir with a slash, and *not*
# have a slash if DESTDIR isn't set at all, since $includedir could be a relative directory.
# Finally, DESTDIR is only available to us as an environment variable in these install scripts,
# not in Meson logic.
# Therefore, our best option is to have Meson pass this script the configured includedir,
# and perform this dance with it and $DESTDIR.
if [[ -n "${DESTDIR:-}" ]]; then
includedir="$DESTDIR/$includedir"
fi
# Intentionally not using -f.
# If these files don't exist then our assumptions have been violated and we should fail.
rm -v "$includedir/lix/libexpr/parser-tab.cc" "$includedir/lix/libexpr/lexer-tab.cc"
-23
View File
@@ -1,23 +0,0 @@
{
stdenv,
cmake,
ninja,
fetchFromGitHub,
}:
stdenv.mkDerivation {
pname = "pegtl";
version = "3.2.7";
src = fetchFromGitHub {
repo = "PEGTL";
owner = "taocpp";
rev = "refs/tags/3.2.7";
hash = "sha256-IV5YNGE4EWVrmg2Sia/rcU8jCuiBynQGJM6n3DCWTQU=";
};
nativeBuildInputs = [
cmake
ninja
];
}
+18 -28
View File
@@ -10,16 +10,17 @@
boehmgc-nix ? __forDefaults.boehmgc-nix,
boehmgc,
nlohmann_json,
bison,
build-release-notes ? __forDefaults.build-release-notes,
boost,
brotli,
bzip2,
callPackage,
cmake,
curl,
doxygen,
editline-lix ? __forDefaults.editline-lix,
editline,
flex,
git,
gtest,
jq,
@@ -35,7 +36,6 @@
meson,
ninja,
openssl,
pegtl ? __forDefaults.pegtl,
pkg-config,
python3,
rapidcheck,
@@ -62,16 +62,22 @@
__forDefaults ? {
canRunInstalled = stdenv.buildPlatform.canExecute stdenv.hostPlatform;
boehmgc-nix = boehmgc.override { enableLargeConfig = true; };
boehmgc-nix = (boehmgc.override { enableLargeConfig = true; }).overrideAttrs {
patches = [
# We do *not* include prev.patches (which doesn't exist in normal pkgs.boehmgc anyway)
# because if the caller of this package passed a patched boehm as `boehmgc` instead of
# `boehmgc-nix` then this will almost certainly have duplicate patches, which means
# the patches won't apply and we'll get a build failure.
./boehmgc-coroutine-sp-fallback.diff
];
};
editline-lix = editline.overrideAttrs (prev: {
configureFlags = prev.configureFlags or [ ] ++ [ (lib.enableFeature true "sigstop") ];
});
lix-doc = callPackage ./lix-doc/package.nix { };
build-release-notes = callPackage ./maintainers/build-release-notes.nix { };
pegtl = callPackage ./misc/pegtl.nix { };
lix-doc = pkgs.callPackage ./lix-doc/package.nix { };
build-release-notes = pkgs.callPackage ./maintainers/build-release-notes.nix { };
},
}:
let
@@ -159,6 +165,7 @@ stdenv.mkDerivation (finalAttrs: {
functionalTestFiles
]
++ lib.optionals (!finalAttrs.dontBuild || internalApiDocs) [
./boehmgc-coroutine-sp-fallback.diff
./doc
./misc
./src
@@ -203,6 +210,8 @@ stdenv.mkDerivation (finalAttrs: {
nativeBuildInputs =
[
bison
flex
python3
meson
ninja
@@ -241,7 +250,6 @@ stdenv.mkDerivation (finalAttrs: {
libsodium
toml11
lix-doc
pegtl
]
++ lib.optionals hostPlatform.isLinux [
libseccomp
@@ -296,9 +304,6 @@ stdenv.mkDerivation (finalAttrs: {
else
appendToVar configureFlags "--disable-tests"
fi
# Fix up /usr/bin/env shebangs relied on by the build
patchShebangs --build tests/ doc/manual/
'';
mesonBuildType = "debugoptimized";
@@ -374,12 +379,7 @@ stdenv.mkDerivation (finalAttrs: {
# Export the patched version of boehmgc.
# flake.nix exports that into its overlay.
passthru = {
inherit (__forDefaults)
boehmgc-nix
editline-lix
build-release-notes
pegtl
;
inherit (__forDefaults) boehmgc-nix editline-lix build-release-notes;
inherit officialRelease;
@@ -392,7 +392,6 @@ stdenv.mkDerivation (finalAttrs: {
bashInteractive,
clang-tools,
clangbuildanalyzer,
doxygen,
glibcLocales,
just,
llvmPackages,
@@ -458,10 +457,6 @@ stdenv.mkDerivation (finalAttrs: {
skopeo
just
nixfmt
# Included above when internalApiDocs is true, but we set that to
# false intentionally to save dev build time.
# To build them in a dev shell, you can set -Dinternal-api-docs=enabled when configuring.
doxygen
# Load-bearing order. Must come before clang-unwrapped below, but after clang_tools above.
stdenv.cc
]
@@ -481,7 +476,7 @@ stdenv.mkDerivation (finalAttrs: {
# https://git.lix.systems/lix-project/lix/src/commit/7575db522e9008685c4009423398f6900a16bcce/src/nix/develop.cc#L240-L241
# this is, of course, absurd.
if [[ $name != lix-shell-env && $name != lix-shell-env-env ]]; then
return
return;
fi
PATH=$prefix/bin:$PATH
@@ -491,11 +486,6 @@ stdenv.mkDerivation (finalAttrs: {
# Make bash completion work.
XDG_DATA_DIRS+=:$out/share
if [[ ! -f ./.this-is-lix ]]; then
echo "Dev shell not started from inside a Lix repo, skipping repo setup" >&2
return
fi
${lib.optionalString (pre-commit-checks ? shellHook) pre-commit-checks.shellHook}
# Allow `touch .nocontribmsg` to turn this notice off.
if ! [[ -f .nocontribmsg ]]; then
-1
View File
@@ -65,6 +65,5 @@ if cxx.get_linker_id() in ['ld.bfd', 'ld.gold']
endif
libstore = dependency('lixstore', 'lix-store', required : true)
libutil = dependency('lixutil', 'lix-util', required : true)
subdir('lib/Nix')
+1 -1
View File
@@ -244,7 +244,7 @@ StorePath ProfileManifest::build(ref<Store> store)
/* Add the symlink tree to the store. */
StringSink sink;
sink << dumpPath(tempDir);
dumpPath(tempDir, sink);
auto narHash = hashString(htSHA256, sink.s);
+11
View File
@@ -0,0 +1,11 @@
#include "command-installable-value.hh"
namespace nix {
void InstallableValueCommand::run(ref<Store> store, ref<Installable> installable)
{
auto installableValue = InstallableValue::require(installable);
run(store, installableValue);
}
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
///@file
#include "installable-value.hh"
#include "command.hh"
namespace nix {
/**
* An InstallableCommand where the single positional argument must be an
* InstallableValue in particular.
*/
struct InstallableValueCommand : InstallableCommand
{
/**
* Entry point to this command
*/
virtual void run(ref<Store> store, ref<InstallableValue> installable) = 0;
void run(ref<Store> store, ref<Installable> installable) override;
};
}
+11 -18
View File
@@ -214,7 +214,7 @@ void SourceExprCommand::completeInstallable(AddCompletions & completions, std::s
evalSettings.pureEval = false;
auto state = getEvalState();
Expr & e = state->parseExprFromFile(
Expr *e = state->parseExprFromFile(
resolveExprPath(state->checkSourcePath(lookupFileArg(*state, *file)))
);
@@ -393,10 +393,13 @@ ref<eval_cache::EvalCache> openEvalCache(
EvalState & state,
std::shared_ptr<flake::LockedFlake> lockedFlake)
{
auto fingerprint = evalSettings.useEvalCache && evalSettings.pureEval
? std::make_optional(lockedFlake->getFingerprint())
: std::nullopt;
auto rootLoader = [&state, lockedFlake]()
auto fingerprint = lockedFlake->getFingerprint();
return make_ref<nix::eval_cache::EvalCache>(
evalSettings.useEvalCache && evalSettings.pureEval
? std::optional { std::cref(fingerprint) }
: std::nullopt,
state,
[&state, lockedFlake]()
{
/* For testing whether the evaluation cache is
complete. */
@@ -412,17 +415,7 @@ ref<eval_cache::EvalCache> openEvalCache(
assert(aOutputs);
return aOutputs->value;
};
if (fingerprint) {
auto search = state.evalCaches.find(fingerprint.value());
if (search == state.evalCaches.end()) {
search = state.evalCaches.emplace(fingerprint.value(), make_ref<nix::eval_cache::EvalCache>(fingerprint, state, rootLoader)).first;
}
return search->second;
} else {
return make_ref<nix::eval_cache::EvalCache>(std::nullopt, state, rootLoader);
}
});
}
Installables SourceExprCommand::parseInstallables(
@@ -441,13 +434,13 @@ Installables SourceExprCommand::parseInstallables(
auto vFile = state->allocValue();
if (file == "-") {
auto & e = state->parseStdin();
auto e = state->parseStdin();
state->eval(e, *vFile);
}
else if (file)
state->evalFile(lookupFileArg(*state, *file), *vFile);
else {
auto & e = state->parseExprFromString(*expr, state->rootPath(CanonPath::fromCwd()));
auto e = state->parseExprFromString(*expr, state->rootPath(CanonPath::fromCwd()));
state->eval(e, *vFile);
}
+2
View File
@@ -1,5 +1,6 @@
libcmd_sources = files(
'built-path.cc',
'command-installable-value.cc',
'cmd-profiles.cc',
'command.cc',
'common-eval-args.cc',
@@ -17,6 +18,7 @@ libcmd_sources = files(
libcmd_headers = files(
'built-path.hh',
'command-installable-value.hh',
'cmd-profiles.hh',
'command.hh',
'common-eval-args.hh',
+15 -77
View File
@@ -1,10 +1,7 @@
#include <cstdio>
#include <editline.h>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <climits>
#include <string_view>
#include "box_ptr.hh"
#include "repl-interacter.hh"
@@ -82,8 +79,6 @@ enum class ProcessLineResult {
PromptAgain,
};
using namespace std::literals::string_view_literals;
struct NixRepl
: AbstractNixRepl
, detail::ReplCompleterMixin
@@ -91,35 +86,6 @@ struct NixRepl
, gc
#endif
{
/* clang-format: off */
static constexpr std::array COMMANDS = {
"add"sv, "a"sv,
"load"sv, "l"sv,
"load-flake"sv, "lf"sv,
"reload"sv, "r"sv,
"edit"sv, "e"sv,
"t"sv,
"u"sv,
"b"sv,
"bl"sv,
"i"sv,
"sh"sv,
"log"sv,
"print"sv, "p"sv,
"quit"sv, "q"sv,
"doc"sv,
"te"sv,
};
static constexpr std::array DEBUG_COMMANDS = {
"env"sv,
"bt"sv, "backtrace"sv,
"st"sv,
"c"sv, "continue"sv,
"s"sv, "step"sv,
};
/* clang-format: on */
size_t debugTraceIndex;
Strings loadedFiles;
@@ -156,7 +122,7 @@ struct NixRepl
void reloadFiles();
void addAttrsToScope(Value & attrs);
void addVarToScope(const Symbol name, Value & v);
Expr & parseString(std::string s);
Expr * parseString(std::string s);
void evalString(std::string s, Value & v);
void loadDebugTraceEnv(DebugTrace & dt);
@@ -243,7 +209,8 @@ NixRepl::NixRepl(const SearchPath & searchPath, nix::ref<Store> store, ref<EvalS
{
}
void runNix(Path program, const Strings & args)
void runNix(Path program, const Strings & args,
const std::optional<std::string> & input = {})
{
auto subprocessEnv = getEnv();
subprocessEnv["NIX_CONFIG"] = globalConfig.toKeyValue();
@@ -252,7 +219,8 @@ void runNix(Path program, const Strings & args)
.program = settings.nixBinDir+ "/" + program,
.args = args,
.environment = subprocessEnv,
}).wait();
.input = input,
});
return;
}
@@ -355,36 +323,6 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
{
StringSet completions;
// We should only complete colon commands if there's a colon at the beginning,
// but editline (for... whatever reason) doesn't *give* us the colon in the
// completion callback. If the user types :rel<TAB>, `prefix` will only be `rel`.
// Luckily, editline provides a global variable for its current buffer, so we can
// check for the presence of a colon there.
if (rl_line_buffer != nullptr && rl_line_buffer[0] == ':') {
for (auto const & colonCmd : this->COMMANDS) {
if (colonCmd.starts_with(prefix)) {
completions.insert(std::string(colonCmd));
}
}
if (state->debugRepl) {
for (auto const & colonCmd : this->DEBUG_COMMANDS) {
if (colonCmd.starts_with(prefix)) {
completions.insert(std::string(colonCmd));
}
}
}
// If there were : command completions, then we should only return those,
// because otherwise this is not valid Nix syntax.
// However if we didn't get any completions, then this could be something
// like `:b pkgs.hel<TAB>`, in which case we should do expression completion
// as normal.
if (!completions.empty()) {
return completions;
}
}
size_t start = prefix.find_last_of(" \n\r\t(){}[]");
std::string prev, cur;
if (start == std::string::npos) {
@@ -427,9 +365,9 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
auto expr = cur.substr(0, dot);
auto cur2 = cur.substr(dot + 1);
Expr & e = parseString(expr);
Expr * e = parseString(expr);
Value v;
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?)");
for (auto & i : *v.attrs) {
@@ -651,7 +589,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
// runProgram redirects stdout to a StringSink,
// using runProgram2 to allow editors to display their UI
runProgram2(RunOptions { .program = editor, .searchPath = true, .args = args }).wait();
runProgram2(RunOptions { .program = editor, .searchPath = true, .args = args });
// Reload right after exiting the editor
state->resetFileCache();
@@ -820,7 +758,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
line[p + 1] != '=' &&
isVarName(name = removeWhitespace(line.substr(0, p))))
{
Expr & e = parseString(line.substr(p + 1));
Expr * e = parseString(line.substr(p + 1));
Value & v(*state->allocValue());
v.mkThunk(env, e);
addVarToScope(state->symbols.create(name), v);
@@ -945,7 +883,7 @@ Value * NixRepl::getReplOverlaysEvalFunction()
auto code =
#include "repl-overlays.nix.gen.hh"
;
auto & expr = state->parseExprFromString(
auto expr = state->parseExprFromString(
code,
SourcePath(evalReplInitFilesPath),
state->staticBaseEnv
@@ -1053,7 +991,7 @@ Value * NixRepl::bindingsToAttrs()
}
Expr & NixRepl::parseString(std::string s)
Expr * NixRepl::parseString(std::string s)
{
return state->parseExprFromString(std::move(s), state->rootPath(CanonPath::fromCwd()), staticEnv);
}
@@ -1061,16 +999,16 @@ Expr & NixRepl::parseString(std::string s)
void NixRepl::evalString(std::string s, Value & v)
{
Expr & e = parseString(s);
e.eval(*state, *env, v);
Expr * e = parseString(s);
e->eval(*state, *env, v);
state->forceValue(v, v.determinePos(noPos));
}
Value * NixRepl::evalFile(SourcePath & path)
{
auto & expr = state->parseExprFromFile(path, staticEnv);
auto expr = state->parseExprFromFile(path, staticEnv);
Value * result(state->allocValue());
expr.eval(*state, *env, *result);
expr->eval(*state, *env, *result);
state->forceValue(*result, result->determinePos(noPos));
return result;
}
+1 -5
View File
@@ -47,16 +47,12 @@ MakeError(MissingArgumentError, EvalError);
MakeError(RestrictedPathError, Error);
MakeError(InfiniteRecursionError, EvalError);
/**
* Represents an exception due to an invalid path; that is, it does not exist.
* It corresponds to `!Store::validPath()`.
*/
struct InvalidPathError : public EvalError
{
public:
Path path;
InvalidPathError(EvalState & state, const Path & path)
: EvalError(state, "path '%s' did not exist in the store during evaluation", path)
: EvalError(state, "path '%s' is not valid", path)
{
}
};
+2 -2
View File
@@ -86,11 +86,11 @@ void EvalState::forceValue(Value & v, const PosIdx pos)
{
if (v.isThunk()) {
Env * env = v.thunk.env;
Expr & expr = *v.thunk.expr;
Expr * expr = v.thunk.expr;
try {
v.mkBlackhole();
//checkInterrupt();
expr.eval(*this, *env, v);
expr->eval(*this, *env, v);
} catch (...) {
v.mkThunk(env, expr);
tryFixupBlackHolePos(v, pos);
+5 -3
View File
@@ -63,9 +63,11 @@ Strings EvalSettings::getDefaultNixPath()
}
};
add(getNixDefExpr() + "/channels");
add(rootChannelsDir() + "/nixpkgs", "nixpkgs");
add(rootChannelsDir());
if (!evalSettings.restrictEval && !evalSettings.pureEval) {
add(getNixDefExpr() + "/channels");
add(rootChannelsDir() + "/nixpkgs", "nixpkgs");
add(rootChannelsDir());
}
return res;
}
+4 -26
View File
@@ -15,21 +15,9 @@ struct EvalSettings : Config
static std::string resolvePseudoUrl(std::string_view url);
Setting<bool> enableNativeCode{this, false, "allow-unsafe-native-code-during-evaluation", R"(
Enable built-in functions that allow executing native code.
Whether builtin functions that allow executing native code should be enabled.
In particular, this adds:
- `builtins.importNative` *path* *symbol*
Runs function with *symbol* from a dynamic shared object (DSO) at *path*.
This may be used to add new builtins to the Nix language.
The procedure must have the following signature:
```cpp
extern "C" typedef void (*ValueInitialiser) (EvalState & state, Value & v);
```
- `builtins.exec` *arguments*
Execute a program, where *arguments* are specified as a list of strings, and parse its output as a Nix expression.
In particular, this adds the `importNative` and `exec` builtins.
)"};
Setting<Strings> nixPath{
@@ -75,17 +63,8 @@ struct EvalSettings : Config
R"(
Pure evaluation mode ensures that the result of Nix expressions is fully determined by explicitly declared inputs, and not influenced by external state:
- File system and network access is restricted to accesses to immutable data only:
- Path literals relative to the home directory like `~/lix` are rejected at parse time.
- Access to absolute paths that did not result from Nix language evaluation is rejected when such paths are given as parameters to builtins like, for example, [`builtins.readFile`](@docroot@/language/builtins.md#builtins-readFile).
Access is nonetheless allowed to (absolute) paths in the Nix store that are returned by builtins like [`builtins.filterSource`](@docroot@/language/builtins.md#builtins-filterSource), [`builtins.fetchTarball`](@docroot@/language/builtins.md#builtins-fetchTarball) and similar.
- Impure fetches such as not specifying a commit ID for `builtins.fetchGit` or not specifying a hash for `builtins.fetchTarball` are rejected.
- In flakes, access to relative paths outside of the root of the flake's source tree (often, a git repository) is rejected.
- The evaluator ignores `NIX_PATH`, `-I` and the `nix-path` setting. Thus, [`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtins-nixPath) is an empty list.
- The builtins [`builtins.currentSystem`](@docroot@/language/builtin-constants.md#builtins-currentSystem) and [`builtins.currentTime`](@docroot@/language/builtin-constants.md#builtins-currentTime) are absent from `builtins`.
- [`builtins.getEnv`](@docroot@/language/builtin-constants.md#builtins-currentSystem) always returns empty string for any variable.
- [`builtins.storePath`](@docroot@/language/builtins.md#builtins-storePath) throws an error (Lix may change this, tracking issue: <https://git.lix.systems/lix-project/lix/issues/402>)
- Restrict file system and network access to files specified by cryptographic hash
- Disable [`builtins.currentSystem`](@docroot@/language/builtin-constants.md#builtins-currentSystem) and [`builtins.currentTime`](@docroot@/language/builtin-constants.md#builtins-currentTime)
)"
};
@@ -107,7 +86,6 @@ struct EvalSettings : Config
allowed to access `https://github.com/NixOS/patchelf.git`.
)"};
Setting<bool> traceFunctionCalls{this, false, "trace-function-calls",
R"(
If set to `true`, the Nix evaluator will trace every function call.
+197 -188
View File
@@ -4,7 +4,6 @@
#include "primops.hh"
#include "print-options.hh"
#include "shared.hh"
#include "suggestions.hh"
#include "types.hh"
#include "store-api.hh"
#include "derivations.hh"
@@ -18,6 +17,7 @@
#include "gc-small-vector.hh"
#include "fetch-to-store.hh"
#include "flake/flakeref.hh"
#include "parser-tab.hh"
#include <algorithm>
#include <chrono>
@@ -42,6 +42,10 @@
#include <gc/gc.h>
#include <gc/gc_cpp.h>
#include <boost/coroutine2/coroutine.hpp>
#include <boost/coroutine2/protected_fixedsize_stack.hpp>
#include <boost/context/stack_context.hpp>
#endif
using json = nlohmann::json;
@@ -201,6 +205,42 @@ static void * oomHandler(size_t requested)
throw std::bad_alloc();
}
class BoehmGCStackAllocator : public StackAllocator {
boost::coroutines2::protected_fixedsize_stack stack {
// We allocate 8 MB, the default max stack size on NixOS.
// A smaller stack might be quicker to allocate but reduces the stack
// depth available for source filter expressions etc.
std::max(boost::context::stack_traits::default_size(), static_cast<std::size_t>(8 * 1024 * 1024))
};
// This is specific to boost::coroutines2::protected_fixedsize_stack.
// The stack protection page is included in sctx.size, so we have to
// subtract one page size from the stack size.
std::size_t pfss_usable_stack_size(boost::context::stack_context &sctx) {
return sctx.size - boost::context::stack_traits::page_size();
}
public:
boost::context::stack_context allocate() override {
auto sctx = stack.allocate();
// Stacks generally start at a high address and grow to lower addresses.
// Architectures that do the opposite are rare; in fact so rare that
// boost_routine does not implement it.
// So we subtract the stack size.
GC_add_roots(static_cast<char *>(sctx.sp) - pfss_usable_stack_size(sctx), sctx.sp);
return sctx;
}
void deallocate(boost::context::stack_context sctx) override {
GC_remove_roots(static_cast<char *>(sctx.sp) - pfss_usable_stack_size(sctx), sctx.sp);
stack.deallocate(sctx);
}
};
static BoehmGCStackAllocator boehmGCStackAllocator;
#endif
@@ -216,6 +256,23 @@ static Symbol getName(const AttrName & name, EvalState & state, Env & env)
}
}
#if HAVE_BOEHMGC
/* Disable GC while this object lives. Used by CoroutineContext.
*
* Boehm keeps a count of GC_disable() and GC_enable() calls,
* and only enables GC when the count matches.
*/
class BoehmDisableGC {
public:
BoehmDisableGC() {
GC_disable();
};
~BoehmDisableGC() {
GC_enable();
};
};
#endif
static bool gcInitialised = false;
void initGC()
@@ -237,6 +294,17 @@ void initGC()
GC_set_oom_fn(oomHandler);
StackAllocator::defaultAllocator = &boehmGCStackAllocator;
#if NIX_BOEHM_PATCH_VERSION != 1
printTalkative("Unpatched BoehmGC, disabling GC inside coroutines");
/* Used to disable GC when entering coroutines on macOS */
create_coro_gc_hook = []() -> std::shared_ptr<void> {
return std::make_shared<BoehmDisableGC>();
};
#endif
/* Set the initial heap size to something fairly big (25% of
physical RAM, up to a maximum of 384 MiB) so that in most cases
we don't need to garbage collect at all. (Collection has a
@@ -350,7 +418,7 @@ EvalState::EvalState(
}
if (evalSettings.restrictEval || evalSettings.pureEval) {
allowedPaths = std::optional(PathSet());
allowedPaths = PathSet();
for (auto & i : searchPath.elements) {
auto r = resolveSearchPathPath(i.path);
@@ -882,14 +950,14 @@ void EvalState::mkList(Value & v, size_t size)
unsigned long nrThunks = 0;
static inline void mkThunk(Value & v, Env & env, Expr & expr)
static inline void mkThunk(Value & v, Env & env, Expr * expr)
{
v.mkThunk(&env, expr);
nrThunks++;
}
void EvalState::mkThunk_(Value & v, Expr & expr)
void EvalState::mkThunk_(Value & v, Expr * expr)
{
mkThunk(v, baseEnv, expr);
}
@@ -990,7 +1058,7 @@ void EvalState::mkSingleDerivedPathString(
Value * Expr::maybeThunk(EvalState & state, Env & env)
{
Value * v = state.allocValue();
mkThunk(*v, env, *this);
mkThunk(*v, env, this);
return v;
}
@@ -1054,7 +1122,7 @@ void EvalState::evalFile(const SourcePath & path_, Value & v, bool mustBeTrivial
e = j->second;
if (!e)
e = &parseExprFromFile(checkSourcePath(resolvedPath));
e = parseExprFromFile(checkSourcePath(resolvedPath));
cacheFile(path, resolvedPath, e, v, mustBeTrivial);
}
@@ -1091,7 +1159,7 @@ void EvalState::cacheFile(
if (mustBeTrivial &&
!(dynamic_cast<ExprAttrs *>(e)))
error<EvalError>("file '%s' must be an attribute set", path).debugThrow();
eval(*e, v);
eval(e, v);
} catch (Error & e) {
addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath.to_string());
throw;
@@ -1102,23 +1170,23 @@ void EvalState::cacheFile(
}
void EvalState::eval(Expr & e, Value & v)
void EvalState::eval(Expr * e, Value & v)
{
e.eval(*this, baseEnv, v);
e->eval(*this, baseEnv, v);
}
inline bool EvalState::evalBool(Env & env, Expr & e, const PosIdx pos, std::string_view errorCtx)
inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos, std::string_view errorCtx)
{
try {
Value v;
e.eval(*this, env, v);
e->eval(*this, env, v);
if (v.type() != nBool)
error<TypeError>(
"expected a Boolean but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).withFrame(env, e).debugThrow();
).atPos(pos).withFrame(env, *e).debugThrow();
return v.boolean;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
@@ -1127,16 +1195,16 @@ inline bool EvalState::evalBool(Env & env, Expr & e, const PosIdx pos, std::stri
}
inline void EvalState::evalAttrs(Env & env, Expr & e, Value & v, const PosIdx pos, std::string_view errorCtx)
inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const PosIdx pos, std::string_view errorCtx)
{
try {
e.eval(*this, env, v);
e->eval(*this, env, v);
if (v.type() != nAttrs)
error<TypeError>(
"expected a set but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).withFrame(env, e).debugThrow();
).withFrame(env, *e).debugThrow();
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
@@ -1179,7 +1247,7 @@ Env * ExprAttrs::buildInheritFromEnv(EvalState & state, Env & up)
inheritEnv.up = &up;
Displacement displ = 0;
for (auto & from : *inheritFromExprs)
for (auto from : *inheritFromExprs)
inheritEnv.values[displ++] = from->maybeThunk(state, up);
return &inheritEnv;
@@ -1209,7 +1277,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
Value * vAttr;
if (hasOverrides && i.second.kind != AttrDef::Kind::Inherited) {
vAttr = state.allocValue();
mkThunk(*vAttr, *i.second.chooseByKind(&env2, &env, inheritEnv), *i.second.e);
mkThunk(*vAttr, *i.second.chooseByKind(&env2, &env, inheritEnv), i.second.e);
} else
vAttr = i.second.e->maybeThunk(state, *i.second.chooseByKind(&env2, &env, inheritEnv));
env2.values[displ++] = vAttr;
@@ -1358,13 +1426,11 @@ static std::string showAttrPath(EvalState & state, Env & env, const AttrPath & a
void ExprSelect::eval(EvalState & state, Env & env, Value & v)
{
Value vFirst;
Value vTmp;
PosIdx pos2;
Value * vAttrs = &vTmp;
// Pointer to the current attrset Value in this select chain.
Value * vCurrent = &vFirst;
// Position for the current attrset Value in this select chain.
PosIdx posCurrent;
e->eval(state, env, vFirst);
e->eval(state, env, vTmp);
try {
auto dts = state.debugRepl
@@ -1377,75 +1443,48 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
showAttrPath(state, env, attrPath))
: nullptr;
for (auto const & currentAttrName : attrPath) {
for (auto & i : attrPath) {
state.nrLookups++;
Symbol const name = getName(currentAttrName, state, env);
state.forceValue(*vCurrent, pos);
if (vCurrent->type() != nAttrs) {
// If we have an `or` provided default,
// then this is allowed to not be an attrset.
if (def != nullptr) {
this->def->eval(state, env, v);
Bindings::iterator j;
auto name = getName(i, state, env);
if (def) {
state.forceValue(*vAttrs, pos);
if (vAttrs->type() != nAttrs ||
(j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
{
def->eval(state, env, v);
return;
}
// Otherwise, we must type error.
state.error<TypeError>(
"expected a set but found %s: %s",
showType(*vCurrent),
ValuePrinter(state, *vCurrent, errorPrintOptions)
).withTrace(pos, "while selecting an attribute").debugThrow();
}
// Now that we know this is actually an attrset, try to find an attr
// with the selected name.
Bindings::iterator attrIt = vCurrent->attrs->find(name);
if (attrIt == vCurrent->attrs->end()) {
// If we have an `or` provided default, then we'll use that.
if (def != nullptr) {
this->def->eval(state, env, v);
return;
} else {
state.forceAttrs(*vAttrs, pos, "while selecting an attribute");
if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) {
std::set<std::string> allAttrNames;
for (auto & attr : *vAttrs->attrs)
allAttrNames.insert(state.symbols[attr.name]);
auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]);
state.error<EvalError>("attribute '%1%' missing", state.symbols[name])
.atPos(pos).withSuggestions(suggestions).withFrame(env, *this).debugThrow();
}
// Otherwise, missing attr error.
std::set<std::string> allAttrNames;
for (auto const & attr : *vCurrent->attrs) {
allAttrNames.insert(state.symbols[attr.name]);
}
auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]);
state.error<EvalError>("attribute '%s' missing", state.symbols[name])
.atPos(pos)
.withSuggestions(suggestions)
.withFrame(env, *this)
.debugThrow();
}
// If we're here, then we successfully found the attribute.
// Set our currently operated-on attrset to this one, and keep going.
vCurrent = attrIt->value;
posCurrent = attrIt->pos;
if (state.countCalls) state.attrSelects[posCurrent]++;
vAttrs = j->value;
pos2 = j->pos;
if (state.countCalls) state.attrSelects[pos2]++;
}
state.forceValue(*vCurrent, (posCurrent ? posCurrent : this->pos));
state.forceValue(*vAttrs, (pos2 ? pos2 : this->pos ) );
} catch (Error & e) {
if (posCurrent) {
auto pos2r = state.positions[posCurrent];
if (pos2) {
auto pos2r = state.positions[pos2];
auto origin = std::get_if<SourcePath>(&pos2r.origin);
if (!(origin && *origin == state.derivationInternal))
state.addErrorTrace(e, posCurrent, "while evaluating the attribute '%1%'",
state.addErrorTrace(e, pos2, "while evaluating the attribute '%1%'",
showAttrPath(state, env, attrPath));
}
throw;
}
v = *vCurrent;
v = *vAttrs;
}
@@ -1494,66 +1533,6 @@ public:
};
};
/** Currently these each just take one, but maybe in the future we could have diagnostics
* for all unexpected and missing arguments?
*/
struct FormalsMatch
{
std::vector<Symbol> missing;
std::vector<Symbol> unexpected;
};
/** Matchup an attribute argument set to a lambda's formal arguments,
* or return what arguments were required but not given, or given but not allowed.
* (currently returns only one, for each).
*/
FormalsMatch matchupFormals(EvalState & state, Env & env, Displacement & displ, ExprLambda const & lambda, Bindings & attrs)
{
size_t attrsUsed = 0;
for (auto const & formal : lambda.formals->formals) {
// The attribute whose name matches the name of the formal we're matching up, if it exists.
Attr const * matchingArg = attrs.get(formal.name);
if (matchingArg) {
attrsUsed += 1;
env.values[displ] = matchingArg->value;
displ += 1;
// We're done here. Move on to the next formal.
continue;
}
// The argument for this formal wasn't given.
// If the formal has a default, use it.
if (formal.def) {
env.values[displ] = formal.def->maybeThunk(state, env);
displ += 1;
} else {
// Otherwise, let our caller know what was missing.
return FormalsMatch{
.missing = {formal.name},
};
}
}
// Check for unexpected extra arguments.
if (!lambda.formals->ellipsis && attrsUsed != attrs.size()) {
// Return the first unexpected argument.
for (Attr const & attr : attrs) {
if (!lambda.formals->has(attr.name)) {
return FormalsMatch{
.unexpected = {attr.name},
};
}
}
abort(); // unreachable.
}
return FormalsMatch{};
}
void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos)
{
if (callDepth > evalSettings.maxCallDepth)
@@ -1607,42 +1586,53 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
if (lambda.arg)
env2.values[displ++] = args[0];
///* For each formal argument, get the actual argument. If
// there is no matching actual argument but the formal
// argument has a default, use the default. */
auto const formalsMatch = matchupFormals(
*this,
env2,
displ,
lambda,
*args[0]->attrs
);
for (auto const & missingArg : formalsMatch.missing) {
auto const missing = symbols[missingArg];
error<TypeError>("function '%s' called without required argument '%s'", lambda.getName(symbols), missing)
.atPos(lambda.pos)
.withTrace(pos, "from call site")
.withFrame(*fun.lambda.env, lambda)
.debugThrow();
}
for (auto const & unexpectedArg : formalsMatch.unexpected) {
auto const unex = symbols[unexpectedArg];
std::set<std::string> formalNames;
for (auto const & formal : lambda.formals->formals) {
formalNames.insert(symbols[formal.name]);
/* For each formal argument, get the actual argument. If
there is no matching actual argument but the formal
argument has a default, use the default. */
size_t attrsUsed = 0;
for (auto & i : lambda.formals->formals) {
auto j = args[0]->attrs->get(i.name);
if (!j) {
if (!i.def) {
error<TypeError>("function '%1%' called without required argument '%2%'",
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
symbols[i.name])
.atPos(lambda.pos)
.withTrace(pos, "from call site")
.withFrame(*fun.lambda.env, lambda)
.debugThrow();
}
env2.values[displ++] = i.def->maybeThunk(*this, env2);
} else {
attrsUsed++;
env2.values[displ++] = j->value;
}
auto sug = Suggestions::bestMatches(formalNames, unex);
error<TypeError>("function '%s' called with unexpected argument '%s'", lambda.getName(symbols), unex)
.atPos(lambda.pos)
.withTrace(pos, "from call site")
.withSuggestions(sug)
.withFrame(*fun.lambda.env, lambda)
.debugThrow();
}
/* Check that each actual argument is listed as a formal
argument (unless the attribute match specifies a `...'). */
if (!lambda.formals->ellipsis && attrsUsed != args[0]->attrs->size()) {
/* Nope, so show the first unexpected argument to the
user. */
for (auto & i : *args[0]->attrs)
if (!lambda.formals->has(i.name)) {
std::set<std::string> formalNames;
for (auto & formal : lambda.formals->formals)
formalNames.insert(symbols[formal.name]);
auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]);
error<TypeError>("function '%1%' called with unexpected argument '%2%'",
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
symbols[i.name])
.atPos(lambda.pos)
.withTrace(pos, "from call site")
.withSuggestions(suggestions)
.withFrame(*fun.lambda.env, lambda)
.debugThrow();
}
abort(); // can't happen
}
}
nrFunctionCalls++;
if (countCalls) incrFunctionCall(&lambda);
@@ -1652,7 +1642,9 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
? makeDebugTraceStacker(
*this, *lambda.body, env2, positions[lambda.pos],
"while calling %s",
lambda.getQuotedName(symbols))
lambda.name
? concatStrings("'", symbols[lambda.name], "'")
: "anonymous lambda")
: nullptr;
lambda.body->eval(*this, env2, vCur);
@@ -1662,7 +1654,9 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
e,
lambda.pos,
"while calling %s",
lambda.getQuotedName(symbols));
lambda.name
? concatStrings("'", symbols[lambda.name], "'")
: "anonymous lambda");
if (pos) addErrorTrace(e, pos, "from call site");
}
throw;
@@ -1878,13 +1872,13 @@ void ExprWith::eval(EvalState & state, Env & env, Value & v)
void ExprIf::eval(EvalState & state, Env & env, Value & v)
{
// We cheat in the parser, and pass the position of the condition as the position of the if itself.
(state.evalBool(env, *cond, pos, "while evaluating a branch condition") ? *then : *else_).eval(state, env, v);
(state.evalBool(env, cond, pos, "while evaluating a branch condition") ? then : else_)->eval(state, env, v);
}
void ExprAssert::eval(EvalState & state, Env & env, Value & v)
{
if (!state.evalBool(env, *cond, pos, "in the condition of the assert statement")) {
if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) {
std::ostringstream out;
cond->show(state.symbols, out);
state.error<AssertionError>("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow();
@@ -1895,7 +1889,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v)
void ExprOpNot::eval(EvalState & state, Env & env, Value & v)
{
v.mkBool(!state.evalBool(env, *e, getPos(), "in the argument of the not operator")); // XXX: FIXME: !
v.mkBool(!state.evalBool(env, e, getPos(), "in the argument of the not operator")); // XXX: FIXME: !
}
@@ -1917,27 +1911,27 @@ void ExprOpNEq::eval(EvalState & state, Env & env, Value & v)
void ExprOpAnd::eval(EvalState & state, Env & env, Value & v)
{
v.mkBool(state.evalBool(env, *e1, pos, "in the left operand of the AND (&&) operator") && state.evalBool(env, *e2, pos, "in the right operand of the AND (&&) operator"));
v.mkBool(state.evalBool(env, e1, pos, "in the left operand of the AND (&&) operator") && state.evalBool(env, e2, pos, "in the right operand of the AND (&&) operator"));
}
void ExprOpOr::eval(EvalState & state, Env & env, Value & v)
{
v.mkBool(state.evalBool(env, *e1, pos, "in the left operand of the OR (||) operator") || state.evalBool(env, *e2, pos, "in the right operand of the OR (||) operator"));
v.mkBool(state.evalBool(env, e1, pos, "in the left operand of the OR (||) operator") || state.evalBool(env, e2, pos, "in the right operand of the OR (||) operator"));
}
void ExprOpImpl::eval(EvalState & state, Env & env, Value & v)
{
v.mkBool(!state.evalBool(env, *e1, pos, "in the left operand of the IMPL (->) operator") || state.evalBool(env, *e2, pos, "in the right operand of the IMPL (->) operator"));
v.mkBool(!state.evalBool(env, e1, pos, "in the left operand of the IMPL (->) operator") || state.evalBool(env, e2, pos, "in the right operand of the IMPL (->) operator"));
}
void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v)
{
Value v1, v2;
state.evalAttrs(env, *e1, v1, pos, "in the left operand of the update (//) operator");
state.evalAttrs(env, *e2, v2, pos, "in the right operand of the update (//) operator");
state.evalAttrs(env, e1, v1, pos, "in the left operand of the update (//) operator");
state.evalAttrs(env, e2, v2, pos, "in the right operand of the update (//) operator");
state.nrOpUpdates++;
@@ -2041,10 +2035,10 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
};
// List of returned strings. References to these Values must NOT be persisted.
SmallTemporaryValueVector<conservativeStackReservation> values(es.size());
SmallTemporaryValueVector<conservativeStackReservation> values(es->size());
Value * vTmpP = values.data();
for (auto & [i_pos, i] : es) {
for (auto & [i_pos, i] : *es) {
Value & vTmp = *vTmpP++;
i->eval(state, env, vTmp);
@@ -2074,7 +2068,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
} else
state.error<EvalError>("cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow();
} else {
if (s.empty()) s.reserve(es.size());
if (s.empty()) s.reserve(es->size());
/* skip canonization of first path, which would only be not
canonized in the first place if it's coming from a ./${foo} type
path */
@@ -2758,39 +2752,39 @@ SourcePath resolveExprPath(SourcePath path)
}
Expr & EvalState::parseExprFromFile(const SourcePath & path)
Expr * EvalState::parseExprFromFile(const SourcePath & path)
{
return parseExprFromFile(path, staticBaseEnv);
}
Expr & EvalState::parseExprFromFile(const SourcePath & path, std::shared_ptr<StaticEnv> & staticEnv)
Expr * EvalState::parseExprFromFile(const SourcePath & path, std::shared_ptr<StaticEnv> & staticEnv)
{
auto buffer = path.readFile();
// readFile hopefully have left some extra space for terminators
buffer.append("\0\0", 2);
return *parse(buffer.data(), buffer.size(), Pos::Origin(path), path.parent(), staticEnv);
return parse(buffer.data(), buffer.size(), Pos::Origin(path), path.parent(), staticEnv);
}
Expr & EvalState::parseExprFromString(std::string s_, const SourcePath & basePath, std::shared_ptr<StaticEnv> & staticEnv)
Expr * EvalState::parseExprFromString(std::string s_, const SourcePath & basePath, std::shared_ptr<StaticEnv> & staticEnv)
{
// NOTE this method (and parseStdin) must take care to *fully copy* their input
// into their respective Pos::Origin until the parser stops overwriting its input
// data.
auto s = make_ref<std::string>(s_);
s_.append("\0\0", 2);
return *parse(s_.data(), s_.size(), Pos::String{.source = s}, basePath, staticEnv);
return parse(s_.data(), s_.size(), Pos::String{.source = s}, basePath, staticEnv);
}
Expr & EvalState::parseExprFromString(std::string s, const SourcePath & basePath)
Expr * EvalState::parseExprFromString(std::string s, const SourcePath & basePath)
{
return parseExprFromString(std::move(s), basePath, staticBaseEnv);
}
Expr & EvalState::parseStdin()
Expr * EvalState::parseStdin()
{
// NOTE this method (and parseExprFromString) must take care to *fully copy* their
// input into their respective Pos::Origin until the parser stops overwriting its
@@ -2800,7 +2794,7 @@ Expr & EvalState::parseStdin()
// drainFD should have left some extra space for terminators
auto s = make_ref<std::string>(buffer);
buffer.append("\0\0", 2);
return *parse(buffer.data(), buffer.size(), Pos::Stdin{.source = s}, rootPath(CanonPath::fromCwd()), staticBaseEnv);
return parse(buffer.data(), buffer.size(), Pos::Stdin{.source = s}, rootPath(CanonPath::fromCwd()), staticBaseEnv);
}
@@ -2889,6 +2883,21 @@ std::optional<std::string> EvalState::resolveSearchPathPath(const SearchPath::Pa
}
Expr * EvalState::parse(
char * text,
size_t length,
Pos::Origin origin,
const SourcePath & basePath,
std::shared_ptr<StaticEnv> & staticEnv)
{
auto result = parseExprFromBuf(text, length, origin, basePath, symbols, positions, exprSymbols);
result->bindVars(*this, staticEnv);
return result;
}
std::string ExternalValueBase::coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const
{
state.error<TypeError>(
+10 -19
View File
@@ -33,10 +33,6 @@ class EvalState;
class StorePath;
struct SingleDerivedPath;
enum RepairFlag : bool;
struct MemoryInputAccessor;
namespace eval_cache {
class EvalCache;
}
/**
@@ -238,11 +234,6 @@ public:
return *new EvalErrorBuilder<T>(*this, args...);
}
/**
* A cache for evaluation caches, so as to reuse the same root value if possible
*/
std::map<const Hash, ref<eval_cache::EvalCache>> evalCaches;
private:
/* Cache for calls to addToStore(); maps source paths to the store
@@ -349,16 +340,16 @@ public:
/**
* Parse a Nix expression from the specified file.
*/
Expr & parseExprFromFile(const SourcePath & path);
Expr & parseExprFromFile(const SourcePath & path, std::shared_ptr<StaticEnv> & staticEnv);
Expr * parseExprFromFile(const SourcePath & path);
Expr * parseExprFromFile(const SourcePath & path, std::shared_ptr<StaticEnv> & staticEnv);
/**
* Parse a Nix expression from the specified string.
*/
Expr & parseExprFromString(std::string s, const SourcePath & basePath, std::shared_ptr<StaticEnv> & staticEnv);
Expr & parseExprFromString(std::string s, const SourcePath & basePath);
Expr * parseExprFromString(std::string s, const SourcePath & basePath, std::shared_ptr<StaticEnv> & staticEnv);
Expr * parseExprFromString(std::string s, const SourcePath & basePath);
Expr & parseStdin();
Expr * parseStdin();
/**
* Evaluate an expression read from the given file to normal
@@ -399,15 +390,15 @@ public:
*
* @param [out] v The resulting is stored here.
*/
void eval(Expr & e, Value & v);
void eval(Expr * e, Value & v);
/**
* Evaluation the expression, then verify that it has the expected
* type.
*/
inline bool evalBool(Env & env, Expr & e);
inline bool evalBool(Env & env, Expr & e, const PosIdx pos, std::string_view errorCtx);
inline void evalAttrs(Env & env, Expr & e, Value & v, const PosIdx pos, std::string_view errorCtx);
inline bool evalBool(Env & env, Expr * e);
inline bool evalBool(Env & env, Expr * e, const PosIdx pos, std::string_view errorCtx);
inline void evalAttrs(Env & env, Expr * e, Value & v, const PosIdx pos, std::string_view errorCtx);
/**
* If `v` is a thunk, enter it and overwrite `v` with the result
@@ -628,7 +619,7 @@ public:
}
void mkList(Value & v, size_t length);
void mkThunk_(Value & v, Expr & expr);
void mkThunk_(Value & v, Expr * expr);
void mkPos(Value & v, PosIdx pos);
/**
+296
View File
@@ -0,0 +1,296 @@
%option reentrant bison-bridge bison-locations
%option align
%option noyywrap
%option never-interactive
%option stack
%option nodefault
%option nounput noyy_top_state
%s DEFAULT
%x STRING
%x IND_STRING
%x INPATH
%x INPATH_SLASH
%x PATH_START
%{
#ifdef __clang__
#pragma clang diagnostic ignored "-Wunneeded-internal-declaration"
#endif
// yacc generates code that uses unannotated fallthrough.
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
#ifdef __clang__
#pragma clang diagnostic ignored "-Wimplicit-fallthrough"
#endif
#include "nixexpr.hh"
#include "parser-tab.hh"
#include "strings.hh"
using namespace nix;
namespace nix {
#define CUR_POS state->at(*yylloc)
static void initLoc(YYLTYPE * loc)
{
loc->first_line = loc->last_line = 0;
loc->first_column = loc->last_column = 0;
}
static void adjustLoc(YYLTYPE * loc, const char * s, size_t len)
{
loc->stash();
loc->first_column = loc->last_column;
loc->last_column += len;
}
// we make use of the fact that the parser receives a private copy of the input
// string and can munge around in it.
static StringToken unescapeStr(SymbolTable & symbols, char * s, size_t length)
{
char * result = s;
char * t = s;
char c;
// the input string is terminated with *two* NULs, so we can safely take
// *one* character after the one being checked against.
while ((c = *s++)) {
if (c == '\\') {
c = *s++;
if (c == 'n') *t = '\n';
else if (c == 'r') *t = '\r';
else if (c == 't') *t = '\t';
else *t = c;
}
else if (c == '\r') {
/* Normalise CR and CR/LF into LF. */
*t = '\n';
if (*s == '\n') s++; /* cr/lf */
}
else *t = c;
t++;
}
return {result, size_t(t - result)};
}
}
#define YY_USER_INIT initLoc(yylloc)
#define YY_USER_ACTION adjustLoc(yylloc, yytext, yyleng);
#define PUSH_STATE(state) yy_push_state(state, yyscanner)
#define POP_STATE() yy_pop_state(yyscanner)
%}
ANY .|\n
ID [a-zA-Z\_][a-zA-Z0-9\_\'\-]*
INT [0-9]+
FLOAT (([1-9][0-9]*\.[0-9]*)|(0?\.[0-9]+))([Ee][+-]?[0-9]+)?
PATH_CHAR [a-zA-Z0-9\.\_\-\+]
PATH {PATH_CHAR}*(\/{PATH_CHAR}+)+\/?
PATH_SEG {PATH_CHAR}*\/
HPATH \~(\/{PATH_CHAR}+)+\/?
HPATH_START \~\/
SPATH \<{PATH_CHAR}+(\/{PATH_CHAR}+)*\>
URI [a-zA-Z][a-zA-Z0-9\+\-\.]*\:[a-zA-Z0-9\%\/\?\:\@\&\=\+\$\,\-\_\.\!\~\*\']+
%%
if { return IF; }
then { return THEN; }
else { return ELSE; }
assert { return ASSERT; }
with { return WITH; }
let { return LET; }
in { return IN; }
rec { return REC; }
inherit { return INHERIT; }
or { return OR_KW; }
\.\.\. { return ELLIPSIS; }
\=\= { return EQ; }
\!\= { return NEQ; }
\<\= { return LEQ; }
\>\= { return GEQ; }
\&\& { return AND; }
\|\| { return OR; }
\-\> { return IMPL; }
\/\/ { return UPDATE; }
\+\+ { return CONCAT; }
{ID} { yylval->id = {yytext, (size_t) yyleng}; return ID; }
{INT} { errno = 0;
std::optional<int64_t> numMay = string2Int<int64_t>(yytext);
if (numMay.has_value()) {
yylval->n = *numMay;
} else {
throw ParseError(ErrorInfo{
.msg = HintFmt("invalid integer '%1%'", yytext),
.pos = state->positions[CUR_POS],
});
}
return INT;
}
{FLOAT} { errno = 0;
yylval->nf = strtod(yytext, 0);
if (errno != 0)
throw ParseError(ErrorInfo{
.msg = HintFmt("invalid float '%1%'", yytext),
.pos = state->positions[CUR_POS],
});
return FLOAT;
}
\$\{ { PUSH_STATE(DEFAULT); return DOLLAR_CURLY; }
\} { /* State INITIAL only exists at the bottom of the stack and is
used as a marker. DEFAULT replaces it everywhere else.
Popping when in INITIAL state causes an empty stack exception,
so don't */
if (YYSTATE != INITIAL)
POP_STATE();
return '}';
}
\{ { PUSH_STATE(DEFAULT); return '{'; }
\" { PUSH_STATE(STRING); return '"'; }
<STRING>([^\$\"\\]|\$[^\{\"\\]|\\{ANY}|\$\\{ANY})*\$/\" |
<STRING>([^\$\"\\]|\$[^\{\"\\]|\\{ANY}|\$\\{ANY})+ {
/* It is impossible to match strings ending with '$' with one
regex because trailing contexts are only valid at the end
of a rule. (A sane but undocumented limitation.) */
yylval->str = unescapeStr(state->symbols, yytext, yyleng);
return STR;
}
<STRING>\$\{ { PUSH_STATE(DEFAULT); return DOLLAR_CURLY; }
<STRING>\" { POP_STATE(); return '"'; }
<STRING>\$|\\|\$\\ {
/* This can only occur when we reach EOF, otherwise the above
(...|\$[^\{\"\\]|\\.|\$\\.)+ would have triggered.
This is technically invalid, but we leave the problem to the
parser who fails with exact location. */
return EOF;
}
\'\'(\ *\n)? { PUSH_STATE(IND_STRING); return IND_STRING_OPEN; }
<IND_STRING>([^\$\']|\$[^\{\']|\'[^\'\$])+ {
yylval->str = {yytext, (size_t) yyleng, true};
return IND_STR;
}
<IND_STRING>\'\'\$ |
<IND_STRING>\$ {
yylval->str = {"$", 1};
return IND_STR;
}
<IND_STRING>\'\'\' {
yylval->str = {"''", 2};
return IND_STR;
}
<IND_STRING>\'\'\\{ANY} {
yylval->str = unescapeStr(state->symbols, yytext + 2, yyleng - 2);
return IND_STR;
}
<IND_STRING>\$\{ { PUSH_STATE(DEFAULT); return DOLLAR_CURLY; }
<IND_STRING>\'\' { POP_STATE(); return IND_STRING_CLOSE; }
<IND_STRING>\' {
yylval->str = {"'", 1};
return IND_STR;
}
{PATH_SEG}\$\{ |
{HPATH_START}\$\{ {
PUSH_STATE(PATH_START);
yyless(0);
yylloc->unstash();
}
<PATH_START>{PATH_SEG} {
POP_STATE();
PUSH_STATE(INPATH_SLASH);
yylval->path = {yytext, (size_t) yyleng};
return PATH;
}
<PATH_START>{HPATH_START} {
POP_STATE();
PUSH_STATE(INPATH_SLASH);
yylval->path = {yytext, (size_t) yyleng};
return HPATH;
}
{PATH} {
if (yytext[yyleng-1] == '/')
PUSH_STATE(INPATH_SLASH);
else
PUSH_STATE(INPATH);
yylval->path = {yytext, (size_t) yyleng};
return PATH;
}
{HPATH} {
if (yytext[yyleng-1] == '/')
PUSH_STATE(INPATH_SLASH);
else
PUSH_STATE(INPATH);
yylval->path = {yytext, (size_t) yyleng};
return HPATH;
}
<INPATH,INPATH_SLASH>\$\{ {
POP_STATE();
PUSH_STATE(INPATH);
PUSH_STATE(DEFAULT);
return DOLLAR_CURLY;
}
<INPATH,INPATH_SLASH>{PATH}|{PATH_SEG}|{PATH_CHAR}+ {
POP_STATE();
if (yytext[yyleng-1] == '/')
PUSH_STATE(INPATH_SLASH);
else
PUSH_STATE(INPATH);
yylval->str = {yytext, (size_t) yyleng};
return STR;
}
<INPATH>{ANY} |
<INPATH><<EOF>> {
/* if we encounter a non-path character we inform the parser that the path has
ended with a PATH_END token and re-parse this character in the default
context (it may be ')', ';', or something of that sort) */
POP_STATE();
yyless(0);
yylloc->unstash();
return PATH_END;
}
<INPATH_SLASH>{ANY} |
<INPATH_SLASH><<EOF>> {
throw ParseError(ErrorInfo{
.msg = HintFmt("path has a trailing slash"),
.pos = state->positions[CUR_POS],
});
}
{SPATH} { yylval->path = {yytext, (size_t) yyleng}; return SPATH; }
{URI} { yylval->uri = {yytext, (size_t) yyleng}; return URI; }
[ \t\r\n]+ /* eat up whitespace */
\#[^\r\n]* /* single-line comments */
\/\*([^*]|\*+[^*/])*\*+\/ /* long comments */
{ANY} {
/* Don't return a negative number, as this will cause
Bison to stop parsing without an error. */
return (unsigned char) yytext[0];
}
%%
+54 -4
View File
@@ -1,3 +1,54 @@
parser_tab = custom_target(
input : 'parser.y',
output : [
'parser-tab.cc',
'parser-tab.hh',
],
command : [
'bison',
'-v',
'-o',
'@OUTPUT0@',
'@INPUT@',
'-d',
],
# NOTE(Qyriad): Meson doesn't support installing only part of a custom target, so we add
# an install script below which removes parser-tab.cc.
install : true,
install_dir : includedir / 'lix/libexpr',
)
lexer_tab = custom_target(
input : [
'lexer.l',
parser_tab,
],
output : [
'lexer-tab.cc',
'lexer-tab.hh',
],
command : [
'flex',
'--outfile',
'@OUTPUT0@',
'--header-file=' + '@OUTPUT1@',
'@INPUT0@',
],
# NOTE(Qyriad): Meson doesn't support installing only part of a custom target, so we add
# an install script below which removes lexer-tab.cc.
install : true,
install_dir : includedir / 'lix/libexpr',
)
# TODO(Qyriad): When the parser and lexer are rewritten this should be removed.
# NOTE(Qyriad): We do this this way instead of an inline bash or rm command
# due to subtleties in Meson. Check the comments in cleanup-install.bash for details.
meson.add_install_script(
bash,
meson.project_source_root() / 'meson/cleanup-install.bash',
'@0@'.format(includedir),
)
libexpr_generated_headers = [
gen_header.process('primops/derivation.nix', preserve_path_from : meson.current_source_dir()),
]
@@ -24,7 +75,6 @@ libexpr_sources = files(
'get-drvs.cc',
'json-to-value.cc',
'nixexpr.cc',
'parser/parser.cc',
'paths.cc',
'primops.cc',
'print-ambiguous.cc',
@@ -60,9 +110,7 @@ libexpr_headers = files(
'get-drvs.hh',
'json-to-value.hh',
'nixexpr.hh',
'parser/change_head.hh',
'parser/grammar.hh',
'parser/state.hh',
'parser-state.hh',
'pos-idx.hh',
'pos-table.hh',
'primops.hh',
@@ -81,6 +129,8 @@ libexpr_headers = files(
libexpr = library(
'lixexpr',
libexpr_sources,
parser_tab,
lexer_tab,
libexpr_generated_headers,
dependencies : [
liblixutil,
+9 -9
View File
@@ -79,7 +79,7 @@ void ExprAttrs::showBindings(const SymbolTable & symbols, std::ostream & str) co
return sa < sb;
});
std::vector<Symbol> inherits;
std::map<Displacement, std::vector<Symbol>> inheritsFrom;
std::map<ExprInheritFrom *, std::vector<Symbol>> inheritsFrom;
for (auto & i : sorted) {
switch (i->second.kind) {
case AttrDef::Kind::Plain:
@@ -90,7 +90,7 @@ void ExprAttrs::showBindings(const SymbolTable & symbols, std::ostream & str) co
case AttrDef::Kind::InheritedFrom: {
auto & select = dynamic_cast<ExprSelect &>(*i->second.e);
auto & from = dynamic_cast<ExprInheritFrom &>(*select.e);
inheritsFrom[from.displ].push_back(i->first);
inheritsFrom[&from].push_back(i->first);
break;
}
}
@@ -102,7 +102,7 @@ void ExprAttrs::showBindings(const SymbolTable & symbols, std::ostream & str) co
}
for (const auto & [from, syms] : inheritsFrom) {
str << "inherit (";
(*inheritFromExprs)[from]->show(symbols, str);
(*inheritFromExprs)[from->displ]->show(symbols, str);
str << ")";
for (auto sym : syms) str << " " << symbols[sym];
str << "; ";
@@ -151,7 +151,7 @@ void ExprLambda::show(const SymbolTable & symbols, std::ostream & str) const
// the natural Symbol ordering is by creation time, which can lead to the
// same expression being printed in two different ways depending on its
// context. always use lexicographic ordering to avoid this.
for (const Formal & i : formals->lexicographicOrder(symbols)) {
for (auto & i : formals->lexicographicOrder(symbols)) {
if (first) first = false; else str << ", ";
str << symbols[i.name];
if (i.def) {
@@ -176,7 +176,7 @@ void ExprCall::show(const SymbolTable & symbols, std::ostream & str) const
{
str << '(';
fun->show(symbols, str);
for (auto & e : args) {
for (auto e : args) {
str << ' ';
e->show(symbols, str);
}
@@ -231,7 +231,7 @@ void ExprConcatStrings::show(const SymbolTable & symbols, std::ostream & str) co
{
bool first = true;
str << "(";
for (auto & i : es) {
for (auto & i : *es) {
if (first) first = false; else str << " + ";
i.second->show(symbols, str);
}
@@ -375,7 +375,7 @@ std::shared_ptr<const StaticEnv> ExprAttrs::bindInheritSources(
// not even *have* an expr that grabs anything from this env since it's fully
// invisible, but the evaluator does not allow for this yet.
auto inner = std::make_shared<StaticEnv>(nullptr, env.get(), 0);
for (auto & from : *inheritFromExprs)
for (auto from : *inheritFromExprs)
from->bindVars(es, env);
return inner;
@@ -462,7 +462,7 @@ void ExprCall::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
es.exprEnvs.insert(std::make_pair(this, env));
fun->bindVars(es, env);
for (auto & e : args)
for (auto e : args)
e->bindVars(es, env);
}
@@ -547,7 +547,7 @@ void ExprConcatStrings::bindVars(EvalState & es, const std::shared_ptr<const Sta
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
for (auto & i : this->es)
for (auto & i : *this->es)
i.second->bindVars(es, env);
}
+45 -89
View File
@@ -28,9 +28,9 @@ struct StaticEnv;
struct AttrName
{
Symbol symbol;
std::unique_ptr<Expr> expr;
Expr * expr;
AttrName(Symbol s) : symbol(s) {};
AttrName(std::unique_ptr<Expr> e) : expr(std::move(e)) {};
AttrName(Expr * e) : expr(e) {};
};
typedef std::vector<AttrName> AttrPath;
@@ -42,21 +42,12 @@ std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath)
struct Expr
{
protected:
Expr(Expr &&) = default;
Expr & operator=(Expr &&) = default;
public:
struct AstSymbols {
Symbol sub, lessThan, mul, div, or_, findFile, nixPath, body;
};
Expr() = default;
Expr(const Expr &) = delete;
Expr & operator=(const Expr &) = delete;
virtual ~Expr() { };
virtual void show(const SymbolTable & symbols, std::ostream & str) const;
virtual void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env);
virtual void eval(EvalState & state, Env & env, Value & v);
@@ -157,29 +148,19 @@ struct ExprInheritFrom : ExprVar
struct ExprSelect : Expr
{
PosIdx pos;
/** The expression attributes are being selected on. e.g. `foo` in `foo.bar.baz`. */
std::unique_ptr<Expr> e;
/** A default value specified with `or`, if the selected attr doesn't exist.
* e.g. `bix` in `foo.bar.baz or bix`
*/
std::unique_ptr<Expr> def;
/** The path of attributes being selected. e.g. `bar.baz` in `foo.bar.baz.` */
Expr * e, * def;
AttrPath attrPath;
ExprSelect(const PosIdx & pos, std::unique_ptr<Expr> e, AttrPath attrPath, std::unique_ptr<Expr> def) : pos(pos), e(std::move(e)), def(std::move(def)), attrPath(std::move(attrPath)) { };
ExprSelect(const PosIdx & pos, std::unique_ptr<Expr> e, Symbol name) : pos(pos), e(std::move(e)) { attrPath.push_back(AttrName(name)); };
ExprSelect(const PosIdx & pos, Expr * e, AttrPath attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(std::move(attrPath)) { };
ExprSelect(const PosIdx & pos, Expr * e, Symbol name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); };
PosIdx getPos() const override { return pos; }
COMMON_METHODS
};
struct ExprOpHasAttr : Expr
{
std::unique_ptr<Expr> e;
Expr * e;
AttrPath attrPath;
ExprOpHasAttr(std::unique_ptr<Expr> e, AttrPath attrPath) : e(std::move(e)), attrPath(std::move(attrPath)) { };
ExprOpHasAttr(Expr * e, AttrPath attrPath) : e(e), attrPath(std::move(attrPath)) { };
PosIdx getPos() const override { return e->getPos(); }
COMMON_METHODS
};
@@ -199,11 +180,11 @@ struct ExprAttrs : Expr
};
Kind kind;
std::unique_ptr<Expr> e;
Expr * e;
PosIdx pos;
Displacement displ; // displacement
AttrDef(std::unique_ptr<Expr> e, const PosIdx & pos, Kind kind = Kind::Plain)
: kind(kind), e(std::move(e)), pos(pos) { };
AttrDef(Expr * e, const PosIdx & pos, Kind kind = Kind::Plain)
: kind(kind), e(e), pos(pos) { };
AttrDef() { };
template<typename T>
@@ -222,12 +203,12 @@ struct ExprAttrs : Expr
};
typedef std::map<Symbol, AttrDef> AttrDefs;
AttrDefs attrs;
std::unique_ptr<std::vector<std::unique_ptr<Expr>>> inheritFromExprs;
std::unique_ptr<std::vector<Expr *>> inheritFromExprs;
struct DynamicAttrDef {
std::unique_ptr<Expr> nameExpr, valueExpr;
Expr * nameExpr, * valueExpr;
PosIdx pos;
DynamicAttrDef(std::unique_ptr<Expr> nameExpr, std::unique_ptr<Expr> valueExpr, const PosIdx & pos)
: nameExpr(std::move(nameExpr)), valueExpr(std::move(valueExpr)), pos(pos) { };
DynamicAttrDef(Expr * nameExpr, Expr * valueExpr, const PosIdx & pos)
: nameExpr(nameExpr), valueExpr(valueExpr), pos(pos) { };
};
typedef std::vector<DynamicAttrDef> DynamicAttrDefs;
DynamicAttrDefs dynamicAttrs;
@@ -244,7 +225,7 @@ struct ExprAttrs : Expr
struct ExprList : Expr
{
std::vector<std::unique_ptr<Expr>> elems;
std::vector<Expr *> elems;
ExprList() { };
COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env) override;
@@ -259,7 +240,7 @@ struct Formal
{
PosIdx pos;
Symbol name;
std::unique_ptr<Expr> def;
Expr * def;
};
/** Attribute set destructuring in arguments of a lambda, if present */
@@ -276,9 +257,9 @@ struct Formals
return it != formals.end() && it->name == arg;
}
std::vector<std::reference_wrapper<const Formal>> lexicographicOrder(const SymbolTable & symbols) const
std::vector<Formal> lexicographicOrder(const SymbolTable & symbols) const
{
std::vector<std::reference_wrapper<const Formal>> result(formals.begin(), formals.end());
std::vector<Formal> result(formals.begin(), formals.end());
std::sort(result.begin(), result.end(),
[&] (const Formal & a, const Formal & b) {
std::string_view sa = symbols[a.name], sb = symbols[b.name];
@@ -302,55 +283,30 @@ struct ExprLambda : Expr
Symbol arg;
/** Formals are present when the lambda destructures an attr set as
* argument, with or without ellipsis */
std::unique_ptr<Formals> formals;
std::unique_ptr<Expr> body;
ExprLambda(PosIdx pos, Symbol arg, std::unique_ptr<Formals> formals, std::unique_ptr<Expr> body)
: pos(pos), arg(arg), formals(std::move(formals)), body(std::move(body))
Formals * formals;
Expr * body;
ExprLambda(PosIdx pos, Symbol arg, Formals * formals, Expr * body)
: pos(pos), arg(arg), formals(formals), body(body)
{
};
ExprLambda(PosIdx pos, std::unique_ptr<Formals> formals, std::unique_ptr<Expr> body)
: pos(pos), formals(std::move(formals)), body(std::move(body))
ExprLambda(PosIdx pos, Formals * formals, Expr * body)
: pos(pos), formals(formals), body(body)
{
}
void setName(Symbol name) override;
std::string showNamePos(const EvalState & state) const;
inline bool hasFormals() const { return formals != nullptr; }
PosIdx getPos() const override { return pos; }
/** Returns the name of the lambda,
* or "anonymous lambda" if it doesn't have one.
*/
inline std::string getName(SymbolTable const & symbols) const
{
if (this->name) {
return symbols[this->name];
}
return "anonymous lambda";
}
/** Returns the name of the lambda in single quotes,
* or "anonymous lambda" if it doesn't have one.
*/
inline std::string getQuotedName(SymbolTable const & symbols) const
{
if (this->name) {
return concatStrings("'", symbols[this->name], "'");
}
return "anonymous lambda";
}
COMMON_METHODS
};
struct ExprCall : Expr
{
std::unique_ptr<Expr> fun;
std::vector<std::unique_ptr<Expr>> args;
Expr * fun;
std::vector<Expr *> args;
PosIdx pos;
ExprCall(const PosIdx & pos, std::unique_ptr<Expr> fun, std::vector<std::unique_ptr<Expr>> && args)
: fun(std::move(fun)), args(std::move(args)), pos(pos)
ExprCall(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args)
: fun(fun), args(args), pos(pos)
{ }
PosIdx getPos() const override { return pos; }
COMMON_METHODS
@@ -358,19 +314,19 @@ struct ExprCall : Expr
struct ExprLet : Expr
{
std::unique_ptr<ExprAttrs> attrs;
std::unique_ptr<Expr> body;
ExprLet(std::unique_ptr<ExprAttrs> attrs, std::unique_ptr<Expr> body) : attrs(std::move(attrs)), body(std::move(body)) { };
ExprAttrs * attrs;
Expr * body;
ExprLet(ExprAttrs * attrs, Expr * body) : attrs(attrs), body(body) { };
COMMON_METHODS
};
struct ExprWith : Expr
{
PosIdx pos;
std::unique_ptr<Expr> attrs, body;
Expr * attrs, * body;
size_t prevWith;
ExprWith * parentWith;
ExprWith(const PosIdx & pos, std::unique_ptr<Expr> attrs, std::unique_ptr<Expr> body) : pos(pos), attrs(std::move(attrs)), body(std::move(body)) { };
ExprWith(const PosIdx & pos, Expr * attrs, Expr * body) : pos(pos), attrs(attrs), body(body) { };
PosIdx getPos() const override { return pos; }
COMMON_METHODS
};
@@ -378,8 +334,8 @@ struct ExprWith : Expr
struct ExprIf : Expr
{
PosIdx pos;
std::unique_ptr<Expr> cond, then, else_;
ExprIf(const PosIdx & pos, std::unique_ptr<Expr> cond, std::unique_ptr<Expr> then, std::unique_ptr<Expr> else_) : pos(pos), cond(std::move(cond)), then(std::move(then)), else_(std::move(else_)) { };
Expr * cond, * then, * else_;
ExprIf(const PosIdx & pos, Expr * cond, Expr * then, Expr * else_) : pos(pos), cond(cond), then(then), else_(else_) { };
PosIdx getPos() const override { return pos; }
COMMON_METHODS
};
@@ -387,16 +343,16 @@ struct ExprIf : Expr
struct ExprAssert : Expr
{
PosIdx pos;
std::unique_ptr<Expr> cond, body;
ExprAssert(const PosIdx & pos, std::unique_ptr<Expr> cond, std::unique_ptr<Expr> body) : pos(pos), cond(std::move(cond)), body(std::move(body)) { };
Expr * cond, * body;
ExprAssert(const PosIdx & pos, Expr * cond, Expr * body) : pos(pos), cond(cond), body(body) { };
PosIdx getPos() const override { return pos; }
COMMON_METHODS
};
struct ExprOpNot : Expr
{
std::unique_ptr<Expr> e;
ExprOpNot(std::unique_ptr<Expr> e) : e(std::move(e)) { };
Expr * e;
ExprOpNot(Expr * e) : e(e) { };
PosIdx getPos() const override { return e->getPos(); }
COMMON_METHODS
};
@@ -405,9 +361,9 @@ struct ExprOpNot : Expr
struct name : Expr \
{ \
PosIdx pos; \
std::unique_ptr<Expr> e1, e2; \
name(std::unique_ptr<Expr> e1, std::unique_ptr<Expr> e2) : e1(std::move(e1)), e2(std::move(e2)) { }; \
name(const PosIdx & pos, std::unique_ptr<Expr> e1, std::unique_ptr<Expr> e2) : pos(pos), e1(std::move(e1)), e2(std::move(e2)) { }; \
Expr * e1, * e2; \
name(Expr * e1, Expr * e2) : e1(e1), e2(e2) { }; \
name(const PosIdx & pos, Expr * e1, Expr * e2) : pos(pos), e1(e1), e2(e2) { }; \
void show(const SymbolTable & symbols, std::ostream & str) const override \
{ \
str << "("; e1->show(symbols, str); str << " " s " "; e2->show(symbols, str); str << ")"; \
@@ -432,9 +388,9 @@ struct ExprConcatStrings : Expr
{
PosIdx pos;
bool forceString;
std::vector<std::pair<PosIdx, std::unique_ptr<Expr>>> es;
ExprConcatStrings(const PosIdx & pos, bool forceString, std::vector<std::pair<PosIdx, std::unique_ptr<Expr>>> es)
: pos(pos), forceString(forceString), es(std::move(es)) { };
std::vector<std::pair<PosIdx, Expr *>> * es;
ExprConcatStrings(const PosIdx & pos, bool forceString, std::vector<std::pair<PosIdx, Expr *>> * es)
: pos(pos), forceString(forceString), es(es) { };
PosIdx getPos() const override { return pos; }
COMMON_METHODS
};
@@ -3,44 +3,59 @@
#include "eval.hh"
namespace nix::parser {
namespace nix {
/**
* @note Storing a C-style `char *` and `size_t` allows us to avoid
* having to define the special members that using string_view here
* would implicitly delete.
*/
struct StringToken
{
std::string_view s;
const char * p;
size_t l;
bool hasIndentation;
operator std::string_view() const { return s; }
operator std::string_view() const { return {p, l}; }
};
struct State
struct ParserLocation
{
int first_line, first_column;
int last_line, last_column;
// backup to recover from yyless(0)
int stashed_first_column, stashed_last_column;
void stash() {
stashed_first_column = first_column;
stashed_last_column = last_column;
}
void unstash() {
first_column = stashed_first_column;
last_column = stashed_last_column;
}
};
struct ParserState
{
SymbolTable & symbols;
PosTable & positions;
Expr * result;
SourcePath basePath;
PosTable::Origin origin;
const Expr::AstSymbols & s;
void dupAttr(const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos);
void dupAttr(Symbol attr, const PosIdx pos, const PosIdx prevPos);
void addAttr(ExprAttrs * attrs, AttrPath && attrPath, std::unique_ptr<Expr> e, const PosIdx pos);
std::unique_ptr<Formals> validateFormals(std::unique_ptr<Formals> formals, PosIdx pos = noPos, Symbol arg = {});
std::unique_ptr<Expr> stripIndentation(const PosIdx pos,
std::vector<std::pair<PosIdx, std::variant<std::unique_ptr<Expr>, StringToken>>> && es);
// lazy positioning means we don't get byte offsets directly, in.position() would work
// but also requires line and column (which is expensive)
PosIdx at(const auto & in)
{
return positions.add(origin, in.begin() - in.input().begin());
}
PosIdx atEnd(const auto & in)
{
return positions.add(origin, in.end() - in.input().begin());
}
void addAttr(ExprAttrs * attrs, AttrPath && attrPath, Expr * e, const PosIdx pos);
Formals * validateFormals(Formals * formals, PosIdx pos = noPos, Symbol arg = {});
Expr * stripIndentation(const PosIdx pos,
std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken>>> && es);
PosIdx at(const ParserLocation & loc);
};
inline void State::dupAttr(const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos)
inline void ParserState::dupAttr(const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos)
{
throw ParseError({
.msg = HintFmt("attribute '%1%' already defined at %2%",
@@ -49,7 +64,7 @@ inline void State::dupAttr(const AttrPath & attrPath, const PosIdx pos, const Po
});
}
inline void State::dupAttr(Symbol attr, const PosIdx pos, const PosIdx prevPos)
inline void ParserState::dupAttr(Symbol attr, const PosIdx pos, const PosIdx prevPos)
{
throw ParseError({
.msg = HintFmt("attribute '%1%' already defined at %2%", symbols[attr], positions[prevPos]),
@@ -57,7 +72,7 @@ inline void State::dupAttr(Symbol attr, const PosIdx pos, const PosIdx prevPos)
});
}
inline void State::addAttr(ExprAttrs * attrs, AttrPath && attrPath, std::unique_ptr<Expr> e, const PosIdx pos)
inline void ParserState::addAttr(ExprAttrs * attrs, AttrPath && attrPath, Expr * e, const PosIdx pos)
{
AttrPath::iterator i;
// All attrpaths have at least one attr
@@ -69,25 +84,20 @@ inline void State::addAttr(ExprAttrs * attrs, AttrPath && attrPath, std::unique_
ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol);
if (j != attrs->attrs.end()) {
if (j->second.kind != ExprAttrs::AttrDef::Kind::Inherited) {
ExprAttrs * attrs2 = dynamic_cast<ExprAttrs *>(j->second.e.get());
if (!attrs2) {
attrPath.erase(i + 1, attrPath.end());
dupAttr(attrPath, pos, j->second.pos);
}
ExprAttrs * attrs2 = dynamic_cast<ExprAttrs *>(j->second.e);
if (!attrs2) dupAttr({attrPath.begin(), i + 1}, pos, j->second.pos);
attrs = attrs2;
} else {
attrPath.erase(i + 1, attrPath.end());
dupAttr(attrPath, pos, j->second.pos);
}
} else
dupAttr({attrPath.begin(), i + 1}, pos, j->second.pos);
} else {
auto next = attrs->attrs.emplace(std::piecewise_construct,
std::tuple(i->symbol),
std::tuple(std::make_unique<ExprAttrs>(), pos));
attrs = static_cast<ExprAttrs *>(next.first->second.e.get());
ExprAttrs * nested = new ExprAttrs;
attrs->attrs[i->symbol] = ExprAttrs::AttrDef(nested, pos);
attrs = nested;
}
} else {
auto & next = attrs->dynamicAttrs.emplace_back(std::move(i->expr), std::make_unique<ExprAttrs>(), pos);
attrs = static_cast<ExprAttrs *>(next.valueExpr.get());
ExprAttrs *nested = new ExprAttrs;
attrs->dynamicAttrs.push_back(ExprAttrs::DynamicAttrDef(i->expr, nested, pos));
attrs = nested;
}
}
// Expr insertion.
@@ -99,41 +109,41 @@ inline void State::addAttr(ExprAttrs * attrs, AttrPath && attrPath, std::unique_
// e and the expr pointed by the attr path are two attribute sets,
// we want to merge them.
// Otherwise, throw an error.
auto * ae = dynamic_cast<ExprAttrs *>(e.get());
auto * jAttrs = dynamic_cast<ExprAttrs *>(j->second.e.get());
auto ae = dynamic_cast<ExprAttrs *>(e);
auto jAttrs = dynamic_cast<ExprAttrs *>(j->second.e);
if (jAttrs && ae) {
if (ae->inheritFromExprs && !jAttrs->inheritFromExprs)
jAttrs->inheritFromExprs = std::make_unique<std::vector<std::unique_ptr<Expr>>>();
jAttrs->inheritFromExprs = std::make_unique<std::vector<Expr *>>();
for (auto & ad : ae->attrs) {
auto j2 = jAttrs->attrs.find(ad.first);
if (j2 != jAttrs->attrs.end()) // Attr already defined in iAttrs, error.
return dupAttr(ad.first, j2->second.pos, ad.second.pos);
dupAttr(ad.first, j2->second.pos, ad.second.pos);
jAttrs->attrs.emplace(ad.first, ad.second);
if (ad.second.kind == ExprAttrs::AttrDef::Kind::InheritedFrom) {
auto & sel = dynamic_cast<ExprSelect &>(*ad.second.e);
auto & from = dynamic_cast<ExprInheritFrom &>(*sel.e);
from.displ += jAttrs->inheritFromExprs->size();
}
jAttrs->attrs.emplace(ad.first, std::move(ad.second));
}
std::ranges::move(ae->dynamicAttrs, std::back_inserter(jAttrs->dynamicAttrs));
if (ae->inheritFromExprs)
std::ranges::move(*ae->inheritFromExprs, std::back_inserter(*jAttrs->inheritFromExprs));
jAttrs->dynamicAttrs.insert(jAttrs->dynamicAttrs.end(), ae->dynamicAttrs.begin(), ae->dynamicAttrs.end());
if (ae->inheritFromExprs) {
jAttrs->inheritFromExprs->insert(jAttrs->inheritFromExprs->end(),
ae->inheritFromExprs->begin(), ae->inheritFromExprs->end());
}
} else {
dupAttr(attrPath, pos, j->second.pos);
}
} else {
// This attr path is not defined. Let's create it.
attrs->attrs.emplace(i->symbol, ExprAttrs::AttrDef(e, pos));
e->setName(i->symbol);
attrs->attrs.emplace(std::piecewise_construct,
std::tuple(i->symbol),
std::tuple(std::move(e), pos));
}
} else {
attrs->dynamicAttrs.emplace_back(std::move(i->expr), std::move(e), pos);
attrs->dynamicAttrs.push_back(ExprAttrs::DynamicAttrDef(i->expr, e, pos));
}
}
inline std::unique_ptr<Formals> State::validateFormals(std::unique_ptr<Formals> formals, PosIdx pos, Symbol arg)
inline Formals * ParserState::validateFormals(Formals * formals, PosIdx pos, Symbol arg)
{
std::sort(formals->formals.begin(), formals->formals.end(),
[] (const auto & a, const auto & b) {
@@ -162,10 +172,10 @@ inline std::unique_ptr<Formals> State::validateFormals(std::unique_ptr<Formals>
return formals;
}
inline std::unique_ptr<Expr> State::stripIndentation(const PosIdx pos,
std::vector<std::pair<PosIdx, std::variant<std::unique_ptr<Expr>, StringToken>>> && es)
inline Expr * ParserState::stripIndentation(const PosIdx pos,
std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken>>> && es)
{
if (es.empty()) return std::make_unique<ExprString>("");
if (es.empty()) return new ExprString("");
/* Figure out the minimum indentation. Note that by design
whitespace-only final lines are not taken into account. (So
@@ -183,11 +193,11 @@ inline std::unique_ptr<Expr> State::stripIndentation(const PosIdx pos,
}
continue;
}
for (size_t j = 0; j < str->s.size(); ++j) {
for (size_t j = 0; j < str->l; ++j) {
if (atStartOfLine) {
if (str->s[j] == ' ')
if (str->p[j] == ' ')
curIndent++;
else if (str->s[j] == '\n') {
else if (str->p[j] == '\n') {
/* Empty line, doesn't influence minimum
indentation. */
curIndent = 0;
@@ -195,7 +205,7 @@ inline std::unique_ptr<Expr> State::stripIndentation(const PosIdx pos,
atStartOfLine = false;
if (curIndent < minIndent) minIndent = curIndent;
}
} else if (str->s[j] == '\n') {
} else if (str->p[j] == '\n') {
atStartOfLine = true;
curIndent = 0;
}
@@ -203,35 +213,35 @@ inline std::unique_ptr<Expr> State::stripIndentation(const PosIdx pos,
}
/* Strip spaces from each line. */
std::vector<std::pair<PosIdx, std::unique_ptr<Expr>>> es2;
auto * es2 = new std::vector<std::pair<PosIdx, Expr *>>;
atStartOfLine = true;
size_t curDropped = 0;
size_t n = es.size();
auto i = es.begin();
const auto trimExpr = [&] (std::unique_ptr<Expr> e) {
const auto trimExpr = [&] (Expr * e) {
atStartOfLine = false;
curDropped = 0;
es2.emplace_back(i->first, std::move(e));
es2->emplace_back(i->first, e);
};
const auto trimString = [&] (const StringToken t) {
const auto trimString = [&] (const StringToken & t) {
std::string s2;
for (size_t j = 0; j < t.s.size(); ++j) {
for (size_t j = 0; j < t.l; ++j) {
if (atStartOfLine) {
if (t.s[j] == ' ') {
if (t.p[j] == ' ') {
if (curDropped++ >= minIndent)
s2 += t.s[j];
s2 += t.p[j];
}
else if (t.s[j] == '\n') {
else if (t.p[j] == '\n') {
curDropped = 0;
s2 += t.s[j];
s2 += t.p[j];
} else {
atStartOfLine = false;
curDropped = 0;
s2 += t.s[j];
s2 += t.p[j];
}
} else {
s2 += t.s[j];
if (t.s[j] == '\n') atStartOfLine = true;
s2 += t.p[j];
if (t.p[j] == '\n') atStartOfLine = true;
}
}
@@ -243,17 +253,24 @@ inline std::unique_ptr<Expr> State::stripIndentation(const PosIdx pos,
s2 = std::string(s2, 0, p + 1);
}
es2.emplace_back(i->first, std::make_unique<ExprString>(std::move(s2)));
es2->emplace_back(i->first, new ExprString(std::move(s2)));
};
for (; i != es.end(); ++i, --n) {
std::visit(overloaded { trimExpr, trimString }, std::move(i->second));
std::visit(overloaded { trimExpr, trimString }, i->second);
}
/* If this is a single string, then don't do a concatenation. */
if (es2.size() == 1 && dynamic_cast<ExprString *>(es2[0].second.get())) {
return std::move(es2[0].second);
if (es2->size() == 1 && dynamic_cast<ExprString *>((*es2)[0].second)) {
auto *const result = (*es2)[0].second;
delete es2;
return result;
}
return std::make_unique<ExprConcatStrings>(pos, true, std::move(es2));
return new ExprConcatStrings(pos, true, es2);
}
inline PosIdx ParserState::at(const ParserLocation & loc)
{
return positions.add(origin, loc.first_column);
}
}
+452
View File
@@ -0,0 +1,452 @@
%glr-parser
%define api.pure
%locations
%define parse.error verbose
%defines
/* %no-lines */
%parse-param { void * scanner }
%parse-param { nix::ParserState * state }
%lex-param { void * scanner }
%lex-param { nix::ParserState * state }
%expect 1
%expect-rr 1
%code requires {
#ifndef BISON_HEADER
#define BISON_HEADER
#include <variant>
#include "finally.hh"
#include "users.hh"
#include "nixexpr.hh"
#include "eval.hh"
#include "eval-settings.hh"
#include "globals.hh"
#include "parser-state.hh"
#define YYLTYPE ::nix::ParserLocation
#define YY_DECL int yylex \
(YYSTYPE * yylval_param, YYLTYPE * yylloc_param, yyscan_t yyscanner, nix::ParserState * state)
namespace nix {
Expr * parseExprFromBuf(
char * text,
size_t length,
Pos::Origin origin,
const SourcePath & basePath,
SymbolTable & symbols,
PosTable & positions,
const Expr::AstSymbols & astSymbols);
}
#endif
}
%{
#include "parser-tab.hh"
#include "lexer-tab.hh"
YY_DECL;
using namespace nix;
#define CUR_POS state->at(*yylocp)
void yyerror(YYLTYPE * loc, yyscan_t scanner, ParserState * state, const char * error)
{
if (std::string_view(error).starts_with("syntax error, unexpected end of file")) {
loc->first_column = loc->last_column;
loc->first_line = loc->last_line;
}
throw ParseError({
.msg = HintFmt(error),
.pos = state->positions[state->at(*loc)]
});
}
%}
%union {
// !!! We're probably leaking stuff here.
nix::Expr * e;
nix::ExprList * list;
nix::ExprAttrs * attrs;
nix::Formals * formals;
nix::Formal * formal;
nix::NixInt n;
nix::NixFloat nf;
nix::StringToken id; // !!! -> Symbol
nix::StringToken path;
nix::StringToken uri;
nix::StringToken str;
std::vector<nix::AttrName> * attrNames;
std::vector<std::pair<nix::AttrName, nix::PosIdx>> * inheritAttrs;
std::vector<std::pair<nix::PosIdx, nix::Expr *>> * string_parts;
std::vector<std::pair<nix::PosIdx, std::variant<nix::Expr *, nix::StringToken>>> * ind_string_parts;
}
%type <e> start expr expr_function expr_if expr_op
%type <e> expr_select expr_simple expr_app
%type <list> expr_list
%type <attrs> binds
%type <formals> formals
%type <formal> formal
%type <attrNames> attrpath
%type <inheritAttrs> attrs
%type <string_parts> string_parts_interpolated
%type <ind_string_parts> ind_string_parts
%type <e> path_start string_parts string_attr
%type <id> attr
%token <id> ID
%token <str> STR IND_STR
%token <n> INT
%token <nf> FLOAT
%token <path> PATH HPATH SPATH PATH_END
%token <uri> URI
%token IF THEN ELSE ASSERT WITH LET IN REC INHERIT EQ NEQ AND OR IMPL OR_KW
%token DOLLAR_CURLY /* == ${ */
%token IND_STRING_OPEN IND_STRING_CLOSE
%token ELLIPSIS
%right IMPL
%left OR
%left AND
%nonassoc EQ NEQ
%nonassoc '<' '>' LEQ GEQ
%right UPDATE
%left NOT
%left '+' '-'
%left '*' '/'
%right CONCAT
%nonassoc '?'
%nonassoc NEGATE
%%
start: expr { state->result = $1; };
expr: expr_function;
expr_function
: ID ':' expr_function
{ $$ = new ExprLambda(CUR_POS, state->symbols.create($1), 0, $3); }
| '{' formals '}' ':' expr_function
{ $$ = new ExprLambda(CUR_POS, state->validateFormals($2), $5); }
| '{' formals '}' '@' ID ':' expr_function
{
auto arg = state->symbols.create($5);
$$ = new ExprLambda(CUR_POS, arg, state->validateFormals($2, CUR_POS, arg), $7);
}
| ID '@' '{' formals '}' ':' expr_function
{
auto arg = state->symbols.create($1);
$$ = new ExprLambda(CUR_POS, arg, state->validateFormals($4, CUR_POS, arg), $7);
}
| ASSERT expr ';' expr_function
{ $$ = new ExprAssert(CUR_POS, $2, $4); }
| WITH expr ';' expr_function
{ $$ = new ExprWith(CUR_POS, $2, $4); }
| LET binds IN expr_function
{ if (!$2->dynamicAttrs.empty())
throw ParseError({
.msg = HintFmt("dynamic attributes not allowed in let"),
.pos = state->positions[CUR_POS]
});
$$ = new ExprLet($2, $4);
}
| expr_if
;
expr_if
: IF expr THEN expr ELSE expr { $$ = new ExprIf(CUR_POS, $2, $4, $6); }
| expr_op
;
expr_op
: '!' expr_op %prec NOT { $$ = new ExprOpNot($2); }
| '-' expr_op %prec NEGATE { $$ = new ExprCall(CUR_POS, new ExprVar(state->s.sub), {new ExprInt(0), $2}); }
| expr_op EQ expr_op { $$ = new ExprOpEq($1, $3); }
| expr_op NEQ expr_op { $$ = new ExprOpNEq($1, $3); }
| expr_op '<' expr_op { $$ = new ExprCall(state->at(@2), new ExprVar(state->s.lessThan), {$1, $3}); }
| expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprCall(state->at(@2), new ExprVar(state->s.lessThan), {$3, $1})); }
| expr_op '>' expr_op { $$ = new ExprCall(state->at(@2), new ExprVar(state->s.lessThan), {$3, $1}); }
| expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprCall(state->at(@2), new ExprVar(state->s.lessThan), {$1, $3})); }
| expr_op AND expr_op { $$ = new ExprOpAnd(state->at(@2), $1, $3); }
| expr_op OR expr_op { $$ = new ExprOpOr(state->at(@2), $1, $3); }
| expr_op IMPL expr_op { $$ = new ExprOpImpl(state->at(@2), $1, $3); }
| expr_op UPDATE expr_op { $$ = new ExprOpUpdate(state->at(@2), $1, $3); }
| expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, std::move(*$3)); delete $3; }
| expr_op '+' expr_op
{ $$ = new ExprConcatStrings(state->at(@2), false, new std::vector<std::pair<PosIdx, Expr *> >({{state->at(@1), $1}, {state->at(@3), $3}})); }
| expr_op '-' expr_op { $$ = new ExprCall(state->at(@2), new ExprVar(state->s.sub), {$1, $3}); }
| expr_op '*' expr_op { $$ = new ExprCall(state->at(@2), new ExprVar(state->s.mul), {$1, $3}); }
| expr_op '/' expr_op { $$ = new ExprCall(state->at(@2), new ExprVar(state->s.div), {$1, $3}); }
| expr_op CONCAT expr_op { $$ = new ExprOpConcatLists(state->at(@2), $1, $3); }
| expr_app
;
expr_app
: expr_app expr_select {
if (auto e2 = dynamic_cast<ExprCall *>($1)) {
e2->args.push_back($2);
$$ = $1;
} else
$$ = new ExprCall(CUR_POS, $1, {$2});
}
| expr_select
;
expr_select
: expr_simple '.' attrpath
{ $$ = new ExprSelect(CUR_POS, $1, std::move(*$3), nullptr); delete $3; }
| expr_simple '.' attrpath OR_KW expr_select
{ $$ = new ExprSelect(CUR_POS, $1, std::move(*$3), $5); delete $3; }
| /* Backwards compatibility: because Nixpkgs has a rarely used
function named or, allow stuff like map or [...]. */
expr_simple OR_KW
{ $$ = new ExprCall(CUR_POS, $1, {new ExprVar(CUR_POS, state->s.or_)}); }
| expr_simple
;
expr_simple
: ID {
std::string_view s = "__curPos";
if ($1.l == s.size() && strncmp($1.p, s.data(), s.size()) == 0)
$$ = new ExprPos(CUR_POS);
else
$$ = new ExprVar(CUR_POS, state->symbols.create($1));
}
| INT { $$ = new ExprInt($1); }
| FLOAT { $$ = new ExprFloat($1); }
| '"' string_parts '"' { $$ = $2; }
| IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE {
$$ = state->stripIndentation(CUR_POS, std::move(*$2));
delete $2;
}
| path_start PATH_END
| path_start string_parts_interpolated PATH_END {
$2->insert($2->begin(), {state->at(@1), $1});
$$ = new ExprConcatStrings(CUR_POS, false, $2);
}
| SPATH {
std::string path($1.p + 1, $1.l - 2);
$$ = new ExprCall(CUR_POS,
new ExprVar(state->s.findFile),
{new ExprVar(state->s.nixPath),
new ExprString(std::move(path))});
}
| URI {
static bool noURLLiterals = experimentalFeatureSettings.isEnabled(Xp::NoUrlLiterals);
if (noURLLiterals)
throw ParseError({
.msg = HintFmt("URL literals are disabled"),
.pos = state->positions[CUR_POS]
});
$$ = new ExprString(std::string($1));
}
| '(' expr ')' { $$ = $2; }
/* Let expressions `let {..., body = ...}' are just desugared
into `(rec {..., body = ...}).body'. */
| LET '{' binds '}'
{ $3->recursive = true; $$ = new ExprSelect(noPos, $3, state->s.body); }
| REC '{' binds '}'
{ $3->recursive = true; $$ = $3; }
| '{' binds '}'
{ $$ = $2; }
| '[' expr_list ']' { $$ = $2; }
;
string_parts
: STR { $$ = new ExprString(std::string($1)); }
| string_parts_interpolated { $$ = new ExprConcatStrings(CUR_POS, true, $1); }
| { $$ = new ExprString(""); }
;
string_parts_interpolated
: string_parts_interpolated STR
{ $$ = $1; $1->emplace_back(state->at(@2), new ExprString(std::string($2))); }
| string_parts_interpolated DOLLAR_CURLY expr '}' { $$ = $1; $1->emplace_back(state->at(@2), $3); }
| DOLLAR_CURLY expr '}' { $$ = new std::vector<std::pair<PosIdx, Expr *>>; $$->emplace_back(state->at(@1), $2); }
| STR DOLLAR_CURLY expr '}' {
$$ = new std::vector<std::pair<PosIdx, Expr *>>;
$$->emplace_back(state->at(@1), new ExprString(std::string($1)));
$$->emplace_back(state->at(@2), $3);
}
;
path_start
: PATH {
Path path(absPath({$1.p, $1.l}, state->basePath.path.abs()));
/* add back in the trailing '/' to the first segment */
if ($1.p[$1.l-1] == '/' && $1.l > 1)
path += "/";
$$ = new ExprPath(path);
}
| HPATH {
if (evalSettings.pureEval) {
throw Error(
"the path '%s' can not be resolved in pure mode",
std::string_view($1.p, $1.l)
);
}
Path path(getHome() + std::string($1.p + 1, $1.l - 1));
$$ = new ExprPath(path);
}
;
ind_string_parts
: ind_string_parts IND_STR { $$ = $1; $1->emplace_back(state->at(@2), $2); }
| ind_string_parts DOLLAR_CURLY expr '}' { $$ = $1; $1->emplace_back(state->at(@2), $3); }
| { $$ = new std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken>>>; }
;
binds
: binds attrpath '=' expr ';' { $$ = $1; state->addAttr($$, std::move(*$2), $4, state->at(@2)); delete $2; }
| binds INHERIT attrs ';'
{ $$ = $1;
for (auto & [i, iPos] : *$3) {
if ($$->attrs.find(i.symbol) != $$->attrs.end())
state->dupAttr(i.symbol, iPos, $$->attrs[i.symbol].pos);
$$->attrs.emplace(
i.symbol,
ExprAttrs::AttrDef(new ExprVar(iPos, i.symbol), iPos, ExprAttrs::AttrDef::Kind::Inherited));
}
delete $3;
}
| binds INHERIT '(' expr ')' attrs ';'
{ $$ = $1;
if (!$$->inheritFromExprs)
$$->inheritFromExprs = std::make_unique<std::vector<Expr *>>();
$$->inheritFromExprs->push_back($4);
auto from = new nix::ExprInheritFrom(state->at(@4), $$->inheritFromExprs->size() - 1);
for (auto & [i, iPos] : *$6) {
if ($$->attrs.find(i.symbol) != $$->attrs.end())
state->dupAttr(i.symbol, iPos, $$->attrs[i.symbol].pos);
$$->attrs.emplace(
i.symbol,
ExprAttrs::AttrDef(
new ExprSelect(iPos, from, i.symbol),
iPos,
ExprAttrs::AttrDef::Kind::InheritedFrom));
}
delete $6;
}
| { $$ = new ExprAttrs(state->at(@0)); }
;
attrs
: attrs attr { $$ = $1; $1->emplace_back(AttrName(state->symbols.create($2)), state->at(@2)); }
| attrs string_attr
{ $$ = $1;
ExprString * str = dynamic_cast<ExprString *>($2);
if (str) {
$$->emplace_back(AttrName(state->symbols.create(str->s)), state->at(@2));
delete str;
} else
throw ParseError({
.msg = HintFmt("dynamic attributes not allowed in inherit"),
.pos = state->positions[state->at(@2)]
});
}
| { $$ = new std::vector<std::pair<AttrName, PosIdx>>; }
;
attrpath
: attrpath '.' attr { $$ = $1; $1->push_back(AttrName(state->symbols.create($3))); }
| attrpath '.' string_attr
{ $$ = $1;
ExprString * str = dynamic_cast<ExprString *>($3);
if (str) {
$$->push_back(AttrName(state->symbols.create(str->s)));
delete str;
} else
$$->push_back(AttrName($3));
}
| attr { $$ = new std::vector<AttrName>; $$->push_back(AttrName(state->symbols.create($1))); }
| string_attr
{ $$ = new std::vector<AttrName>;
ExprString *str = dynamic_cast<ExprString *>($1);
if (str) {
$$->push_back(AttrName(state->symbols.create(str->s)));
delete str;
} else
$$->push_back(AttrName($1));
}
;
attr
: ID
| OR_KW { $$ = {"or", 2}; }
;
string_attr
: '"' string_parts '"' { $$ = $2; }
| DOLLAR_CURLY expr '}' { $$ = $2; }
;
expr_list
: expr_list expr_select { $$ = $1; $1->elems.push_back($2); /* !!! dangerous */ }
| { $$ = new ExprList; }
;
formals
: formal ',' formals
{ $$ = $3; $$->formals.emplace_back(*$1); delete $1; }
| formal
{ $$ = new Formals; $$->formals.emplace_back(*$1); $$->ellipsis = false; delete $1; }
|
{ $$ = new Formals; $$->ellipsis = false; }
| ELLIPSIS
{ $$ = new Formals; $$->ellipsis = true; }
;
formal
: ID { $$ = new Formal{CUR_POS, state->symbols.create($1), 0}; }
| ID '?' expr { $$ = new Formal{CUR_POS, state->symbols.create($1), $3}; }
;
%%
#include "eval.hh"
namespace nix {
Expr * parseExprFromBuf(
char * text,
size_t length,
Pos::Origin origin,
const SourcePath & basePath,
SymbolTable & symbols,
PosTable & positions,
const Expr::AstSymbols & astSymbols)
{
yyscan_t scanner;
ParserState state {
.symbols = symbols,
.positions = positions,
.basePath = basePath,
.origin = positions.addOrigin(origin, length),
.s = astSymbols,
};
yylex_init(&scanner);
Finally _destroy([&] { yylex_destroy(scanner); });
yy_scan_buffer(text, length, scanner);
yyparse(scanner, &state);
return state.result;
}
}
-66
View File
@@ -1,66 +0,0 @@
#pragma once
///@file
#include <tao/pegtl.hpp>
namespace nix::parser {
// modified copy of change_state, as the manual suggest for more involved
// state manipulation. we want to change only the first state parameter,
// and we care about the *initial* position of a rule application (not the
// past-the-end position as pegtl change_state provides)
template<typename NewState>
struct change_head : tao::pegtl::maybe_nothing
{
template<
typename Rule,
tao::pegtl::apply_mode A,
tao::pegtl::rewind_mode M,
template<typename...> class Action,
template<typename...> class Control,
typename ParseInput,
typename State,
typename... States
>
[[nodiscard]] static bool match(ParseInput & in, State && st, States &&... sts)
{
const auto begin = in.iterator();
if constexpr (std::is_constructible_v<NewState, State, States...>) {
NewState s(st, sts...);
if (tao::pegtl::match<Rule, A, M, Action, Control>(in, s, sts...)) {
if constexpr (A == tao::pegtl::apply_mode::action) {
_success<Action<Rule>>(0, begin, in, s, st, sts...);
}
return true;
}
return false;
} else if constexpr (std::is_default_constructible_v<NewState>) {
NewState s;
if (tao::pegtl::match<Rule, A, M, Action, Control>(in, s, sts...)) {
if constexpr (A == tao::pegtl::apply_mode::action) {
_success<Action<Rule>>(0, begin, in, s, st, sts...);
}
return true;
}
return false;
} else {
static_assert(decltype(sizeof(NewState))(), "unable to instantiate new state");
}
}
template<typename Target, typename ParseInput, typename... S>
static void _success(void *, auto & begin, ParseInput & in, S & ... sts)
{
const typename ParseInput::action_t at(begin, in);
Target::success(at, sts...);
}
template<typename Target, typename... S>
static void _success(decltype(Target::success0(std::declval<S &>()...), 0), auto &, auto &, S & ... sts)
{
Target::success0(sts...);
}
};
}
-707
View File
@@ -1,707 +0,0 @@
#pragma once
///@file
#include "tao/pegtl.hpp"
#include <type_traits>
#include <variant>
#include <boost/container/small_vector.hpp>
// NOTE
// nix line endings are \n, \r\n, \r. the grammar does not use eol or
// eolf rules in favor of reproducing the old flex lexer as faithfully as
// possible, and deferring calculation of positions to downstream users.
namespace nix::parser::grammar {
using namespace tao::pegtl;
namespace p = tao::pegtl;
// character classes
namespace c {
struct path : sor<
ranges<'a', 'z', 'A', 'Z', '0', '9'>,
one<'.', '_', '-', '+'>
> {};
struct path_sep : one<'/'> {};
struct id_first : ranges<'a', 'z', 'A', 'Z', '_'> {};
struct id_rest : sor<
ranges<'a', 'z', 'A', 'Z', '0', '9'>,
one<'_', '\'', '-'>
> {};
struct uri_scheme_first : ranges<'a', 'z', 'A', 'Z'> {};
struct uri_scheme_rest : sor<
ranges<'a', 'z', 'A', 'Z', '0', '9'>,
one<'+', '-', '.'>
> {};
struct uri_sep : one<':'> {};
struct uri_rest : sor<
ranges<'a', 'z', 'A', 'Z', '0', '9'>,
one<'%', '/', '?', ':', '@', '&', '=', '+', '$', ',', '-', '_', '.', '!', '~', '*', '\''>
> {};
}
// "tokens". PEGs don't really care about tokens, we merely use them as a convenient
// way of writing down keywords and a couple complicated syntax rules.
namespace t {
struct _extend_as_path : seq<
star<c::path>,
not_at<TAO_PEGTL_STRING("/*")>,
not_at<TAO_PEGTL_STRING("//")>,
c::path_sep,
sor<c::path, TAO_PEGTL_STRING("${")>
> {};
struct _extend_as_uri : seq<
star<c::uri_scheme_rest>,
c::uri_sep,
c::uri_rest
> {};
// keywords might be extended to identifiers, paths, or uris.
// NOTE this assumes that keywords are a-zA-Z only, otherwise uri schemes would never
// match correctly.
// NOTE not a simple seq<...> because this would report incorrect positions for
// keywords used inside must<> if a prefix of the keyword matches.
template<typename S>
struct _keyword : sor<
seq<
S,
not_at<c::id_rest>,
not_at<_extend_as_path>,
not_at<_extend_as_uri>
>,
failure
> {};
struct kw_if : _keyword<TAO_PEGTL_STRING("if")> {};
struct kw_then : _keyword<TAO_PEGTL_STRING("then")> {};
struct kw_else : _keyword<TAO_PEGTL_STRING("else")> {};
struct kw_assert : _keyword<TAO_PEGTL_STRING("assert")> {};
struct kw_with : _keyword<TAO_PEGTL_STRING("with")> {};
struct kw_let : _keyword<TAO_PEGTL_STRING("let")> {};
struct kw_in : _keyword<TAO_PEGTL_STRING("in")> {};
struct kw_rec : _keyword<TAO_PEGTL_STRING("rec")> {};
struct kw_inherit : _keyword<TAO_PEGTL_STRING("inherit")> {};
struct kw_or : _keyword<TAO_PEGTL_STRING("or")> {};
// `-` can be a unary prefix op, a binary infix op, or the first character
// of a path or -> (ex 1->1--1)
// `/` can be a path leader or an operator (ex a?a /a)
struct op_minus : seq<one<'-'>, not_at<one<'>'>>, not_at<_extend_as_path>> {};
struct op_div : seq<one<'/'>, not_at<c::path>> {};
// match a rule, making sure we are not matching it where a keyword would match.
// using minus like this is a lot faster than flipping the order and using seq.
template<typename... Rules>
struct _not_at_any_keyword : minus<
seq<Rules...>,
sor<
TAO_PEGTL_STRING("inherit"),
TAO_PEGTL_STRING("assert"),
TAO_PEGTL_STRING("else"),
TAO_PEGTL_STRING("then"),
TAO_PEGTL_STRING("with"),
TAO_PEGTL_STRING("let"),
TAO_PEGTL_STRING("rec"),
TAO_PEGTL_STRING("if"),
TAO_PEGTL_STRING("in"),
TAO_PEGTL_STRING("or")
>
> {};
// identifiers are kind of horrid:
//
// - uri_scheme_first ⊂ id_first
// - uri_scheme_first ⊂ uri_scheme_rest ⊂ path
// - id_first ⊂ id_rest { ' } ⊂ path
// - id_first ∩ (path uri_scheme_first) = { _ }
// - uri_sep ∉ { id_first, id_rest, uri_scheme_first, uri_scheme_rest, path }
// - path_sep ∉ { id_first, id_rest, uri_scheme_first, uri_scheme_rest }
//
// and we want, without reading the input more than once, a string that
// matches (id_first id_rest*) and is not followed by any number of
// characters such that the extended string matches path or uri rules.
//
// since the first character must be either _ or a uri scheme character
// we can ignore path-like bits at the beginning. uri_sep cannot appear anywhere
// in an identifier, so it's only needed in lookahead checks at the uri-like
// prefix. likewise path_sep cannot appear anywhere in the idenfier, so it's
// only needed in lookahead checks in the path-like prefix.
//
// in total that gives us a decomposition of
//
// (uri-scheme-like? (?! continues-as-uri) | _)
// (path-segment-like? (?! continues-as-path))
// id_rest*
struct identifier : _not_at_any_keyword<
// we don't use (at<id_rest>, ...) matches here because identifiers are
// a really hot path and rewinding as needed by at<> isn't entirely free.
sor<
seq<
c::uri_scheme_first,
star<ranges<'a', 'z', 'A', 'Z', '0', '9', '-'>>,
not_at<_extend_as_uri>
>,
one<'_'>
>,
star<sor<ranges<'a', 'z', 'A', 'Z', '0', '9'>, one<'_', '-'>>>,
not_at<_extend_as_path>,
star<c::id_rest>
> {};
// floats may extend ints, thus these rules are very similar.
struct integer : seq<
sor<
seq<range<'1', '9'>, star<digit>, not_at<one<'.'>>>,
seq<one<'0'>, not_at<one<'.'>, digit>, star<digit>>
>,
not_at<_extend_as_path>
> {};
struct floating : seq<
sor<
seq<range<'1', '9'>, star<digit>, one<'.'>, star<digit>>,
seq<opt<one<'0'>>, one<'.'>, plus<digit>>
>,
opt<one<'E', 'e'>, opt<one<'+', '-'>>, plus<digit>>,
not_at<_extend_as_path>
> {};
struct uri : seq<
c::uri_scheme_first,
star<c::uri_scheme_rest>,
c::uri_sep,
plus<c::uri_rest>
> {};
struct sep : sor<
plus<one<' ', '\t', '\r', '\n'>>,
seq<one<'#'>, star<not_one<'\r', '\n'>>>,
seq<string<'/', '*'>, until<string<'*', '/'>>>
> {};
}
using seps = star<t::sep>;
// marker for semantic rules. not handling one of these in an action that cares about
// semantics is probably an error.
struct semantic {};
struct expr;
struct _string {
template<typename... Inner>
struct literal : semantic, seq<Inner...> {};
struct cr_lf : semantic, seq<one<'\r'>, opt<one<'\n'>>> {};
struct interpolation : semantic, seq<
p::string<'$', '{'>, seps,
must<expr>, seps,
must<one<'}'>>
> {};
struct escape : semantic, must<any> {};
};
struct string : _string, seq<
one<'"'>,
star<
sor<
_string::literal<plus<not_one<'$', '"', '\\', '\r'>>>,
_string::cr_lf,
_string::interpolation,
_string::literal<one<'$'>, opt<one<'$'>>>,
seq<one<'\\'>, _string::escape>
>
>,
must<one<'"'>>
> {};
struct _ind_string {
template<bool Indented, typename... Inner>
struct literal : semantic, seq<Inner...> {};
struct interpolation : semantic, seq<
p::string<'$', '{'>, seps,
must<expr>, seps,
must<one<'}'>>
> {};
struct escape : semantic, must<any> {};
};
struct ind_string : _ind_string, seq<
TAO_PEGTL_STRING("''"),
opt<star<one<' '>>, one<'\n'>>,
star<
sor<
_ind_string::literal<
true,
plus<
sor<
not_one<'$', '\''>,
seq<one<'$'>, not_one<'{', '\''>>,
seq<one<'\''>, not_one<'\'', '$'>>
>
>
>,
_ind_string::interpolation,
_ind_string::literal<false, one<'$'>>,
_ind_string::literal<false, one<'\''>, not_at<one<'\''>>>,
seq<one<'\''>, _ind_string::literal<false, p::string<'\'', '\''>>>,
seq<
p::string<'\'', '\''>,
sor<
_ind_string::literal<false, one<'$'>>,
seq<one<'\\'>, _ind_string::escape>
>
>
>
>,
must<TAO_PEGTL_STRING("''")>
> {};
struct _path {
// legacy lexer rules. extra l_ to avoid reserved c++ identifiers.
struct _l_PATH : seq<star<c::path>, plus<c::path_sep, plus<c::path>>, opt<c::path_sep>> {};
struct _l_PATH_SEG : seq<star<c::path>, c::path_sep> {};
struct _l_HPATH : seq<one<'~'>, plus<c::path_sep, plus<c::path>>, opt<c::path_sep>> {};
struct _l_HPATH_START : TAO_PEGTL_STRING("~/") {};
struct _path_str : sor<_l_PATH, _l_PATH_SEG, plus<c::path>> {};
// modern rules
template<typename... Inner>
struct literal : semantic, seq<Inner...> {};
struct interpolation : semantic, seq<
p::string<'$', '{'>, seps,
must<expr>, seps,
must<one<'}'>>
> {};
struct anchor : semantic, sor<
_l_PATH,
seq<_l_PATH_SEG, at<TAO_PEGTL_STRING("${")>>
> {};
struct home_anchor : semantic, sor<
_l_HPATH,
seq<_l_HPATH_START, at<TAO_PEGTL_STRING("${")>>
> {};
struct searched_path : semantic, list<plus<c::path>, c::path_sep> {};
struct forbid_prefix_triple_slash : sor<not_at<c::path_sep>, failure> {};
struct forbid_prefix_double_slash_no_interp : sor<
not_at<c::path_sep, star<c::path>, not_at<TAO_PEGTL_STRING("${")>>,
failure
> {};
// legacy parser rules
struct _str_rest : seq<
must<forbid_prefix_double_slash_no_interp>,
opt<literal<_path_str>>,
must<forbid_prefix_triple_slash>,
star<
sor<
literal<_path_str>,
interpolation
>
>
> {};
};
struct path : _path, sor<
seq<
sor<_path::anchor, _path::home_anchor>,
_path::_str_rest
>,
seq<one<'<'>, _path::searched_path, one<'>'>>
> {};
struct _formal {
struct name : semantic, t::identifier {};
struct default_value : semantic, must<expr> {};
};
struct formal : semantic, _formal, seq<
_formal::name,
opt<seps, one<'?'>, seps, _formal::default_value>
> {};
struct _formals {
struct ellipsis : semantic, p::ellipsis {};
};
struct formals : semantic, _formals, seq<
one<'{'>, seps,
// formals and attrsets share a two-token head sequence ('{' <id>).
// this rule unrolls the formals list a bit to provide better error messages than
// "expected '='" at the first ',' if formals are incorrect.
sor<
one<'}'>,
seq<_formals::ellipsis, seps, must<one<'}'>>>,
seq<
formal, seps,
if_then_else<
at<one<','>>,
seq<
star<one<','>, seps, formal, seps>,
opt<one<','>, seps, opt<_formals::ellipsis, seps>>,
must<one<'}'>>
>,
one<'}'>
>
>
>
> {};
struct _attr {
struct simple : semantic, sor<t::identifier, t::kw_or> {};
struct string : semantic, seq<grammar::string> {};
struct expr : semantic, seq<
TAO_PEGTL_STRING("${"), seps,
must<grammar::expr>, seps,
must<one<'}'>>
> {};
};
struct attr : _attr, sor<
_attr::simple,
_attr::string,
_attr::expr
> {};
struct attrpath : list<attr, one<'.'>, t::sep> {};
struct _inherit {
struct from : semantic, must<expr> {};
struct attrs : list<attr, seps> {};
};
struct inherit : _inherit, seq<
t::kw_inherit, seps,
opt<one<'('>, seps, _inherit::from, seps, must<one<')'>>, seps>,
opt<_inherit::attrs, seps>,
must<one<';'>>
> {};
struct _binding {
struct path : semantic, attrpath {};
struct equal : one<'='> {};
struct value : semantic, must<expr> {};
};
struct binding : _binding, seq<
_binding::path, seps,
must<_binding::equal>, seps,
_binding::value, seps,
must<one<';'>>
> {};
struct bindings : opt<list<sor<inherit, binding>, seps>> {};
struct op {
enum class kind {
// NOTE non-associativity is *NOT* handled in the grammar structure.
// handling it in the grammar itself instead of in semantic actions
// slows down the parser significantly and makes the rules *much*
// harder to read. maybe this will be different at some point when
// ! does not sit between two binary precedence levels.
nonAssoc,
leftAssoc,
rightAssoc,
unary,
};
template<typename Rule, unsigned Precedence, kind Kind = kind::leftAssoc>
struct _op : Rule {
static constexpr unsigned precedence = Precedence;
static constexpr op::kind kind = Kind;
};
struct unary_minus : _op<t::op_minus, 3, kind::unary> {};
// treating this like a unary postfix operator is sketchy, but that's
// the most reasonable way to implement the operator precedence set forth
// by the language way back. it'd be much better if `.` and `?` had the same
// precedence, but alas.
struct has_attr : _op<seq<one<'?'>, seps, must<attrpath>>, 4> {};
struct concat : _op<TAO_PEGTL_STRING("++"), 5, kind::rightAssoc> {};
struct mul : _op<one<'*'>, 6> {};
struct div : _op<t::op_div, 6> {};
struct plus : _op<one<'+'>, 7> {};
struct minus : _op<t::op_minus, 7> {};
struct not_ : _op<one<'!'>, 8, kind::unary> {};
struct update : _op<TAO_PEGTL_STRING("//"), 9, kind::rightAssoc> {};
struct less_eq : _op<TAO_PEGTL_STRING("<="), 10, kind::nonAssoc> {};
struct greater_eq : _op<TAO_PEGTL_STRING(">="), 10, kind::nonAssoc> {};
struct less : _op<one<'<'>, 10, kind::nonAssoc> {};
struct greater : _op<one<'>'>, 10, kind::nonAssoc> {};
struct equals : _op<TAO_PEGTL_STRING("=="), 11, kind::nonAssoc> {};
struct not_equals : _op<TAO_PEGTL_STRING("!="), 11, kind::nonAssoc> {};
struct and_ : _op<TAO_PEGTL_STRING("&&"), 12> {};
struct or_ : _op<TAO_PEGTL_STRING("||"), 13> {};
struct implies : _op<TAO_PEGTL_STRING("->"), 14, kind::rightAssoc> {};
};
struct _expr {
template<template<typename...> class OpenMod = seq, typename... Init>
struct _attrset : seq<
Init...,
OpenMod<one<'{'>>, seps,
bindings, seps,
must<one<'}'>>
> {};
struct select;
struct id : semantic, t::identifier {};
struct int_ : semantic, t::integer {};
struct float_ : semantic, t::floating {};
struct string : semantic, seq<grammar::string> {};
struct ind_string : semantic, seq<grammar::ind_string> {};
struct path : semantic, seq<grammar::path> {};
struct uri : semantic, t::uri {};
struct ancient_let : semantic, _attrset<must, t::kw_let, seps> {};
struct rec_set : semantic, _attrset<must, t::kw_rec, seps> {};
struct set : semantic, _attrset<> {};
struct _list {
struct entry : semantic, seq<select> {};
};
struct list : semantic, _list, seq<
one<'['>, seps,
opt<p::list<_list::entry, seps>, seps>,
must<one<']'>>
> {};
struct _simple : sor<
id,
int_,
float_,
string,
ind_string,
path,
uri,
seq<one<'('>, seps, must<expr>, seps, must<one<')'>>>,
ancient_let,
rec_set,
set,
list
> {};
struct _select {
struct head : _simple {};
struct attr : semantic, seq<attrpath> {};
struct attr_or : semantic, must<select> {};
struct as_app_or : semantic, t::kw_or {};
};
struct _app {
struct first_arg : semantic, seq<select> {};
struct another_arg : semantic, seq<select> {};
// can be used to stash a position of the application head node
struct select_or_fn : seq<select> {};
};
struct select : _select, seq<
_select::head, seps,
opt<
sor<
seq<
one<'.'>, seps, _select::attr,
opt<seps, t::kw_or, seps, _select::attr_or>
>,
_select::as_app_or
>
>
> {};
struct app : _app, seq<
_app::select_or_fn,
opt<seps, _app::first_arg, star<seps, _app::another_arg>>
> {};
template<typename Op>
struct operator_ : semantic, Op {};
struct unary : seq<
star<sor<operator_<op::not_>, operator_<op::unary_minus>>, seps>,
app
> {};
struct _binary_operator : sor<
operator_<op::implies>,
operator_<op::update>,
operator_<op::concat>,
operator_<op::plus>,
operator_<op::minus>,
operator_<op::mul>,
operator_<op::div>,
operator_<op::less_eq>,
operator_<op::greater_eq>,
operator_<op::less>,
operator_<op::greater>,
operator_<op::equals>,
operator_<op::not_equals>,
operator_<op::or_>,
operator_<op::and_>
> {};
struct _binop : seq<
unary,
star<
seps,
sor<
seq<_binary_operator, seps, must<unary>>,
operator_<op::has_attr>
>
>
> {};
struct _lambda {
struct arg : semantic, t::identifier {};
};
struct lambda : semantic, _lambda, sor<
seq<
_lambda::arg, seps,
sor<
seq<one<':'>, seps, must<expr>>,
seq<one<'@'>, seps, must<formals, seps, one<':'>, seps, expr>>
>
>,
seq<
formals, seps,
sor<
seq<one<':'>, seps, must<expr>>,
seq<one<'@'>, seps, must<_lambda::arg, seps, one<':'>, seps, expr>>
>
>
> {};
struct assert_ : semantic, seq<
t::kw_assert, seps,
must<expr>, seps,
must<one<';'>>, seps,
must<expr>
> {};
struct with : semantic, seq<
t::kw_with, seps,
must<expr>, seps,
must<one<';'>>, seps,
must<expr>
> {};
struct let : seq<
t::kw_let, seps,
not_at<one<'{'>>, // exclude ancient_let so we can must<kw_in>
bindings, seps,
must<t::kw_in>, seps,
must<expr>
> {};
struct if_ : semantic, seq<
t::kw_if, seps,
must<expr>, seps,
must<t::kw_then>, seps,
must<expr>, seps,
must<t::kw_else>, seps,
must<expr>
> {};
};
struct expr : semantic, _expr, sor<
_expr::lambda,
_expr::assert_,
_expr::with,
_expr::let,
_expr::if_,
_expr::_binop
> {};
// legacy support: \0 terminates input if passed from flex to bison as a token
struct eof : sor<p::eof, one<0>> {};
struct root : must<seps, expr, seps, eof> {};
template<typename Rule>
struct nothing : p::nothing<Rule> {
static_assert(!std::is_base_of_v<semantic, Rule>);
};
template<typename Self, typename OpCtx, typename AttrPathT, typename ExprT>
struct operator_semantics {
struct has_attr : grammar::op::has_attr {
AttrPathT path;
};
struct OpEntry {
OpCtx ctx;
uint8_t prec;
grammar::op::kind assoc;
std::variant<
grammar::op::not_,
grammar::op::unary_minus,
grammar::op::implies,
grammar::op::or_,
grammar::op::and_,
grammar::op::equals,
grammar::op::not_equals,
grammar::op::less_eq,
grammar::op::greater_eq,
grammar::op::update,
grammar::op::concat,
grammar::op::less,
grammar::op::greater,
grammar::op::plus,
grammar::op::minus,
grammar::op::mul,
grammar::op::div,
has_attr
> op;
};
// statistics here are taken from nixpkgs commit de502c4d0ba96261e5de803e4d1d1925afd3e22f.
// over 99.9% of contexts in nixpkgs need at most 4 slots, ~85% need only 1
boost::container::small_vector<ExprT, 4> exprs;
// over 99.9% of contexts in nixpkgs need at most 2 slots, ~85% need only 1
boost::container::small_vector<OpEntry, 2> ops;
// derived class is expected to define members:
//
// ExprT applyOp(OpCtx & pos, auto & op, auto &... args);
// [[noreturn]] static void badOperator(OpCtx & pos, auto &... args);
void reduce(uint8_t toPrecedence, auto &... args) {
while (!ops.empty()) {
auto & [ctx, precedence, kind, op] = ops.back();
// NOTE this relies on associativity not being mixed within a precedence level.
if ((precedence > toPrecedence)
|| (kind != grammar::op::kind::leftAssoc && precedence == toPrecedence))
break;
std::visit([&, ctx=std::move(ctx)] (auto & op) {
exprs.push_back(static_cast<Self &>(*this).applyOp(ctx, op, args...));
}, op);
ops.pop_back();
}
}
ExprT popExpr()
{
auto r = std::move(exprs.back());
exprs.pop_back();
return r;
}
void pushOp(OpCtx ctx, auto o, auto &... args)
{
if (o.kind != grammar::op::kind::unary)
reduce(o.precedence, args...);
if (!ops.empty() && o.kind == grammar::op::kind::nonAssoc) {
auto & [_pos, _prec, _kind, _o] = ops.back();
if (_kind == o.kind && _prec == o.precedence)
Self::badOperator(ctx, args...);
}
ops.emplace_back(ctx, o.precedence, o.kind, std::move(o));
}
ExprT finish(auto &... args)
{
reduce(255, args...);
return popExpr();
}
};
}
-862
View File
@@ -1,862 +0,0 @@
#include "attr-set.hh"
#include "error.hh"
#include "eval-settings.hh"
#include "eval.hh"
#include "finally.hh"
#include "nixexpr.hh"
#include "symbol-table.hh"
#include "users.hh"
#include "change_head.hh"
#include "grammar.hh"
#include "state.hh"
#include <charconv>
#include <clocale>
#include <memory>
// flip this define when doing parser development to enable some g checks.
#if 0
#include <tao/pegtl/contrib/analyze.hpp>
#define ANALYZE_GRAMMAR \
([] { \
const std::size_t issues = tao::pegtl::analyze<grammar::root>(); \
assert(issues == 0); \
})()
#else
#define ANALYZE_GRAMMAR ((void) 0)
#endif
namespace p = tao::pegtl;
namespace nix::parser {
namespace {
template<typename>
inline constexpr const char * error_message = nullptr;
#define error_message_for(...) \
template<> inline constexpr auto error_message<__VA_ARGS__>
error_message_for(p::one<'{'>) = "expecting '{'";
error_message_for(p::one<'}'>) = "expecting '}'";
error_message_for(p::one<'"'>) = "expecting '\"'";
error_message_for(p::one<';'>) = "expecting ';'";
error_message_for(p::one<')'>) = "expecting ')'";
error_message_for(p::one<'='>) = "expecting '='";
error_message_for(p::one<']'>) = "expecting ']'";
error_message_for(p::one<':'>) = "expecting ':'";
error_message_for(p::string<'\'', '\''>) = "expecting \"''\"";
error_message_for(p::any) = "expecting any character";
error_message_for(grammar::eof) = "expecting end of file";
error_message_for(grammar::seps) = "expecting separators";
error_message_for(grammar::path::forbid_prefix_triple_slash) = "too many slashes in path";
error_message_for(grammar::path::forbid_prefix_double_slash_no_interp) = "path has a trailing slash";
error_message_for(grammar::expr) = "expecting expression";
error_message_for(grammar::expr::unary) = "expecting expression";
error_message_for(grammar::binding::equal) = "expecting '='";
error_message_for(grammar::expr::lambda::arg) = "expecting identifier";
error_message_for(grammar::formals) = "expecting formals";
error_message_for(grammar::attrpath) = "expecting attribute path";
error_message_for(grammar::expr::select) = "expecting selection expression";
error_message_for(grammar::t::kw_then) = "expecting 'then'";
error_message_for(grammar::t::kw_else) = "expecting 'else'";
error_message_for(grammar::t::kw_in) = "expecting 'in'";
struct SyntaxErrors
{
template<typename Rule>
static constexpr auto message = error_message<Rule>;
template<typename Rule>
static constexpr bool raise_on_failure = false;
};
template<typename Rule>
struct Control : p::must_if<SyntaxErrors>::control<Rule>
{
template<typename ParseInput, typename... States>
[[noreturn]] static void raise(const ParseInput & in, States &&... st)
{
if (in.empty()) {
std::string expected;
if constexpr (constexpr auto msg = error_message<Rule>)
expected = fmt(", %s", msg);
throw p::parse_error("unexpected end of file" + expected, in);
}
p::must_if<SyntaxErrors>::control<Rule>::raise(in, st...);
}
};
struct ExprState
: grammar::
operator_semantics<ExprState, PosIdx, AttrPath, std::pair<PosIdx, std::unique_ptr<Expr>>>
{
std::unique_ptr<Expr> popExprOnly() {
return std::move(popExpr().second);
}
template<typename Op, typename... Args>
std::unique_ptr<Expr> applyUnary(Args &&... args) {
return std::make_unique<Op>(popExprOnly(), std::forward<Args>(args)...);
}
template<typename Op>
std::unique_ptr<Expr> applyBinary(PosIdx pos) {
auto right = popExprOnly(), left = popExprOnly();
return std::make_unique<Op>(pos, std::move(left), std::move(right));
}
std::unique_ptr<Expr> call(PosIdx pos, Symbol fn, bool flip = false)
{
std::vector<std::unique_ptr<Expr>> args(2);
args[flip ? 0 : 1] = popExprOnly();
args[flip ? 1 : 0] = popExprOnly();
return std::make_unique<ExprCall>(pos, std::make_unique<ExprVar>(fn), std::move(args));
}
std::unique_ptr<Expr> order(PosIdx pos, bool less, State & state)
{
return call(pos, state.s.lessThan, !less);
}
std::unique_ptr<Expr> concatStrings(PosIdx pos)
{
std::vector<std::pair<PosIdx, std::unique_ptr<Expr>>> args(2);
args[1] = popExpr();
args[0] = popExpr();
return std::make_unique<ExprConcatStrings>(pos, false, std::move(args));
}
std::unique_ptr<Expr> negate(PosIdx pos, State & state)
{
std::vector<std::unique_ptr<Expr>> args(2);
args[0] = std::make_unique<ExprInt>(0);
args[1] = popExprOnly();
return std::make_unique<ExprCall>(pos, std::make_unique<ExprVar>(state.s.sub), std::move(args));
}
std::pair<PosIdx, std::unique_ptr<Expr>> applyOp(PosIdx pos, auto & op, State & state) {
using Op = grammar::op;
auto not_ = [] (auto e) {
return std::make_unique<ExprOpNot>(std::move(e));
};
return {
pos,
(overloaded {
[&] (Op::implies) { return applyBinary<ExprOpImpl>(pos); },
[&] (Op::or_) { return applyBinary<ExprOpOr>(pos); },
[&] (Op::and_) { return applyBinary<ExprOpAnd>(pos); },
[&] (Op::equals) { return applyBinary<ExprOpEq>(pos); },
[&] (Op::not_equals) { return applyBinary<ExprOpNEq>(pos); },
[&] (Op::less) { return order(pos, true, state); },
[&] (Op::greater_eq) { return not_(order(pos, true, state)); },
[&] (Op::greater) { return order(pos, false, state); },
[&] (Op::less_eq) { return not_(order(pos, false, state)); },
[&] (Op::update) { return applyBinary<ExprOpUpdate>(pos); },
[&] (Op::not_) { return applyUnary<ExprOpNot>(); },
[&] (Op::plus) { return concatStrings(pos); },
[&] (Op::minus) { return call(pos, state.s.sub); },
[&] (Op::mul) { return call(pos, state.s.mul); },
[&] (Op::div) { return call(pos, state.s.div); },
[&] (Op::concat) { return applyBinary<ExprOpConcatLists>(pos); },
[&] (has_attr & a) { return applyUnary<ExprOpHasAttr>(std::move(a.path)); },
[&] (Op::unary_minus) { return negate(pos, state); },
})(op)
};
}
// always_inline is needed, otherwise pushOp slows down considerably
[[noreturn, gnu::always_inline]]
static void badOperator(PosIdx pos, State & state)
{
throw ParseError({
.msg = HintFmt("syntax error, unexpected operator"),
.pos = state.positions[pos]
});
}
template<typename Expr, typename... Args>
Expr & pushExpr(PosIdx pos, Args && ... args)
{
auto p = std::make_unique<Expr>(std::forward<Args>(args)...);
auto & result = *p;
exprs.emplace_back(pos, std::move(p));
return result;
}
};
struct SubexprState {
private:
ExprState * up;
public:
explicit SubexprState(ExprState & up, auto &...) : up(&up) {}
operator ExprState &() { return *up; }
ExprState * operator->() { return up; }
};
template<typename Rule>
struct BuildAST : grammar::nothing<Rule> {};
struct LambdaState : SubexprState {
using SubexprState::SubexprState;
Symbol arg;
std::unique_ptr<Formals> formals;
};
struct FormalsState : SubexprState {
using SubexprState::SubexprState;
Formals formals{};
Formal formal{};
};
template<> struct BuildAST<grammar::formal::name> {
static void apply(const auto & in, FormalsState & s, State & ps) {
s.formal = {
.pos = ps.at(in),
.name = ps.symbols.create(in.string_view()),
};
}
};
template<> struct BuildAST<grammar::formal> {
static void apply0(FormalsState & s, State &) {
s.formals.formals.emplace_back(std::move(s.formal));
}
};
template<> struct BuildAST<grammar::formal::default_value> {
static void apply0(FormalsState & s, State & ps) {
s.formal.def = s->popExprOnly();
}
};
template<> struct BuildAST<grammar::formals::ellipsis> {
static void apply0(FormalsState & s, State &) {
s.formals.ellipsis = true;
}
};
template<> struct BuildAST<grammar::formals> : change_head<FormalsState> {
static void success0(FormalsState & f, LambdaState & s, State &) {
s.formals = std::make_unique<Formals>(std::move(f.formals));
}
};
struct AttrState : SubexprState {
using SubexprState::SubexprState;
std::vector<AttrName> attrs;
void pushAttr(auto && attr, PosIdx) { attrs.emplace_back(std::move(attr)); }
};
template<> struct BuildAST<grammar::attr::simple> {
static void apply(const auto & in, auto & s, State & ps) {
s.pushAttr(ps.symbols.create(in.string_view()), ps.at(in));
}
};
template<> struct BuildAST<grammar::attr::string> {
static void apply(const auto & in, auto & s, State & ps) {
auto e = s->popExprOnly();
if (auto str = dynamic_cast<ExprString *>(e.get()))
s.pushAttr(ps.symbols.create(str->s), ps.at(in));
else
s.pushAttr(std::move(e), ps.at(in));
}
};
template<> struct BuildAST<grammar::attr::expr> : BuildAST<grammar::attr::string> {};
struct BindingsState : SubexprState {
using SubexprState::SubexprState;
ExprAttrs attrs;
AttrPath path;
std::unique_ptr<Expr> value;
};
struct InheritState : SubexprState {
using SubexprState::SubexprState;
std::vector<std::pair<AttrName, PosIdx>> attrs;
std::unique_ptr<Expr> from;
PosIdx fromPos;
void pushAttr(auto && attr, PosIdx pos) { attrs.emplace_back(std::move(attr), pos); }
};
template<> struct BuildAST<grammar::inherit::from> {
static void apply(const auto & in, InheritState & s, State & ps) {
s.from = s->popExprOnly();
s.fromPos = ps.at(in);
}
};
template<> struct BuildAST<grammar::inherit> : change_head<InheritState> {
static void success0(InheritState & s, BindingsState & b, State & ps) {
auto & attrs = b.attrs.attrs;
// TODO this should not reuse generic attrpath rules.
for (auto & [i, iPos] : s.attrs) {
if (i.symbol)
continue;
if (auto str = dynamic_cast<ExprString *>(i.expr.get()))
i = AttrName(ps.symbols.create(str->s));
else {
throw ParseError({
.msg = HintFmt("dynamic attributes not allowed in inherit"),
.pos = ps.positions[iPos]
});
}
}
if (auto fromE = std::move(s.from)) {
if (!b.attrs.inheritFromExprs)
b.attrs.inheritFromExprs = std::make_unique<std::vector<std::unique_ptr<Expr>>>();
b.attrs.inheritFromExprs->push_back(std::move(fromE));
for (auto & [i, iPos] : s.attrs) {
if (attrs.find(i.symbol) != attrs.end())
ps.dupAttr(i.symbol, iPos, attrs[i.symbol].pos);
auto from = std::make_unique<ExprInheritFrom>(s.fromPos, b.attrs.inheritFromExprs->size() - 1);
attrs.emplace(
i.symbol,
ExprAttrs::AttrDef(
std::make_unique<ExprSelect>(iPos, std::move(from), i.symbol),
iPos,
ExprAttrs::AttrDef::Kind::InheritedFrom));
}
} else {
for (auto & [i, iPos] : s.attrs) {
if (attrs.find(i.symbol) != attrs.end())
ps.dupAttr(i.symbol, iPos, attrs[i.symbol].pos);
attrs.emplace(
i.symbol,
ExprAttrs::AttrDef(
std::make_unique<ExprVar>(iPos, i.symbol),
iPos,
ExprAttrs::AttrDef::Kind::Inherited));
}
}
}
};
template<> struct BuildAST<grammar::binding::path> : change_head<AttrState> {
static void success0(AttrState & a, BindingsState & s, State & ps) {
s.path = std::move(a.attrs);
}
};
template<> struct BuildAST<grammar::binding::value> {
static void apply0(BindingsState & s, State & ps) {
s.value = s->popExprOnly();
}
};
template<> struct BuildAST<grammar::binding> {
static void apply(const auto & in, BindingsState & s, State & ps) {
ps.addAttr(&s.attrs, std::move(s.path), std::move(s.value), ps.at(in));
}
};
template<> struct BuildAST<grammar::expr::id> {
static void apply(const auto & in, ExprState & s, State & ps) {
if (in.string_view() == "__curPos")
s.pushExpr<ExprPos>(ps.at(in), ps.at(in));
else
s.pushExpr<ExprVar>(ps.at(in), ps.at(in), ps.symbols.create(in.string_view()));
}
};
template<> struct BuildAST<grammar::expr::int_> {
static void apply(const auto & in, ExprState & s, State & ps) {
int64_t v;
if (std::from_chars(in.begin(), in.end(), v).ec != std::errc{}) {
throw ParseError({
.msg = HintFmt("invalid integer '%1%'", in.string_view()),
.pos = ps.positions[ps.at(in)],
});
}
s.pushExpr<ExprInt>(noPos, v);
}
};
template<> struct BuildAST<grammar::expr::float_> {
static void apply(const auto & in, ExprState & s, State & ps) {
// copy the input into a temporary string so we can call stod.
// can't use from_chars because libc++ (thus darwin) does not have it,
// and floats are not performance-sensitive anyway. if they were you'd
// be in much bigger trouble than this.
//
// we also get to do a locale-save dance because stod is locale-aware and
// something (a plugin?) may have called setlocale or uselocale.
static struct locale_hack {
locale_t posix;
locale_hack(): posix(newlocale(LC_ALL_MASK, "POSIX", 0))
{
if (posix == 0)
throw SysError("could not get POSIX locale");
}
} locale;
auto tmp = in.string();
double v = [&] {
auto oldLocale = uselocale(locale.posix);
Finally resetLocale([=] { uselocale(oldLocale); });
try {
return std::stod(tmp);
} catch (...) {
throw ParseError({
.msg = HintFmt("invalid float '%1%'", in.string_view()),
.pos = ps.positions[ps.at(in)],
});
}
}();
s.pushExpr<ExprFloat>(noPos, v);
}
};
struct StringState : SubexprState {
using SubexprState::SubexprState;
std::string currentLiteral;
PosIdx currentPos;
std::vector<std::pair<nix::PosIdx, std::unique_ptr<Expr>>> parts;
void append(PosIdx pos, std::string_view s)
{
if (currentLiteral.empty())
currentPos = pos;
currentLiteral += s;
}
// FIXME this truncates strings on NUL for compat with the old parser. ideally
// we should use the decomposition the g gives us instead of iterating over
// the entire string again.
static void unescapeStr(std::string & str)
{
char * s = str.data();
char * t = s;
char c;
while ((c = *s++)) {
if (c == '\\') {
c = *s++;
if (c == 'n') *t = '\n';
else if (c == 'r') *t = '\r';
else if (c == 't') *t = '\t';
else *t = c;
}
else if (c == '\r') {
/* Normalise CR and CR/LF into LF. */
*t = '\n';
if (*s == '\n') s++; /* cr/lf */
}
else *t = c;
t++;
}
str.resize(t - str.data());
}
void endLiteral()
{
if (!currentLiteral.empty()) {
unescapeStr(currentLiteral);
parts.emplace_back(currentPos, std::make_unique<ExprString>(std::move(currentLiteral)));
}
}
std::unique_ptr<Expr> finish()
{
if (parts.empty()) {
unescapeStr(currentLiteral);
return std::make_unique<ExprString>(std::move(currentLiteral));
} else {
endLiteral();
auto pos = parts[0].first;
return std::make_unique<ExprConcatStrings>(pos, true, std::move(parts));
}
}
};
template<typename... Content> struct BuildAST<grammar::string::literal<Content...>> {
static void apply(const auto & in, StringState & s, State & ps) {
s.append(ps.at(in), in.string_view());
}
};
template<> struct BuildAST<grammar::string::cr_lf> {
static void apply(const auto & in, StringState & s, State & ps) {
s.append(ps.at(in), in.string_view()); // FIXME compat with old parser
}
};
template<> struct BuildAST<grammar::string::interpolation> {
static void apply(const auto & in, StringState & s, State & ps) {
s.endLiteral();
s.parts.emplace_back(ps.at(in), s->popExprOnly());
}
};
template<> struct BuildAST<grammar::string::escape> {
static void apply(const auto & in, StringState & s, State & ps) {
s.append(ps.at(in), "\\"); // FIXME compat with old parser
s.append(ps.at(in), in.string_view());
}
};
template<> struct BuildAST<grammar::string> : change_head<StringState> {
static void success0(StringState & s, ExprState & e, State &) {
e.exprs.emplace_back(noPos, s.finish());
}
};
struct IndStringState : SubexprState {
using SubexprState::SubexprState;
std::vector<std::pair<PosIdx, std::variant<std::unique_ptr<Expr>, StringToken>>> parts;
};
template<bool Indented, typename... Content>
struct BuildAST<grammar::ind_string::literal<Indented, Content...>> {
static void apply(const auto & in, IndStringState & s, State & ps) {
s.parts.emplace_back(ps.at(in), StringToken{in.string_view(), Indented});
}
};
template<> struct BuildAST<grammar::ind_string::interpolation> {
static void apply(const auto & in, IndStringState & s, State & ps) {
s.parts.emplace_back(ps.at(in), s->popExprOnly());
}
};
template<> struct BuildAST<grammar::ind_string::escape> {
static void apply(const auto & in, IndStringState & s, State & ps) {
switch (*in.begin()) {
case 'n': s.parts.emplace_back(ps.at(in), StringToken{"\n"}); break;
case 'r': s.parts.emplace_back(ps.at(in), StringToken{"\r"}); break;
case 't': s.parts.emplace_back(ps.at(in), StringToken{"\t"}); break;
default: s.parts.emplace_back(ps.at(in), StringToken{in.string_view()}); break;
}
}
};
template<> struct BuildAST<grammar::ind_string> : change_head<IndStringState> {
static void success(const auto & in, IndStringState & s, ExprState & e, State & ps) {
e.exprs.emplace_back(noPos, ps.stripIndentation(ps.at(in), std::move(s.parts)));
}
};
template<typename... Content> struct BuildAST<grammar::path::literal<Content...>> {
static void apply(const auto & in, StringState & s, State & ps) {
s.append(ps.at(in), in.string_view());
s.endLiteral();
}
};
template<> struct BuildAST<grammar::path::interpolation> : BuildAST<grammar::string::interpolation> {};
template<> struct BuildAST<grammar::path::anchor> {
static void apply(const auto & in, StringState & s, State & ps) {
Path path(absPath(in.string(), ps.basePath.path.abs()));
/* add back in the trailing '/' to the first segment */
if (in.string_view().ends_with('/') && in.size() > 1)
path += "/";
s.parts.emplace_back(ps.at(in), new ExprPath(std::move(path)));
}
};
template<> struct BuildAST<grammar::path::home_anchor> {
static void apply(const auto & in, StringState & s, State & ps) {
if (evalSettings.pureEval)
throw Error("the path '%s' can not be resolved in pure mode", in.string_view());
Path path(getHome() + in.string_view().substr(1));
s.parts.emplace_back(ps.at(in), new ExprPath(std::move(path)));
}
};
template<> struct BuildAST<grammar::path::searched_path> {
static void apply(const auto & in, StringState & s, State & ps) {
std::vector<std::unique_ptr<Expr>> args{2};
args[0] = std::make_unique<ExprVar>(ps.s.nixPath);
args[1] = std::make_unique<ExprString>(in.string());
s.parts.emplace_back(
ps.at(in),
std::make_unique<ExprCall>(
ps.at(in),
std::make_unique<ExprVar>(ps.s.findFile),
std::move(args)));
}
};
template<> struct BuildAST<grammar::path> : change_head<StringState> {
template<typename E>
static void check_slash(PosIdx end, StringState & s, State & ps) {
auto e = dynamic_cast<E *>(s.parts.back().second.get());
if (!e || !e->s.ends_with('/'))
return;
if (s.parts.size() > 1 || e->s != "/")
throw ParseError({
.msg = HintFmt("path has a trailing slash"),
.pos = ps.positions[end],
});
}
static void success(const auto & in, StringState & s, ExprState & e, State & ps) {
s.endLiteral();
check_slash<ExprPath>(ps.atEnd(in), s, ps);
check_slash<ExprString>(ps.atEnd(in), s, ps);
if (s.parts.size() == 1) {
e.exprs.emplace_back(noPos, std::move(s.parts.back().second));
} else {
e.pushExpr<ExprConcatStrings>(ps.at(in), ps.at(in), false, std::move(s.parts));
}
}
};
// strings and paths sare handled fully by the grammar-level rule for now
template<> struct BuildAST<grammar::expr::string> : p::maybe_nothing {};
template<> struct BuildAST<grammar::expr::ind_string> : p::maybe_nothing {};
template<> struct BuildAST<grammar::expr::path> : p::maybe_nothing {};
template<> struct BuildAST<grammar::expr::uri> {
static void apply(const auto & in, ExprState & s, State & ps) {
static bool noURLLiterals = experimentalFeatureSettings.isEnabled(Xp::NoUrlLiterals);
if (noURLLiterals)
throw ParseError({
.msg = HintFmt("URL literals are disabled"),
.pos = ps.positions[ps.at(in)]
});
s.pushExpr<ExprString>(ps.at(in), in.string());
}
};
template<> struct BuildAST<grammar::expr::ancient_let> : change_head<BindingsState> {
static void success(const auto & in, BindingsState & b, ExprState & s, State & ps) {
b.attrs.pos = ps.at(in);
b.attrs.recursive = true;
s.pushExpr<ExprSelect>(b.attrs.pos, b.attrs.pos, std::make_unique<ExprAttrs>(std::move(b.attrs)), ps.s.body);
}
};
template<> struct BuildAST<grammar::expr::rec_set> : change_head<BindingsState> {
static void success(const auto & in, BindingsState & b, ExprState & s, State & ps) {
b.attrs.pos = ps.at(in);
b.attrs.recursive = true;
s.pushExpr<ExprAttrs>(b.attrs.pos, std::move(b.attrs));
}
};
template<> struct BuildAST<grammar::expr::set> : change_head<BindingsState> {
static void success(const auto & in, BindingsState & b, ExprState & s, State & ps) {
b.attrs.pos = ps.at(in);
s.pushExpr<ExprAttrs>(b.attrs.pos, std::move(b.attrs));
}
};
using ListState = std::vector<std::unique_ptr<Expr>>;
template<> struct BuildAST<grammar::expr::list> : change_head<ListState> {
static void success(const auto & in, ListState & ls, ExprState & s, State & ps) {
auto e = std::make_unique<ExprList>();
e->elems = std::move(ls);
s.exprs.emplace_back(ps.at(in), std::move(e));
}
};
template<> struct BuildAST<grammar::expr::list::entry> : change_head<ExprState> {
static void success0(ExprState & e, ListState & s, State & ps) {
s.emplace_back(e.finish(ps).second);
}
};
struct SelectState : SubexprState {
using SubexprState::SubexprState;
PosIdx pos;
ExprSelect * e = nullptr;
};
template<> struct BuildAST<grammar::expr::select::head> {
static void apply(const auto & in, SelectState & s, State & ps) {
s.pos = ps.at(in);
}
};
template<> struct BuildAST<grammar::expr::select::attr> : change_head<AttrState> {
static void success0(AttrState & a, SelectState & s, State &) {
s.e = &s->pushExpr<ExprSelect>(s.pos, s.pos, s->popExprOnly(), std::move(a.attrs), nullptr);
}
};
template<> struct BuildAST<grammar::expr::select::attr_or> {
static void apply0(SelectState & s, State &) {
s.e->def = s->popExprOnly();
}
};
template<> struct BuildAST<grammar::expr::select::as_app_or> {
static void apply(const auto & in, SelectState & s, State & ps) {
std::vector<std::unique_ptr<Expr>> args(1);
args[0] = std::make_unique<ExprVar>(ps.at(in), ps.s.or_);
s->pushExpr<ExprCall>(s.pos, s.pos, s->popExprOnly(), std::move(args));
}
};
template<> struct BuildAST<grammar::expr::select> : change_head<SelectState> {
static void success0(const auto &...) {}
};
struct AppState : SubexprState {
using SubexprState::SubexprState;
PosIdx pos;
ExprCall * e = nullptr;
};
template<> struct BuildAST<grammar::expr::app::select_or_fn> {
static void apply(const auto & in, AppState & s, State & ps) {
s.pos = ps.at(in);
}
};
template<> struct BuildAST<grammar::expr::app::first_arg> {
static void apply(auto & in, AppState & s, State & ps) {
auto arg = s->popExprOnly(), fn = s->popExprOnly();
if ((s.e = dynamic_cast<ExprCall *>(fn.get()))) {
// TODO remove.
// AST compat with old parser, semantics are the same.
// this can happen on occasions such as `<p> <p>` or `a or b or`,
// neither of which are super worth optimizing.
s.e->args.push_back(std::move(arg));
s->exprs.emplace_back(noPos, std::move(fn));
} else {
std::vector<std::unique_ptr<Expr>> args{1};
args[0] = std::move(arg);
s.e = &s->pushExpr<ExprCall>(s.pos, s.pos, std::move(fn), std::move(args));
}
}
};
template<> struct BuildAST<grammar::expr::app::another_arg> {
static void apply0(AppState & s, State & ps) {
s.e->args.push_back(s->popExprOnly());
}
};
template<> struct BuildAST<grammar::expr::app> : change_head<AppState> {
static void success0(const auto &...) {}
};
template<typename Op> struct BuildAST<grammar::expr::operator_<Op>> {
static void apply(const auto & in, ExprState & s, State & ps) {
s.pushOp(ps.at(in), Op{}, ps);
}
};
template<> struct BuildAST<grammar::expr::operator_<grammar::op::has_attr>> : change_head<AttrState> {
static void success(const auto & in, AttrState & a, ExprState & s, State & ps) {
s.pushOp(ps.at(in), ExprState::has_attr{{}, std::move(a.attrs)}, ps);
}
};
template<> struct BuildAST<grammar::expr::lambda::arg> {
static void apply(const auto & in, LambdaState & s, State & ps) {
s.arg = ps.symbols.create(in.string_view());
}
};
template<> struct BuildAST<grammar::expr::lambda> : change_head<LambdaState> {
static void success(const auto & in, LambdaState & l, ExprState & s, State & ps) {
if (l.formals)
l.formals = ps.validateFormals(std::move(l.formals), ps.at(in), l.arg);
s.pushExpr<ExprLambda>(ps.at(in), ps.at(in), l.arg, std::move(l.formals), l->popExprOnly());
}
};
template<> struct BuildAST<grammar::expr::assert_> {
static void apply(const auto & in, ExprState & s, State & ps) {
auto body = s.popExprOnly(), cond = s.popExprOnly();
s.pushExpr<ExprAssert>(ps.at(in), ps.at(in), std::move(cond), std::move(body));
}
};
template<> struct BuildAST<grammar::expr::with> {
static void apply(const auto & in, ExprState & s, State & ps) {
auto body = s.popExprOnly(), scope = s.popExprOnly();
s.pushExpr<ExprWith>(ps.at(in), ps.at(in), std::move(scope), std::move(body));
}
};
template<> struct BuildAST<grammar::expr::let> : change_head<BindingsState> {
static void success(const auto & in, BindingsState & b, ExprState & s, State & ps) {
if (!b.attrs.dynamicAttrs.empty())
throw ParseError({
.msg = HintFmt("dynamic attributes not allowed in let"),
.pos = ps.positions[ps.at(in)]
});
s.pushExpr<ExprLet>(ps.at(in), std::make_unique<ExprAttrs>(std::move(b.attrs)), b->popExprOnly());
}
};
template<> struct BuildAST<grammar::expr::if_> {
static void apply(const auto & in, ExprState & s, State & ps) {
auto else_ = s.popExprOnly(), then = s.popExprOnly(), cond = s.popExprOnly();
s.pushExpr<ExprIf>(ps.at(in), ps.at(in), std::move(cond), std::move(then), std::move(else_));
}
};
template<> struct BuildAST<grammar::expr> : change_head<ExprState> {
static void success0(ExprState & inner, ExprState & outer, State & ps) {
outer.exprs.push_back(inner.finish(ps));
}
};
}
}
namespace nix {
Expr * EvalState::parse(
char * text,
size_t length,
Pos::Origin origin,
const SourcePath & basePath,
std::shared_ptr<StaticEnv> & staticEnv)
{
parser::State s = {
symbols,
positions,
basePath,
positions.addOrigin(origin, length),
exprSymbols,
};
parser::ExprState x;
assert(length >= 2);
assert(text[length - 1] == 0);
assert(text[length - 2] == 0);
length -= 2;
p::string_input<p::tracking_mode::lazy> inp{std::string_view{text, length}, "input"};
try {
p::parse<parser::grammar::root, parser::BuildAST, parser::Control>(inp, x, s);
} catch (p::parse_error & e) {
auto pos = e.positions().back();
throw ParseError({
.msg = HintFmt("syntax error, %s", e.message()),
.pos = positions[s.positions.add(s.origin, pos.byte)]
});
}
auto [_pos, result] = x.finish(s);
result->bindVars(*this, staticEnv);
return result.release();
}
}
+12 -15
View File
@@ -244,9 +244,9 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
// args[0]->attrs is already sorted.
debug("evaluating file '%1%'", path);
Expr & e = state.parseExprFromFile(resolveExprPath(path), staticEnv);
Expr * e = state.parseExprFromFile(resolveExprPath(path), staticEnv);
e.eval(state, *env, v);
e->eval(state, *env, v);
}
}
}
@@ -383,20 +383,19 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
try {
auto _ = state.realiseContext(context); // FIXME: Handle CA derivations
} catch (InvalidPathError & e) {
e.addTrace(state.positions[pos], "while realising the context for builtins.exec");
throw;
state.error<EvalError>("cannot execute '%1%', since path '%2%' is not valid", program, e.path).atPos(pos).debugThrow();
}
auto output = runProgram(program, true, commandArgs);
Expr * parsed;
try {
parsed = &state.parseExprFromString(std::move(output), state.rootPath(CanonPath::root));
parsed = state.parseExprFromString(std::move(output), state.rootPath(CanonPath::root));
} catch (Error & e) {
e.addTrace(state.positions[pos], "while parsing the output from '%1%'", program);
throw;
}
try {
state.eval(*parsed, v);
state.eval(parsed, v);
} catch (Error & e) {
e.addTrace(state.positions[pos], "while evaluating the output from '%1%'", program);
throw;
@@ -923,15 +922,14 @@ static RegisterPrimOp primop_getEnv({
.args = {"s"},
.doc = R"(
`getEnv` returns the value of the environment variable *s*, or an
empty string if the variable doesn't exist. This function should be
empty string if the variable doesnt exist. This function should be
used with care, as it can introduce all sorts of nasty environment
dependencies in your Nix expression.
`getEnv` is used in nixpkgs for evil impurities such as locating the file
`~/.config/nixpkgs/config.nix` which contains user-local settings for nixpkgs.
(That is, it does a `getEnv "HOME"` to locate the user's home directory.)
When in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval), this function always returns an empty string.
`getEnv` is used in Nix Packages to locate the file
`~/.nixpkgs/config.nix`, which contains user-local settings for Nix
Packages. (That is, it does a `getEnv "HOME"` to locate the users
home directory.)
)",
.fun = prim_getEnv,
});
@@ -1507,7 +1505,6 @@ static RegisterPrimOp primop_storePath({
in a new path (e.g. `/nix/store/ld01dnzc-source-source`).
Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).
Lix may change this, tracking issue: <https://git.lix.systems/lix-project/lix/issues/402>
See also [`builtins.fetchClosure`](#builtins-fetchClosure).
)",
@@ -2819,7 +2816,7 @@ static void prim_functionArgs(EvalState & state, const PosIdx pos, Value * * arg
auto attrs = state.buildBindings(args[0]->lambda.fun->formals->formals.size());
for (auto & i : args[0]->lambda.fun->formals->formals)
// !!! should optimise booleans (allocate only once)
attrs.alloc(i.name, i.pos).mkBool(i.def != nullptr);
attrs.alloc(i.name, i.pos).mkBool(i.def);
v.mkAttrs(attrs);
}
@@ -4496,7 +4493,7 @@ void EvalState::createBaseEnv()
// the parser needs two NUL bytes as terminators; one of them
// is implied by being a C string.
"\0";
eval(*parse(code, sizeof(code), derivationInternal, {CanonPath::root}, staticBaseEnv), *vDerivation);
eval(parse(code, sizeof(code), derivationInternal, {CanonPath::root}, staticBaseEnv), *vDerivation);
}
+1 -1
View File
@@ -140,7 +140,7 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
if (v.lambda.fun->arg) attrs["name"] = state.symbols[v.lambda.fun->arg];
if (v.lambda.fun->formals->ellipsis) attrs["ellipsis"] = "1";
XMLOpenElement _(doc, "attrspat", attrs);
for (const Formal & i : v.lambda.fun->formals->lexicographicOrder(state.symbols))
for (auto & i : v.lambda.fun->formals->lexicographicOrder(state.symbols))
doc.writeEmptyElement("attr", singletonAttrs("name", state.symbols[i.name]));
} else
doc.writeEmptyElement("varpat", singletonAttrs("name", state.symbols[v.lambda.fun->arg]));
+2 -2
View File
@@ -323,11 +323,11 @@ public:
}
}
inline void mkThunk(Env * e, Expr & ex)
inline void mkThunk(Env * e, Expr * ex)
{
internalType = tThunk;
thunk.env = e;
thunk.expr = &ex;
thunk.expr = ex;
}
inline void mkApp(Value * l, Value * r)
+16 -16
View File
@@ -232,7 +232,7 @@ std::pair<StorePath, Input> fetchFromWorkdir(ref<Store> store, Input & input, co
if (S_ISDIR(st.st_mode)) {
auto prefix = file + "/";
auto i = files.lower_bound(prefix);
return (i != files.end() && (*i).starts_with(prefix)) || files.count(file);
return i != files.end() && (*i).starts_with(prefix);
}
return files.count(file);
@@ -361,7 +361,7 @@ struct GitInputScheme : InputScheme
args.push_back(destDir);
runProgram("git", true, args, true);
runProgram("git", true, args, {}, true);
}
std::optional<Path> getSourcePath(const Input & input) const override
@@ -399,15 +399,12 @@ struct GitInputScheme : InputScheme
if (commitMsg) {
auto [_fd, msgPath] = createTempFile("nix-msg");
AutoDelete const _delete{msgPath};
writeFile(msgPath, *commitMsg);
// Pause the logger to allow for user input (such as a gpg passphrase) in `git commit`
logger->pause();
Finally restoreLogger([]() { logger->resume(); });
runProgram("git", true,
{ "-C", *root, "--git-dir", gitDir, "commit", std::string(path.rel()), "-F", msgPath });
{ "-C", *root, "--git-dir", gitDir, "commit", std::string(path.rel()), "-F", "-" },
*commitMsg);
}
}
}
@@ -592,7 +589,7 @@ struct GitInputScheme : InputScheme
: ref == "HEAD"
? *ref
: "refs/heads/" + *ref;
runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "fetch", "--quiet", "--force", "--", actualUrl, fmt("%s:%s", fetchRef, fetchRef) }, true);
runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "fetch", "--quiet", "--force", "--", actualUrl, fmt("%s:%s", fetchRef, fetchRef) }, {}, true);
} catch (Error & e) {
if (!pathExists(localRefFile)) throw;
warn("could not update local clone of Git repository '%s'; continuing with the most recent version", actualUrl);
@@ -659,7 +656,7 @@ struct GitInputScheme : InputScheme
// everything to ensure we get the rev.
Activity act(*logger, lvlTalkative, actUnknown, fmt("making temporary clone of '%s'", repoDir));
runProgram("git", true, { "-C", tmpDir, "fetch", "--quiet", "--force",
"--update-head-ok", "--", repoDir, "refs/*:refs/*" }, true);
"--update-head-ok", "--", repoDir, "refs/*:refs/*" }, {}, true);
}
runProgram("git", true, { "-C", tmpDir, "checkout", "--quiet", input.getRev()->gitRev() });
@@ -686,19 +683,22 @@ struct GitInputScheme : InputScheme
{
Activity act(*logger, lvlTalkative, actUnknown, fmt("fetching submodules of '%s'", actualUrl));
runProgram("git", true, { "-C", tmpDir, "submodule", "--quiet", "update", "--init", "--recursive" }, true);
runProgram("git", true, { "-C", tmpDir, "submodule", "--quiet", "update", "--init", "--recursive" }, {}, true);
}
filter = isNotDotGitDirectory;
} else {
auto proc = runProgram2({
.program = "git",
.args = { "-C", repoDir, "--git-dir", gitDir, "archive", input.getRev()->gitRev() },
.captureStdout = true,
// FIXME: should pipe this, or find some better way to extract a
// revision.
auto source = sinkToSource([&](Sink & sink) {
runProgram2({
.program = "git",
.args = { "-C", repoDir, "--git-dir", gitDir, "archive", input.getRev()->gitRev() },
.standardOut = &sink
});
});
Finally const _wait([&] { proc.wait(); });
unpackTarfile(*proc.stdout(), tmpDir);
unpackTarfile(*source, tmpDir);
}
auto storePath = store->addToStore(name, tmpDir, FileIngestionMethod::Recursive, htSHA256, filter);
+2 -1
View File
@@ -28,9 +28,10 @@ static RunOptions hgOptions(const Strings & args)
}
// runProgram wrapper that uses hgOptions instead of stock RunOptions.
static std::string runHg(const Strings & args)
static std::string runHg(const Strings & args, const std::optional<std::string> & input = {})
{
RunOptions opts = hgOptions(args);
opts.input = input;
auto res = runProgram(std::move(opts));
+4 -2
View File
@@ -130,8 +130,10 @@ struct PathInputScheme : InputScheme
time_t mtime = 0;
if (!storePath || storePath->name() != "source" || !store->isValidPath(*storePath)) {
// FIXME: try to substitute storePath.
auto src = GeneratorSource{dumpPathAndGetMtime(absPath, mtime, defaultPathFilter)};
storePath = store->addToStoreFromDump(src, "source");
auto src = sinkToSource([&](Sink & sink) {
mtime = dumpPathAndGetMtime(absPath, sink, defaultPathFilter);
});
storePath = store->addToStoreFromDump(*src, "source");
}
input.attrs.insert_or_assign("lastModified", uint64_t(mtime));
+2 -2
View File
@@ -46,7 +46,7 @@ DownloadFileResult downloadFile(
request.expectedETag = getStrAttr(cached->infoAttrs, "etag");
FileTransferResult res;
try {
res = getFileTransfer()->transfer(request);
res = getFileTransfer()->download(request);
} catch (FileTransferError & e) {
if (cached) {
warn("%s; using cached version", e.msg());
@@ -71,7 +71,7 @@ DownloadFileResult downloadFile(
storePath = std::move(cached->storePath);
} else {
StringSink sink;
sink << dumpString(res.data);
dumpString(res.data, sink);
auto hash = hashString(htSHA256, res.data);
ValidPathInfo info {
*store,
+3 -4
View File
@@ -1,6 +1,5 @@
#include "common-args.hh"
#include "args/root.hh"
#include "error.hh"
#include "globals.hh"
#include "loggers.hh"
#include "logging.hh"
@@ -15,14 +14,14 @@ MixCommonArgs::MixCommonArgs(const std::string & programName)
.shortName = 'v',
.description = "Increase the logging verbosity level.",
.category = loggingCategory,
.handler = {[]() { verbosity = verbosityFromIntClamped(int(verbosity) + 1); }},
.handler = {[]() { verbosity = (Verbosity) (verbosity + 1); }},
});
addFlag({
.longName = "quiet",
.description = "Decrease the logging verbosity level.",
.category = loggingCategory,
.handler = {[]() { verbosity = verbosityFromIntClamped(int(verbosity) - 1); }},
.handler = {[]() { verbosity = verbosity > lvlError ? (Verbosity) (verbosity - 1) : lvlError; }},
});
addFlag({
@@ -58,7 +57,7 @@ MixCommonArgs::MixCommonArgs(const std::string & programName)
addFlag({
.longName = "log-format",
.description = "Set the format of log output; one of `raw`, `internal-json`, `bar`, `bar-with-logs`, `multiline` or `multiline-with-logs`.",
.description = "Set the format of log output; one of `raw`, `internal-json`, `bar` or `bar-with-logs`.",
.category = loggingCategory,
.labels = {"format"},
.handler = {[](std::string format) { setLogFormat(format); }},
-15
View File
@@ -17,10 +17,6 @@ LogFormat parseLogFormat(const std::string & logFormatStr) {
return LogFormat::bar;
else if (logFormatStr == "bar-with-logs")
return LogFormat::barWithLogs;
else if (logFormatStr == "multiline")
return LogFormat::multiline;
else if (logFormatStr == "multiline-with-logs")
return LogFormat::multilineWithLogs;
throw Error("option 'log-format' has an invalid value '%s'", logFormatStr);
}
@@ -39,17 +35,6 @@ Logger * makeDefaultLogger() {
logger->setPrintBuildLogs(true);
return logger;
}
case LogFormat::multiline: {
auto logger = makeProgressBar();
logger->setPrintMultiline(true);
return logger;
}
case LogFormat::multilineWithLogs: {
auto logger = makeProgressBar();
logger->setPrintMultiline(true);
logger->setPrintBuildLogs(true);
return logger;
}
default:
abort();
}
-2
View File
@@ -11,8 +11,6 @@ enum class LogFormat {
internalJSON,
bar,
barWithLogs,
multiline,
multilineWithLogs,
};
void setLogFormat(const std::string & logFormatStr);
+453 -476
View File
@@ -13,11 +13,6 @@
namespace nix {
// 100 years ought to be enough for anyone (yet sufficiently smaller than max() to not cause signed integer overflow).
constexpr const auto A_LONG_TIME = std::chrono::duration_cast<std::chrono::milliseconds>(
100 * 365 * std::chrono::seconds(86400)
);
using namespace std::literals::chrono_literals;
static std::string_view getS(const std::vector<Logger::Field> & fields, size_t n)
@@ -41,518 +36,500 @@ static std::string_view storePathToName(std::string_view path)
return i == std::string::npos ? base.substr(0, 0) : base.substr(i + 1);
}
ProgressBar::ProgressBar(bool isTTY)
: isTTY(isTTY)
// 100 years ought to be enough for anyone (yet sufficiently smaller than max() to not cause signed integer overflow).
constexpr const auto A_LONG_TIME = std::chrono::duration_cast<std::chrono::milliseconds>(100 * 365 * 86400s);
class ProgressBar : public Logger
{
state_.lock()->active = isTTY;
updateThread = std::thread([&]() {
auto state(state_.lock());
auto nextWakeup = A_LONG_TIME;
while (state->active) {
if (!state->haveUpdate)
state.wait_for(updateCV, nextWakeup);
nextWakeup = draw(*state, {});
state.wait_for(quitCV, std::chrono::milliseconds(50));
private:
struct ActInfo
{
std::string s, lastLine, phase;
ActivityType type = actUnknown;
uint64_t done = 0;
uint64_t expected = 0;
uint64_t running = 0;
uint64_t failed = 0;
std::map<ActivityType, uint64_t> expectedByType;
bool visible = true;
ActivityId parent;
std::optional<std::string> name;
std::chrono::time_point<std::chrono::steady_clock> startTime;
};
struct ActivitiesByType
{
std::map<ActivityId, std::list<ActInfo>::iterator> its;
uint64_t done = 0;
uint64_t expected = 0;
uint64_t failed = 0;
};
struct State
{
std::list<ActInfo> activities;
std::map<ActivityId, std::list<ActInfo>::iterator> its;
std::map<ActivityType, ActivitiesByType> activitiesByType;
uint64_t filesLinked = 0, bytesLinked = 0;
uint64_t corruptedPaths = 0, untrustedPaths = 0;
bool active = true;
bool paused = false;
bool haveUpdate = true;
};
Sync<State> state_;
std::thread updateThread;
std::condition_variable quitCV, updateCV;
bool printBuildLogs = false;
bool isTTY;
public:
ProgressBar(bool isTTY)
: isTTY(isTTY)
{
state_.lock()->active = isTTY;
updateThread = std::thread([&]() {
auto state(state_.lock());
auto nextWakeup = A_LONG_TIME;
while (state->active) {
if (!state->haveUpdate)
state.wait_for(updateCV, nextWakeup);
nextWakeup = draw(*state);
state.wait_for(quitCV, std::chrono::milliseconds(50));
}
});
}
~ProgressBar()
{
stop();
}
/* Called by destructor, can't be overridden */
void stop() override final
{
{
auto state(state_.lock());
if (!state->active) return;
state->active = false;
writeToStderr("\r\e[K");
updateCV.notify_one();
quitCV.notify_one();
}
});
}
updateThread.join();
}
ProgressBar::~ProgressBar()
{
stop();
}
void pause() override {
state_.lock()->paused = true;
writeToStderr("\r\e[K");
}
/* Called by destructor, can't be overridden */
void ProgressBar::stop()
{
void resume() override {
state_.lock()->paused = false;
writeToStderr("\r\e[K");
state_.lock()->haveUpdate = true;
updateCV.notify_one();
}
bool isVerbose() override
{
return printBuildLogs;
}
void log(Verbosity lvl, std::string_view s) override
{
if (lvl > verbosity) return;
auto state(state_.lock());
log(*state, lvl, s);
}
void logEI(const ErrorInfo & ei) override
{
auto state(state_.lock());
if (!state->active) return;
state->active = false;
writeToStderr("\r\e[K");
updateCV.notify_one();
quitCV.notify_one();
std::stringstream oss;
showErrorInfo(oss, ei, loggerSettings.showTrace.get());
log(*state, ei.level, oss.str());
}
updateThread.join();
}
void ProgressBar::pause()
{
state_.lock()->paused = true;
writeToStderr("\r\e[K");
}
void ProgressBar::resume()
{
state_.lock()->paused = false;
writeToStderr("\r\e[K");
state_.lock()->haveUpdate = true;
updateCV.notify_one();
}
bool ProgressBar::isVerbose()
{
return printBuildLogs;
}
void ProgressBar::log(Verbosity lvl, std::string_view s)
{
if (lvl > verbosity) return;
auto state(state_.lock());
log(*state, lvl, s);
}
void ProgressBar::logEI(const ErrorInfo & ei)
{
auto state(state_.lock());
std::stringstream oss;
showErrorInfo(oss, ei, loggerSettings.showTrace.get());
log(*state, ei.level, oss.str());
}
void ProgressBar::log(State & state, Verbosity lvl, std::string_view s)
{
if (state.active) {
draw(state, s);
} else {
auto s2 = s + ANSI_NORMAL "\n";
if (!isTTY) s2 = filterANSIEscapes(s2, true);
writeToStderr(s2);
}
}
void ProgressBar::startActivity(
ActivityId act,
Verbosity lvl,
ActivityType type,
const std::string & s,
const Fields & fields,
ActivityId parent
)
{
auto state(state_.lock());
if (lvl <= verbosity && !s.empty() && type != actBuildWaiting)
log(*state, lvl, s + "...");
state->activities.emplace_back(ActInfo {
.s = s,
.type = type,
.parent = parent,
.startTime = std::chrono::steady_clock::now()
});
auto i = std::prev(state->activities.end());
state->its.emplace(act, i);
state->activitiesByType[type].its.emplace(act, i);
if (type == actBuild) {
std::string name(storePathToName(getS(fields, 0)));
if (name.ends_with(".drv"))
name = name.substr(0, name.size() - 4);
i->s = fmt("building " ANSI_BOLD "%s" ANSI_NORMAL, name);
auto machineName = getS(fields, 1);
if (machineName != "")
i->s += fmt(" on " ANSI_BOLD "%s" ANSI_NORMAL, machineName);
// Used to be curRound and nrRounds, but the
// implementation was broken for a long time.
if (getI(fields, 2) != 1 || getI(fields, 3) != 1) {
throw Error("log message indicated repeating builds, but this is not currently implemented");
void log(State & state, Verbosity lvl, std::string_view s)
{
if (state.active) {
writeToStderr("\r\e[K" + filterANSIEscapes(s, !isTTY) + ANSI_NORMAL "\n");
draw(state);
} else {
auto s2 = s + ANSI_NORMAL "\n";
if (!isTTY) s2 = filterANSIEscapes(s2, true);
writeToStderr(s2);
}
i->name = DrvName(name).name;
}
if (type == actSubstitute) {
auto name = storePathToName(getS(fields, 0));
auto sub = getS(fields, 1);
i->s = fmt(
sub.starts_with("local")
? "copying " ANSI_BOLD "%s" ANSI_NORMAL " from %s"
: "fetching " ANSI_BOLD "%s" ANSI_NORMAL " from %s",
name, sub);
}
void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
const std::string & s, const Fields & fields, ActivityId parent) override
{
auto state(state_.lock());
if (type == actPostBuildHook) {
auto name = storePathToName(getS(fields, 0));
if (name.ends_with(".drv"))
name = name.substr(0, name.size() - 4);
i->s = fmt("post-build " ANSI_BOLD "%s" ANSI_NORMAL, name);
i->name = DrvName(name).name;
}
if (lvl <= verbosity && !s.empty() && type != actBuildWaiting)
log(*state, lvl, s + "...");
if (type == actQueryPathInfo) {
auto name = storePathToName(getS(fields, 0));
i->s = fmt("querying " ANSI_BOLD "%s" ANSI_NORMAL " on %s", name, getS(fields, 1));
}
state->activities.emplace_back(ActInfo {
.s = s,
.type = type,
.parent = parent,
.startTime = std::chrono::steady_clock::now()
});
auto i = std::prev(state->activities.end());
state->its.emplace(act, i);
state->activitiesByType[type].its.emplace(act, i);
if ((type == actFileTransfer && hasAncestor(*state, actCopyPath, parent))
|| (type == actFileTransfer && hasAncestor(*state, actQueryPathInfo, parent))
|| (type == actCopyPath && hasAncestor(*state, actSubstitute, parent)))
i->visible = false;
if (type == actBuild) {
std::string name(storePathToName(getS(fields, 0)));
if (name.ends_with(".drv"))
name = name.substr(0, name.size() - 4);
i->s = fmt("building " ANSI_BOLD "%s" ANSI_NORMAL, name);
auto machineName = getS(fields, 1);
if (machineName != "")
i->s += fmt(" on " ANSI_BOLD "%s" ANSI_NORMAL, machineName);
update(*state);
}
// Used to be curRound and nrRounds, but the
// implementation was broken for a long time.
if (getI(fields, 2) != 1 || getI(fields, 3) != 1) {
throw Error("log message indicated repeating builds, but this is not currently implemented");
}
i->name = DrvName(name).name;
}
/* Check whether an activity has an ancestore with the specified
type. */
bool ProgressBar::hasAncestor(State & state, ActivityType type, ActivityId act)
{
while (act != 0) {
auto i = state.its.find(act);
if (i == state.its.end()) break;
if (i->second->type == type) return true;
act = i->second->parent;
}
return false;
}
if (type == actSubstitute) {
auto name = storePathToName(getS(fields, 0));
auto sub = getS(fields, 1);
i->s = fmt(
sub.starts_with("local")
? "copying " ANSI_BOLD "%s" ANSI_NORMAL " from %s"
: "fetching " ANSI_BOLD "%s" ANSI_NORMAL " from %s",
name, sub);
}
void ProgressBar::stopActivity(ActivityId act)
{
auto state(state_.lock());
if (type == actPostBuildHook) {
auto name = storePathToName(getS(fields, 0));
if (name.ends_with(".drv"))
name = name.substr(0, name.size() - 4);
i->s = fmt("post-build " ANSI_BOLD "%s" ANSI_NORMAL, name);
i->name = DrvName(name).name;
}
auto i = state->its.find(act);
if (i != state->its.end()) {
if (type == actQueryPathInfo) {
auto name = storePathToName(getS(fields, 0));
i->s = fmt("querying " ANSI_BOLD "%s" ANSI_NORMAL " on %s", name, getS(fields, 1));
}
auto & actByType = state->activitiesByType[i->second->type];
actByType.done += i->second->done;
actByType.failed += i->second->failed;
if ((type == actFileTransfer && hasAncestor(*state, actCopyPath, parent))
|| (type == actFileTransfer && hasAncestor(*state, actQueryPathInfo, parent))
|| (type == actCopyPath && hasAncestor(*state, actSubstitute, parent)))
i->visible = false;
for (auto & j : i->second->expectedByType)
state->activitiesByType[j.first].expected -= j.second;
actByType.its.erase(act);
state->activities.erase(i->second);
state->its.erase(i);
}
update(*state);
}
void ProgressBar::result(ActivityId act, ResultType type, const std::vector<Field> & fields)
{
auto state(state_.lock());
if (type == resFileLinked) {
state->filesLinked++;
state->bytesLinked += getI(fields, 0);
update(*state);
}
else if (type == resBuildLogLine || type == resPostBuildLogLine) {
auto lastLine = chomp(getS(fields, 0));
if (!lastLine.empty()) {
auto i = state->its.find(act);
assert(i != state->its.end());
ActInfo info = *i->second;
if (printBuildLogs) {
auto suffix = "> ";
if (type == resPostBuildLogLine) {
suffix = " (post)> ";
}
log(*state, lvlInfo, ANSI_FAINT + info.name.value_or("unnamed") + suffix + ANSI_NORMAL + lastLine);
} else {
if (!printMultiline) {
/* Check whether an activity has an ancestore with the specified
type. */
bool hasAncestor(State & state, ActivityType type, ActivityId act)
{
while (act != 0) {
auto i = state.its.find(act);
if (i == state.its.end()) break;
if (i->second->type == type) return true;
act = i->second->parent;
}
return false;
}
void stopActivity(ActivityId act) override
{
auto state(state_.lock());
auto i = state->its.find(act);
if (i != state->its.end()) {
auto & actByType = state->activitiesByType[i->second->type];
actByType.done += i->second->done;
actByType.failed += i->second->failed;
for (auto & j : i->second->expectedByType)
state->activitiesByType[j.first].expected -= j.second;
actByType.its.erase(act);
state->activities.erase(i->second);
state->its.erase(i);
}
update(*state);
}
void result(ActivityId act, ResultType type, const std::vector<Field> & fields) override
{
auto state(state_.lock());
if (type == resFileLinked) {
state->filesLinked++;
state->bytesLinked += getI(fields, 0);
update(*state);
}
else if (type == resBuildLogLine || type == resPostBuildLogLine) {
auto lastLine = chomp(getS(fields, 0));
if (!lastLine.empty()) {
auto i = state->its.find(act);
assert(i != state->its.end());
ActInfo info = *i->second;
if (printBuildLogs) {
auto suffix = "> ";
if (type == resPostBuildLogLine) {
suffix = " (post)> ";
}
log(*state, lvlInfo, ANSI_FAINT + info.name.value_or("unnamed") + suffix + ANSI_NORMAL + lastLine);
} else {
state->activities.erase(i->second);
info.lastLine = lastLine;
state->activities.emplace_back(info);
i->second = std::prev(state->activities.end());
} else {
i->second->lastLine = lastLine;
update(*state);
}
update(*state);
}
}
else if (type == resUntrustedPath) {
state->untrustedPaths++;
update(*state);
}
else if (type == resCorruptedPath) {
state->corruptedPaths++;
update(*state);
}
else if (type == resSetPhase) {
auto i = state->its.find(act);
assert(i != state->its.end());
i->second->phase = getS(fields, 0);
update(*state);
}
else if (type == resProgress) {
auto i = state->its.find(act);
assert(i != state->its.end());
ActInfo & actInfo = *i->second;
actInfo.done = getI(fields, 0);
actInfo.expected = getI(fields, 1);
actInfo.running = getI(fields, 2);
actInfo.failed = getI(fields, 3);
update(*state);
}
else if (type == resSetExpected) {
auto i = state->its.find(act);
assert(i != state->its.end());
ActInfo & actInfo = *i->second;
auto type = (ActivityType) getI(fields, 0);
auto & j = actInfo.expectedByType[type];
state->activitiesByType[type].expected -= j;
j = getI(fields, 1);
state->activitiesByType[type].expected += j;
update(*state);
}
}
else if (type == resUntrustedPath) {
state->untrustedPaths++;
update(*state);
}
else if (type == resCorruptedPath) {
state->corruptedPaths++;
update(*state);
}
else if (type == resSetPhase) {
auto i = state->its.find(act);
assert(i != state->its.end());
i->second->phase = getS(fields, 0);
update(*state);
}
else if (type == resProgress) {
auto i = state->its.find(act);
assert(i != state->its.end());
ActInfo & actInfo = *i->second;
actInfo.done = getI(fields, 0);
actInfo.expected = getI(fields, 1);
actInfo.running = getI(fields, 2);
actInfo.failed = getI(fields, 3);
update(*state);
}
else if (type == resSetExpected) {
auto i = state->its.find(act);
assert(i != state->its.end());
ActInfo & actInfo = *i->second;
auto type = (ActivityType) getI(fields, 0);
auto & j = actInfo.expectedByType[type];
state->activitiesByType[type].expected -= j;
j = getI(fields, 1);
state->activitiesByType[type].expected += j;
update(*state);
}
}
void ProgressBar::update(State & state)
{
state.haveUpdate = true;
updateCV.notify_one();
}
std::chrono::milliseconds ProgressBar::draw(State & state, const std::optional<std::string_view> & s)
{
auto nextWakeup = A_LONG_TIME;
state.haveUpdate = false;
if (state.paused || !state.active) return nextWakeup;
auto windowSize = getWindowSize();
auto width = windowSize.second;
if (width <= 0) {
width = std::numeric_limits<decltype(width)>::max();
}
if (printMultiline && (state.lastLines >= 1)) {
// FIXME: make sure this works on windows
writeToStderr(fmt("\e[G\e[%dF\e[J", state.lastLines));
}
state.lastLines = 0;
if (s != std::nullopt)
writeToStderr("\r\e[K" + filterANSIEscapes(s.value(), !isTTY) + ANSI_NORMAL "\n");
std::string line;
std::string status = getStatus(state);
if (!status.empty()) {
line += '[';
line += status;
line += "]";
}
if (printMultiline && !line.empty()) {
writeToStderr(filterANSIEscapes(line, false, width) + "\n");
state.lastLines++;
}
auto height = windowSize.first > 0 ? windowSize.first : 25;
auto moreActivities = 0;
auto now = std::chrono::steady_clock::now();
std::string activity_line;
if (!state.activities.empty()) {
for (auto i = state.activities.begin(); i != state.activities.end(); ++i) {
if (!(i->visible && (!i->s.empty() || !i->lastLine.empty()))) {
continue;
}
/* Don't show activities until some time has
passed, to avoid displaying very short
activities. */
auto delay = std::chrono::milliseconds(10);
if (i->startTime + delay >= now) {
nextWakeup = std::min(
nextWakeup,
std::chrono::duration_cast<std::chrono::milliseconds>(
delay - (now - i->startTime)
)
);
}
activity_line = i->s;
if (!i->phase.empty()) {
activity_line += " (";
activity_line += i->phase;
activity_line += ")";
}
if (!i->lastLine.empty()) {
if (!i->s.empty())
activity_line += ": ";
activity_line += i->lastLine;
}
if (printMultiline) {
if (state.lastLines < (height -1)) {
writeToStderr(filterANSIEscapes(activity_line, false, width) + "\n");
state.lastLines++;
} else moreActivities++;
}
}
}
if (printMultiline && moreActivities)
writeToStderr(fmt("And %d more...", moreActivities));
if (!printMultiline && !line.empty()) {
line += " " + activity_line;
writeToStderr("\r" + filterANSIEscapes(line, false, width) + ANSI_NORMAL + "\e[K");
}
return nextWakeup;
}
std::string ProgressBar::getStatus(State & state)
{
constexpr auto MiB = 1024.0 * 1024.0;
std::string res;
auto renderActivity = [&](ActivityType type, const std::string & itemFmt, const std::string & numberFmt = "%d", double unit = 1) {
auto & act = state.activitiesByType[type];
uint64_t done = act.done, expected = act.done, running = 0, failed = act.failed;
for (auto & [actId, infoIt] : act.its) {
done += infoIt->done;
expected += infoIt->expected;
running += infoIt->running;
failed += infoIt->failed;
}
expected = std::max(expected, act.expected);
std::string rendered;
if (running || done || expected || failed) {
if (running) {
if (expected != 0) {
auto const runningPart = fmt(numberFmt, running / unit);
auto const donePart = fmt(numberFmt, done / unit);
auto const expectedPart = fmt(numberFmt, expected / unit);
rendered = fmt(
ANSI_BLUE "%s" ANSI_NORMAL "/" ANSI_GREEN "%s" ANSI_NORMAL "/%s",
runningPart,
donePart,
expectedPart
);
} else {
auto const runningPart = fmt(numberFmt, running / unit);
auto const donePart = fmt(numberFmt, done / unit);
rendered = fmt(
ANSI_BLUE "%s" ANSI_NORMAL "/" ANSI_GREEN "%s" ANSI_NORMAL,
runningPart,
donePart
);
}
} else if (expected != done) {
if (expected != 0) {
auto const donePart = fmt(numberFmt, done / unit);
auto const expectedPart = fmt(numberFmt, expected / unit);
rendered = fmt(
ANSI_GREEN "%s" ANSI_NORMAL "/%s",
donePart,
expectedPart
);
} else {
auto const donePart = fmt(numberFmt, done / unit);
rendered = fmt(ANSI_GREEN "%s" ANSI_NORMAL, donePart);
}
} else {
auto const donePart = fmt(numberFmt, done / unit);
// We only color if `done` is non-zero.
if (done) {
rendered = concatStrings(ANSI_GREEN, donePart, ANSI_NORMAL);
} else {
rendered = donePart;
}
}
rendered = fmt(itemFmt, rendered);
if (failed)
rendered += fmt(" (" ANSI_RED "%d failed" ANSI_NORMAL ")", failed / unit);
}
return rendered;
};
auto showActivity = [&](ActivityType type, const std::string & itemFmt, const std::string & numberFmt = "%d", double unit = 1) {
auto s = renderActivity(type, itemFmt, numberFmt, unit);
if (s.empty()) return;
if (!res.empty()) res += ", ";
res += s;
};
showActivity(actBuilds, "%s built");
auto s1 = renderActivity(actCopyPaths, "%s copied");
auto s2 = renderActivity(actCopyPath, "%s MiB", "%.1f", MiB);
if (!s1.empty() || !s2.empty()) {
if (!res.empty()) res += ", ";
if (s1.empty()) res += "0 copied"; else res += s1;
if (!s2.empty()) { res += " ("; res += s2; res += ')'; }
}
showActivity(actFileTransfer, "%s MiB DL", "%.1f", MiB);
void update(State & state)
{
auto s = renderActivity(actOptimiseStore, "%s paths optimised");
if (s != "") {
s += fmt(", %.1f MiB / %d inodes freed", state.bytesLinked / MiB, state.filesLinked);
state.haveUpdate = true;
updateCV.notify_one();
}
std::chrono::milliseconds draw(State & state)
{
auto nextWakeup = A_LONG_TIME;
state.haveUpdate = false;
if (state.paused || !state.active) return nextWakeup;
std::string line;
std::string status = getStatus(state);
if (!status.empty()) {
line += '[';
line += status;
line += "]";
}
auto now = std::chrono::steady_clock::now();
if (!state.activities.empty()) {
if (!status.empty()) line += " ";
auto i = state.activities.rbegin();
while (i != state.activities.rend()) {
if (i->visible && (!i->s.empty() || !i->lastLine.empty())) {
/* Don't show activities until some time has
passed, to avoid displaying very short
activities. */
auto delay = std::chrono::milliseconds(10);
if (i->startTime + delay < now)
break;
else
nextWakeup = std::min(nextWakeup, std::chrono::duration_cast<std::chrono::milliseconds>(delay - (now - i->startTime)));
}
++i;
}
if (i != state.activities.rend()) {
line += i->s;
if (!i->phase.empty()) {
line += " (";
line += i->phase;
line += ")";
}
if (!i->lastLine.empty()) {
if (!i->s.empty()) line += ": ";
line += i->lastLine;
}
}
}
auto width = getWindowSize().second;
if (width <= 0) width = std::numeric_limits<decltype(width)>::max();
writeToStderr("\r" + filterANSIEscapes(line, false, width) + ANSI_NORMAL + "\e[K");
return nextWakeup;
}
std::string getStatus(State & state)
{
auto MiB = 1024.0 * 1024.0;
std::string res;
auto renderActivity = [&](ActivityType type, const std::string & itemFmt, const std::string & numberFmt = "%d", double unit = 1) {
auto & act = state.activitiesByType[type];
uint64_t done = act.done, expected = act.done, running = 0, failed = act.failed;
for (auto & j : act.its) {
done += j.second->done;
expected += j.second->expected;
running += j.second->running;
failed += j.second->failed;
}
expected = std::max(expected, act.expected);
std::string s;
if (running || done || expected || failed) {
if (running)
if (expected != 0)
s = fmt(ANSI_BLUE + numberFmt + ANSI_NORMAL "/" ANSI_GREEN + numberFmt + ANSI_NORMAL "/" + numberFmt,
running / unit, done / unit, expected / unit);
else
s = fmt(ANSI_BLUE + numberFmt + ANSI_NORMAL "/" ANSI_GREEN + numberFmt + ANSI_NORMAL,
running / unit, done / unit);
else if (expected != done)
if (expected != 0)
s = fmt(ANSI_GREEN + numberFmt + ANSI_NORMAL "/" + numberFmt,
done / unit, expected / unit);
else
s = fmt(ANSI_GREEN + numberFmt + ANSI_NORMAL, done / unit);
else
s = fmt(done ? ANSI_GREEN + numberFmt + ANSI_NORMAL : numberFmt, done / unit);
s = fmt(itemFmt, s);
if (failed)
s += fmt(" (" ANSI_RED "%d failed" ANSI_NORMAL ")", failed / unit);
}
return s;
};
auto showActivity = [&](ActivityType type, const std::string & itemFmt, const std::string & numberFmt = "%d", double unit = 1) {
auto s = renderActivity(type, itemFmt, numberFmt, unit);
if (s.empty()) return;
if (!res.empty()) res += ", ";
res += s;
};
showActivity(actBuilds, "%s built");
auto s1 = renderActivity(actCopyPaths, "%s copied");
auto s2 = renderActivity(actCopyPath, "%s MiB", "%.1f", MiB);
if (!s1.empty() || !s2.empty()) {
if (!res.empty()) res += ", ";
if (s1.empty()) res += "0 copied"; else res += s1;
if (!s2.empty()) { res += " ("; res += s2; res += ')'; }
}
showActivity(actFileTransfer, "%s MiB DL", "%.1f", MiB);
{
auto s = renderActivity(actOptimiseStore, "%s paths optimised");
if (s != "") {
s += fmt(", %.1f MiB / %d inodes freed", state.bytesLinked / MiB, state.filesLinked);
if (!res.empty()) res += ", ";
res += s;
}
}
// FIXME: don't show "done" paths in green.
showActivity(actVerifyPaths, "%s paths verified");
if (state.corruptedPaths) {
if (!res.empty()) res += ", ";
res += fmt(ANSI_RED "%d corrupted" ANSI_NORMAL, state.corruptedPaths);
}
if (state.untrustedPaths) {
if (!res.empty()) res += ", ";
res += fmt(ANSI_RED "%d untrusted" ANSI_NORMAL, state.untrustedPaths);
}
return res;
}
void writeToStdout(std::string_view s) override
{
auto state(state_.lock());
if (state->active) {
std::cerr << "\r\e[K";
Logger::writeToStdout(s);
draw(*state);
} else {
Logger::writeToStdout(s);
}
}
// FIXME: don't show "done" paths in green.
showActivity(actVerifyPaths, "%s paths verified");
if (state.corruptedPaths) {
if (!res.empty()) res += ", ";
res += fmt(ANSI_RED "%d corrupted" ANSI_NORMAL, state.corruptedPaths);
std::optional<char> ask(std::string_view msg) override
{
auto state(state_.lock());
if (!state->active || !isatty(STDIN_FILENO)) return {};
std::cerr << fmt("\r\e[K%s ", msg);
auto s = trim(readLine(STDIN_FILENO));
if (s.size() != 1) return {};
draw(*state);
return s[0];
}
if (state.untrustedPaths) {
if (!res.empty()) res += ", ";
res += fmt(ANSI_RED "%d untrusted" ANSI_NORMAL, state.untrustedPaths);
void setPrintBuildLogs(bool printBuildLogs) override
{
this->printBuildLogs = printBuildLogs;
}
return res;
}
void ProgressBar::writeToStdout(std::string_view s)
{
auto state(state_.lock());
if (state->active) {
Logger::writeToStdout(s);
draw(*state, {});
} else {
Logger::writeToStdout(s);
}
}
std::optional<char> ProgressBar::ask(std::string_view msg)
{
auto state(state_.lock());
if (!state->active || !isatty(STDIN_FILENO)) return {};
std::cerr << fmt("\r\e[K%s ", msg);
auto s = trim(readLine(STDIN_FILENO));
if (s.size() != 1) return {};
draw(*state, {});
return s[0];
}
void ProgressBar::setPrintBuildLogs(bool printBuildLogs)
{
this->printBuildLogs = printBuildLogs;
}
void ProgressBar::setPrintMultiline(bool printMultiline)
{
this->printMultiline = printMultiline;
}
};
Logger * makeProgressBar()
{
-106
View File
@@ -1,116 +1,10 @@
#pragma once
///@file
#include <chrono>
#include "logging.hh"
#include "sync.hh"
namespace nix {
struct ProgressBar : public Logger
{
struct ActInfo
{
using TimePoint = std::chrono::time_point<std::chrono::steady_clock>;
std::string s, lastLine, phase;
ActivityType type = actUnknown;
uint64_t done = 0;
uint64_t expected = 0;
uint64_t running = 0;
uint64_t failed = 0;
std::map<ActivityType, uint64_t> expectedByType;
bool visible = true;
ActivityId parent;
std::optional<std::string> name;
TimePoint startTime;
};
struct ActivitiesByType
{
std::map<ActivityId, std::list<ActInfo>::iterator> its;
uint64_t done = 0;
uint64_t expected = 0;
uint64_t failed = 0;
};
struct State
{
std::list<ActInfo> activities;
std::map<ActivityId, std::list<ActInfo>::iterator> its;
std::map<ActivityType, ActivitiesByType> activitiesByType;
int lastLines = 0;
uint64_t filesLinked = 0, bytesLinked = 0;
uint64_t corruptedPaths = 0, untrustedPaths = 0;
bool active = true;
bool paused = false;
bool haveUpdate = true;
};
Sync<State> state_;
std::thread updateThread;
std::condition_variable quitCV, updateCV;
bool printBuildLogs = false;
bool printMultiline = false;
bool isTTY;
ProgressBar(bool isTTY);
~ProgressBar();
void stop() override final;
void pause() override;
void resume() override;
bool isVerbose() override;
void log(Verbosity lvl, std::string_view s) override;
void logEI(const ErrorInfo & ei) override;
void log(State & state, Verbosity lvl, std::string_view s);
void startActivity(
ActivityId act,
Verbosity lvl,
ActivityType type,
const std::string & s,
const Fields & fields,
ActivityId parent
) override;
bool hasAncestor(State & state, ActivityType type, ActivityId act);
void stopActivity(ActivityId act) override;
void result(ActivityId act, ResultType type, const std::vector<Field> & fields) override;
void update(State & state);
std::chrono::milliseconds draw(State & state, const std::optional<std::string_view> & s);
std::string getStatus(State & state);
void writeToStdout(std::string_view s) override;
std::optional<char> ask(std::string_view msg) override;
void setPrintBuildLogs(bool printBuildLogs) override;
void setPrintMultiline(bool printMultiline) override;
};
Logger * makeProgressBar();
void startProgressBar();
+1 -1
View File
@@ -378,7 +378,7 @@ RunPager::RunPager()
RunPager::~RunPager()
{
try {
if (pid) {
if (pid != -1) {
std::cout.flush();
dup2(std_out, STDOUT_FILENO);
pid.wait();
+30 -36
View File
@@ -38,7 +38,7 @@ void BinaryCacheStore::init()
{
std::string cacheInfoFile = "nix-cache-info";
auto cacheInfo = getFileContents(cacheInfoFile);
auto cacheInfo = getFile(cacheInfoFile);
if (!cacheInfo) {
upsertFile(cacheInfoFile, "StoreDir: " + storeDir + "\n", "text/x-nix-cache-info");
} else {
@@ -67,18 +67,16 @@ void BinaryCacheStore::upsertFile(const std::string & path,
upsertFile(path, std::make_shared<std::stringstream>(std::move(data)), mimeType);
}
box_ptr<Source> BinaryCacheStore::getFile(const std::string & path)
void BinaryCacheStore::getFile(const std::string & path, Sink & sink)
{
return make_box_ptr<GeneratorSource>([](std::string data) -> Generator<Bytes> {
co_yield std::span{data.data(), data.size()};
}(std::move(*getFileContents(path))));
sink(*getFile(path));
}
std::optional<std::string> BinaryCacheStore::getFileContents(const std::string & path)
std::optional<std::string> BinaryCacheStore::getFile(const std::string & path)
{
StringSink sink;
try {
return getFile(path)->drain();
getFile(path, sink);
} catch (NoSuchBinaryCacheFile &) {
return std::nullopt;
}
@@ -172,7 +170,7 @@ ref<const ValidPathInfo> BinaryCacheStore::addToStoreCommon(
if (ref != info.path)
queryPathInfo(ref);
} catch (InvalidPath &) {
throw Error("cannot add '%s' to the binary cache because the reference '%s' does not exist",
throw Error("cannot add '%s' to the binary cache because the reference '%s' is not valid",
printStorePath(info.path), printStorePath(ref));
}
@@ -329,32 +327,26 @@ std::optional<StorePath> BinaryCacheStore::queryPathFromHashPart(const std::stri
}
}
WireFormatGenerator BinaryCacheStore::narFromPath(const StorePath & storePath)
void BinaryCacheStore::narFromPath(const StorePath & storePath, Sink & sink)
{
auto info = queryPathInfo(storePath).cast<const NarInfo>();
try {
auto file = getFile(info->url);
return [](auto info, auto file, auto & stats) -> WireFormatGenerator {
char buf[65536];
size_t total = 0;
auto decompressor = makeDecompressionSource(info->compression, *file);
try {
while (true) {
const auto len = decompressor->read(buf, sizeof(buf));
co_yield std::span{buf, len};
total += len;
}
} catch (EndOfFile &) {
}
LengthSink narSize;
TeeSink tee { sink, narSize };
stats.narRead++;
//stats.narReadCompressedBytes += nar->size(); // FIXME
stats.narReadBytes += total;
}(std::move(info), std::move(file), stats);
auto decompressor = makeDecompressionSink(info->compression, tee);
try {
getFile(info->url, *decompressor);
} catch (NoSuchBinaryCacheFile & e) {
throw SubstituteGone(std::move(e.info()));
}
decompressor->finish();
stats.narRead++;
//stats.narReadCompressedBytes += nar->size(); // FIXME
stats.narReadBytes += narSize.length;
}
std::shared_ptr<const ValidPathInfo> BinaryCacheStore::queryPathInfoUncached(const StorePath & storePath)
@@ -367,9 +359,9 @@ std::shared_ptr<const ValidPathInfo> BinaryCacheStore::queryPathInfoUncached(con
auto narInfoFile = narInfoFileFor(storePath);
auto data = getFileContents(narInfoFile);
auto data = getFile(narInfoFile);
if (!data) return nullptr;
if (!data) return {};
stats.narInfoRead++;
@@ -391,14 +383,16 @@ StorePath BinaryCacheStore::addToStore(
HashSink sink { hashAlgo };
if (method == FileIngestionMethod::Recursive) {
sink << dumpPath(srcPath, filter);
dumpPath(srcPath, sink, filter);
} else {
sink << readFileSource(srcPath);
readFile(srcPath, sink);
}
auto h = sink.finish().first;
auto source = GeneratorSource{dumpPath(srcPath, filter)};
return addToStoreCommon(source, repair, CheckSigs, [&](HashResult nar) {
auto source = sinkToSource([&](Sink & sink) {
dumpPath(srcPath, sink, filter);
});
return addToStoreCommon(*source, repair, CheckSigs, [&](HashResult nar) {
ValidPathInfo info {
*this,
name,
@@ -431,7 +425,7 @@ StorePath BinaryCacheStore::addTextToStore(
return path;
StringSink sink;
sink << dumpString(s);
dumpString(s, sink);
StringSource source(sink.s);
return addToStoreCommon(source, repair, CheckSigs, [&](HashResult nar) {
ValidPathInfo info {
@@ -452,7 +446,7 @@ std::shared_ptr<const Realisation> BinaryCacheStore::queryRealisationUncached(co
{
auto outputInfoFilePath = realisationsPrefix + "/" + id.to_string() + ".doi";
auto data = getFileContents(outputInfoFilePath);
auto data = getFile(outputInfoFilePath);
if (!data) return {};
auto realisation = Realisation::fromJSON(
@@ -492,7 +486,7 @@ std::optional<std::string> BinaryCacheStore::getBuildLogExact(const StorePath &
debug("fetching build log from binary cache '%s/%s'", getUri(), logPath);
return getFileContents(logPath);
return getFile(logPath);
}
void BinaryCacheStore::addBuildLog(const StorePath & drvPath, std::string_view log)
+3 -3
View File
@@ -83,9 +83,9 @@ public:
/**
* Dump the contents of the specified file to a sink.
*/
virtual box_ptr<Source> getFile(const std::string & path);
virtual void getFile(const std::string & path, Sink & sink);
virtual std::optional<std::string> getFileContents(const std::string & path);
virtual std::optional<std::string> getFile(const std::string & path);
public:
@@ -136,7 +136,7 @@ public:
std::shared_ptr<const Realisation> queryRealisationUncached(const DrvOutput &) override;
WireFormatGenerator narFromPath(const StorePath & path) override;
void narFromPath(const StorePath & path, Sink & sink) override;
ref<FSAccessor> getFSAccessor() override;
+6 -11
View File
@@ -760,7 +760,7 @@ void DerivationGoal::tryToBuild()
/* Not now; wait until at least one child finishes or
the wake-up timeout expires. */
if (!actLock)
actLock = std::make_unique<Activity>(*logger, lvlTalkative, actBuildWaiting,
actLock = std::make_unique<Activity>(*logger, lvlWarn, actBuildWaiting,
fmt("waiting for a machine to build '%s'", Magenta(worker.store.printStorePath(drvPath))));
worker.waitForAWhile(shared_from_this());
outputLocks.unlock();
@@ -923,16 +923,12 @@ void runPostBuildHook(
};
LogSink sink(act);
auto proc = runProgram2({
runProgram2({
.program = settings.postBuildHook,
.environment = hookEnvironment,
.captureStdout = true,
.standardOut = &sink,
.mergeStderrToStdout = true,
});
Finally const _wait([&] { proc.wait(); });
// FIXME just process the data, without a wrapper sink class
proc.stdout()->drainInto(sink);
}
void DerivationGoal::buildDone()
@@ -1208,7 +1204,7 @@ HookReply DerivationGoal::tryBuildHook()
/* Tell the hook all the inputs that have to be copied to the
remote system. */
conn.to << CommonProto::write(worker.store, conn, inputPaths);
CommonProto::write(worker.store, conn, inputPaths);
/* Tell the hooks the missing outputs that have to be copied back
from the remote system. */
@@ -1219,7 +1215,7 @@ HookReply DerivationGoal::tryBuildHook()
if (buildMode != bmCheck && status.known && status.known->isValid()) continue;
missingOutputs.insert(outputName);
}
conn.to << CommonProto::write(worker.store, conn, missingOutputs);
CommonProto::write(worker.store, conn, missingOutputs);
}
hook->sink = FdSink();
@@ -1557,12 +1553,11 @@ void DerivationGoal::waiteeDone(GoalPtr waitee, ExitCode result)
Goal::waiteeDone(waitee, result);
if (!useDerivation) return;
auto & fullDrv = *dynamic_cast<Derivation *>(drv.get());
auto * dg = dynamic_cast<DerivationGoal *>(&*waitee);
if (!dg) return;
auto & fullDrv = *dynamic_cast<Derivation *>(drv.get());
auto * nodeP = fullDrv.inputDrvs.findSlot(DerivedPath::Opaque { .path = dg->drvPath });
if (!nodeP) return;
auto & outputs = nodeP->value;
+1 -1
View File
@@ -79,7 +79,7 @@ HookInstance::~HookInstance()
{
try {
toHook.writeSide.reset();
if (pid) pid.kill();
if (pid != -1) pid.kill();
} catch (...) {
ignoreException();
}
+75 -36
View File
@@ -51,6 +51,9 @@
#endif
#if __APPLE__
#include <spawn.h>
#include <sys/sysctl.h>
/* This definition is undocumented but depended upon by all major browsers. */
extern "C" int sandbox_init_with_parameters(const char *profile, uint64_t flags, const char *const parameters[], char **errorbuf);
#endif
@@ -137,7 +140,7 @@ LocalStore & LocalDerivationGoal::getLocalStore()
void LocalDerivationGoal::killChild()
{
if (pid) {
if (pid != -1) {
worker.childTerminated(this);
/* If we're using a build user, then there is a tricky race
@@ -145,7 +148,7 @@ void LocalDerivationGoal::killChild()
done its setuid() to the build user uid, then it won't be
killed, and we'll potentially lock up in pid.wait(). So
also send a conventional kill to the child. */
::kill(-pid.get(), SIGKILL); /* ignore the result */
::kill(-pid, SIGKILL); /* ignore the result */
killSandbox(true);
@@ -158,7 +161,19 @@ void LocalDerivationGoal::killChild()
void LocalDerivationGoal::killSandbox(bool getStats)
{
if (buildUser) {
if (cgroup) {
#if __linux__
auto stats = destroyCgroup(*cgroup);
if (getStats) {
buildResult.cpuUser = stats.cpuUser;
buildResult.cpuSystem = stats.cpuSystem;
}
#else
abort();
#endif
}
else if (buildUser) {
auto uid = buildUser->getUID();
assert(uid != 0);
killUser(uid);
@@ -495,7 +510,7 @@ void LocalDerivationGoal::startBuilder()
/* Create a temporary directory where the build will take
place. */
tmpDir = createTempDir(settings.buildDir.get().value_or(""), "nix-build-" + std::string(drvPath.name()), false, false, 0700);
tmpDir = createTempDir("", "nix-build-" + std::string(drvPath.name()), false, false, 0700);
chownToBuilder(tmpDir);
@@ -930,7 +945,7 @@ void LocalDerivationGoal::startBuilder()
if (usingUserNamespace)
options.cloneFlags |= CLONE_NEWUSER;
pid_t child = startProcess([&]() { runChild(); }, options).release();
pid_t child = startProcess([&]() { runChild(); }, options);
writeFull(sendPid.writeSide.get(), fmt("%d\n", child));
_exit(0);
@@ -951,7 +966,7 @@ void LocalDerivationGoal::startBuilder()
auto ss = tokenizeString<std::vector<std::string>>(readLine(sendPid.readSide.get()));
assert(ss.size() == 1);
pid = Pid{string2Int<pid_t>(ss[0]).value()};
pid = string2Int<pid_t>(ss[0]).value();
if (usingUserNamespace) {
/* Set the UID/GID mapping of the builder's user namespace
@@ -961,13 +976,13 @@ void LocalDerivationGoal::startBuilder()
uid_t hostGid = buildUser ? buildUser->getGID() : getgid();
uid_t nrIds = buildUser ? buildUser->getUIDCount() : 1;
writeFile("/proc/" + std::to_string(pid.get()) + "/uid_map",
writeFile("/proc/" + std::to_string(pid) + "/uid_map",
fmt("%d %d %d", sandboxUid(), hostUid, nrIds));
if (!buildUser || buildUser->getUIDCount() == 1)
writeFile("/proc/" + std::to_string(pid.get()) + "/setgroups", "deny");
writeFile("/proc/" + std::to_string(pid) + "/setgroups", "deny");
writeFile("/proc/" + std::to_string(pid.get()) + "/gid_map",
writeFile("/proc/" + std::to_string(pid) + "/gid_map",
fmt("%d %d %d", sandboxGid(), hostGid, nrIds));
} else {
debug("note: not using a user namespace");
@@ -990,19 +1005,19 @@ void LocalDerivationGoal::startBuilder()
/* Save the mount- and user namespace of the child. We have to do this
*before* the child does a chroot. */
sandboxMountNamespace = AutoCloseFD{open(fmt("/proc/%d/ns/mnt", pid.get()).c_str(), O_RDONLY)};
sandboxMountNamespace = AutoCloseFD{open(fmt("/proc/%d/ns/mnt", (pid_t) pid).c_str(), O_RDONLY)};
if (sandboxMountNamespace.get() == -1)
throw SysError("getting sandbox mount namespace");
if (usingUserNamespace) {
sandboxUserNamespace = AutoCloseFD{open(fmt("/proc/%d/ns/user", pid.get()).c_str(), O_RDONLY)};
sandboxUserNamespace = AutoCloseFD{open(fmt("/proc/%d/ns/user", (pid_t) pid).c_str(), O_RDONLY)};
if (sandboxUserNamespace.get() == -1)
throw SysError("getting sandbox user namespace");
}
/* Move the child into its own cgroup. */
if (cgroup)
writeFile(*cgroup + "/cgroup.procs", fmt("%d", pid.get()));
writeFile(*cgroup + "/cgroup.procs", fmt("%d", (pid_t) pid));
/* Signal the builder that we've updated its user namespace. */
writeFull(userNamespaceSync.writeSide.get(), "1");
@@ -1324,11 +1339,11 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual In
return path;
}
WireFormatGenerator narFromPath(const StorePath & path) override
void narFromPath(const StorePath & path, Sink & sink) override
{
if (!goal.isAllowed(path))
throw InvalidPath("cannot dump unknown path '%s' in recursive Nix", printStorePath(path));
return LocalFSStore::narFromPath(path);
LocalFSStore::narFromPath(path, sink);
}
void ensurePath(const StorePath & path) override
@@ -1570,7 +1585,7 @@ void LocalDerivationGoal::addDependency(const StorePath & path)
entering its mount namespace, which is not possible
in multithreaded programs. So we do this in a
child process.*/
Pid child = startProcess([&]() {
Pid child(startProcess([&]() {
if (usingUserNamespace && (setns(sandboxUserNamespace.get(), 0) == -1))
throw SysError("entering sandbox user namespace");
@@ -1581,7 +1596,7 @@ void LocalDerivationGoal::addDependency(const StorePath & path)
doBind(source, target);
_exit(0);
});
}));
int status = child.wait();
if (status != 0)
@@ -2107,8 +2122,8 @@ void LocalDerivationGoal::runChild()
bool allowLocalNetworking = parsedDrv->getBoolAttr("__darwinAllowLocalNetworking");
/* The tmpDir in scope points at the temporary build directory for our derivation. Some packages try different mechanisms
to find temporary directories, so we want to open up a broader place for them to put their files, if needed. */
Path globalTmpDir = canonPath(defaultTempDir(), true);
to find temporary directories, so we want to open up a broader place for them to dump their files, if needed. */
Path globalTmpDir = canonPath(getEnvNonEmpty("TMPDIR").value_or("/tmp"), true);
/* They don't like trailing slashes on subpath directives */
if (globalTmpDir.back() == '/') globalTmpDir.pop_back();
@@ -2162,8 +2177,31 @@ void LocalDerivationGoal::runChild()
}
}
execBuilder(drv->builder, args, envStrs);
// execBuilder should not return
#if __APPLE__
posix_spawnattr_t attrp;
if (posix_spawnattr_init(&attrp))
throw SysError("failed to initialize builder");
if (posix_spawnattr_setflags(&attrp, POSIX_SPAWN_SETEXEC))
throw SysError("failed to initialize builder");
if (drv->platform == "aarch64-darwin") {
// Unset kern.curproc_arch_affinity so we can escape Rosetta
int affinity = 0;
sysctlbyname("kern.curproc_arch_affinity", NULL, NULL, &affinity, sizeof(affinity));
cpu_type_t cpu = CPU_TYPE_ARM64;
posix_spawnattr_setbinpref_np(&attrp, 1, &cpu, NULL);
} else if (drv->platform == "x86_64-darwin") {
cpu_type_t cpu = CPU_TYPE_X86_64;
posix_spawnattr_setbinpref_np(&attrp, 1, &cpu, NULL);
}
posix_spawn(NULL, drv->builder.c_str(), NULL, &attrp, stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data());
#else
execve(drv->builder.c_str(), stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data());
#endif
throw SysError("executing '%1%'", drv->builder);
@@ -2179,11 +2217,6 @@ void LocalDerivationGoal::runChild()
}
}
void LocalDerivationGoal::execBuilder(std::string builder, Strings args, Strings envStrs)
{
execve(builder.c_str(), stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data());
}
SingleDrvOutputs LocalDerivationGoal::registerOutputs()
{
@@ -2389,10 +2422,14 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
if (!rewrites.empty()) {
debug("rewriting hashes in '%1%'; cross fingers", actualPath);
GeneratorSource dump{dumpPath(actualPath)};
RewritingSource rewritten(rewrites, dump);
/* FIXME: Is this actually streaming? */
auto source = sinkToSource([&](Sink & nextSink) {
RewritingSink rsink(rewrites, nextSink);
dumpPath(actualPath, rsink);
rsink.flush();
});
Path tmpPath = actualPath + ".tmp";
restorePath(tmpPath, rewritten);
restorePath(tmpPath, *source);
deletePath(actualPath);
movePath(tmpPath, actualPath);
@@ -2446,21 +2483,23 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
rewriteOutput(outputRewrites);
/* FIXME optimize and deduplicate with addToStore */
std::string oldHashPart { scratchPath->hashPart() };
auto input = std::visit(overloaded {
[&](const TextIngestionMethod &) -> GeneratorSource {
return GeneratorSource(readFileSource(actualPath));
HashModuloSink caSink { outputHash.hashType, oldHashPart };
std::visit(overloaded {
[&](const TextIngestionMethod &) {
readFile(actualPath, caSink);
},
[&](const FileIngestionMethod & m2) -> GeneratorSource {
[&](const FileIngestionMethod & m2) {
switch (m2) {
case FileIngestionMethod::Recursive:
return GeneratorSource(dumpPath(actualPath));
dumpPath(actualPath, caSink);
break;
case FileIngestionMethod::Flat:
return GeneratorSource(readFileSource(actualPath));
readFile(actualPath, caSink);
break;
}
assert(false);
},
}, outputHash.method.raw);
auto got = computeHashModulo(outputHash.hashType, oldHashPart, input).first;
auto got = caSink.finish().first;
auto optCA = ContentAddressWithReferences::fromPartsOpt(
outputHash.method,
+2 -33
View File
@@ -178,28 +178,7 @@ struct LocalDerivationGoal : public DerivationGoal
friend struct RestrictedStore;
/**
* Create a LocalDerivationGoal without an on-disk .drv file,
* possibly a platform-specific subclass
*/
static std::shared_ptr<LocalDerivationGoal> makeLocalDerivationGoal(
const StorePath & drvPath,
const OutputsSpec & wantedOutputs,
Worker & worker,
BuildMode buildMode
);
/**
* Create a LocalDerivationGoal for an on-disk .drv file,
* possibly a platform-specific subclass
*/
static std::shared_ptr<LocalDerivationGoal> makeLocalDerivationGoal(
const StorePath & drvPath,
const BasicDerivation & drv,
const OutputsSpec & wantedOutputs,
Worker & worker,
BuildMode buildMode
);
using DerivationGoal::DerivationGoal;
virtual ~LocalDerivationGoal() noexcept(false) override;
@@ -303,7 +282,7 @@ struct LocalDerivationGoal : public DerivationGoal
* Kill any processes running under the build user UID or in the
* cgroup of the build.
*/
virtual void killSandbox(bool getStats);
void killSandbox(bool getStats);
/**
* Create alternative path calculated from but distinct from the
@@ -320,16 +299,6 @@ struct LocalDerivationGoal : public DerivationGoal
* rewrites caught everything
*/
StorePath makeFallbackPath(OutputNameView outputName);
protected:
using DerivationGoal::DerivationGoal;
/**
* Execute the builder, replacing the current process.
* Generally this means an `execve` call.
*/
virtual void execBuilder(std::string builder, Strings args, Strings envStrs);
};
}
+2 -10
View File
@@ -217,7 +217,6 @@ void PathSubstitutionGoal::tryToRun()
promise = std::promise<void>();
thr = std::thread([this]() {
auto & fetchPath = subPath ? *subPath : storePath;
try {
ReceiveInterrupts receiveInterrupts;
@@ -227,17 +226,10 @@ void PathSubstitutionGoal::tryToRun()
Activity act(*logger, actSubstitute, Logger::Fields{worker.store.printStorePath(storePath), sub->getUri()});
PushActivity pact(act.id);
copyStorePath(
*sub, worker.store, fetchPath, repair, sub->isTrusted ? NoCheckSigs : CheckSigs
);
copyStorePath(*sub, worker.store,
subPath ? *subPath : storePath, repair, sub->isTrusted ? NoCheckSigs : CheckSigs);
promise.set_value();
} catch (const EndOfFile &) {
promise.set_exception(std::make_exception_ptr(EndOfFile(
"NAR for '%s' fetched from '%s' is incomplete",
sub->printStorePath(fetchPath),
sub->getUri()
)));
} catch (...) {
promise.set_exception(std::current_exception());
}
+4 -4
View File
@@ -65,8 +65,8 @@ std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(const StorePath & drv
{
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
return !dynamic_cast<LocalStore *>(&store)
? std::make_shared<DerivationGoal>(drvPath, wantedOutputs, *this, buildMode)
: LocalDerivationGoal::makeLocalDerivationGoal(drvPath, wantedOutputs, *this, buildMode);
? std::make_shared</* */DerivationGoal>(drvPath, wantedOutputs, *this, buildMode)
: std::make_shared<LocalDerivationGoal>(drvPath, wantedOutputs, *this, buildMode);
});
}
@@ -76,8 +76,8 @@ std::shared_ptr<DerivationGoal> Worker::makeBasicDerivationGoal(const StorePath
{
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
return !dynamic_cast<LocalStore *>(&store)
? std::make_shared<DerivationGoal>(drvPath, drv, wantedOutputs, *this, buildMode)
: LocalDerivationGoal::makeLocalDerivationGoal(drvPath, drv, wantedOutputs, *this, buildMode);
? std::make_shared</* */DerivationGoal>(drvPath, drv, wantedOutputs, *this, buildMode)
: std::make_shared<LocalDerivationGoal>(drvPath, drv, wantedOutputs, *this, buildMode);
});
}
+13 -9
View File
@@ -32,19 +32,23 @@ void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData)
auto fetch = [&](const std::string & url) {
/* No need to do TLS verification, because we check the hash of
the result anyway. */
FileTransferRequest request(url);
request.verifyTLS = false;
auto source = sinkToSource([&](Sink & sink) {
auto raw = fileTransfer->download(std::move(request));
auto decompressor = makeDecompressionSource(
unpack && mainUrl.ends_with(".xz") ? "xz" : "none", *raw);
/* No need to do TLS verification, because we check the hash of
the result anyway. */
FileTransferRequest request(url);
request.verifyTLS = false;
auto decompressor = makeDecompressionSink(
unpack && mainUrl.ends_with(".xz") ? "xz" : "none", sink);
fileTransfer->download(std::move(request), *decompressor);
decompressor->finish();
});
if (unpack)
restorePath(storePath, *decompressor);
restorePath(storePath, *source);
else
writeFile(storePath, *decompressor);
writeFile(storePath, *source);
auto executable = drv.env.find("executable");
if (executable != drv.env.end() && executable->second == "1") {
+2 -2
View File
@@ -20,9 +20,9 @@ namespace nix {
{ \
return LengthPrefixedProtoHelper<CommonProto, T >::read(store, conn); \
} \
TEMPLATE [[nodiscard]] WireFormatGenerator CommonProto::Serialise< T >::write(const Store & store, CommonProto::WriteConn conn, const T & t) \
TEMPLATE void CommonProto::Serialise< T >::write(const Store & store, CommonProto::WriteConn conn, const T & t) \
{ \
return LengthPrefixedProtoHelper<CommonProto, T >::write(store, conn, t); \
LengthPrefixedProtoHelper<CommonProto, T >::write(store, conn, t); \
}
COMMON_USE_LENGTH_PREFIX_SERIALISER(template<typename T>, std::vector<T>)
+14 -18
View File
@@ -16,9 +16,9 @@ std::string CommonProto::Serialise<std::string>::read(const Store & store, Commo
return readString(conn.from);
}
WireFormatGenerator CommonProto::Serialise<std::string>::write(const Store & store, CommonProto::WriteConn conn, const std::string & str)
void CommonProto::Serialise<std::string>::write(const Store & store, CommonProto::WriteConn conn, const std::string & str)
{
co_yield str;
conn.to << str;
}
@@ -27,9 +27,9 @@ StorePath CommonProto::Serialise<StorePath>::read(const Store & store, CommonPro
return store.parseStorePath(readString(conn.from));
}
WireFormatGenerator CommonProto::Serialise<StorePath>::write(const Store & store, CommonProto::WriteConn conn, const StorePath & storePath)
void CommonProto::Serialise<StorePath>::write(const Store & store, CommonProto::WriteConn conn, const StorePath & storePath)
{
co_yield store.printStorePath(storePath);
conn.to << store.printStorePath(storePath);
}
@@ -38,9 +38,9 @@ ContentAddress CommonProto::Serialise<ContentAddress>::read(const Store & store,
return ContentAddress::parse(readString(conn.from));
}
WireFormatGenerator CommonProto::Serialise<ContentAddress>::write(const Store & store, CommonProto::WriteConn conn, const ContentAddress & ca)
void CommonProto::Serialise<ContentAddress>::write(const Store & store, CommonProto::WriteConn conn, const ContentAddress & ca)
{
co_yield renderContentAddress(ca);
conn.to << renderContentAddress(ca);
}
@@ -53,9 +53,9 @@ Realisation CommonProto::Serialise<Realisation>::read(const Store & store, Commo
);
}
WireFormatGenerator CommonProto::Serialise<Realisation>::write(const Store & store, CommonProto::WriteConn conn, const Realisation & realisation)
void CommonProto::Serialise<Realisation>::write(const Store & store, CommonProto::WriteConn conn, const Realisation & realisation)
{
co_yield realisation.toJSON().dump();
conn.to << realisation.toJSON().dump();
}
@@ -64,9 +64,9 @@ DrvOutput CommonProto::Serialise<DrvOutput>::read(const Store & store, CommonPro
return DrvOutput::parse(readString(conn.from));
}
WireFormatGenerator CommonProto::Serialise<DrvOutput>::write(const Store & store, CommonProto::WriteConn conn, const DrvOutput & drvOutput)
void CommonProto::Serialise<DrvOutput>::write(const Store & store, CommonProto::WriteConn conn, const DrvOutput & drvOutput)
{
co_yield drvOutput.to_string();
conn.to << drvOutput.to_string();
}
@@ -76,11 +76,9 @@ std::optional<StorePath> CommonProto::Serialise<std::optional<StorePath>>::read(
return s == "" ? std::optional<StorePath> {} : store.parseStorePath(s);
}
WireFormatGenerator CommonProto::Serialise<std::optional<StorePath>>::write(const Store & store, CommonProto::WriteConn conn, const std::optional<StorePath> & storePathOpt)
void CommonProto::Serialise<std::optional<StorePath>>::write(const Store & store, CommonProto::WriteConn conn, const std::optional<StorePath> & storePathOpt)
{
return [](std::string s) -> WireFormatGenerator {
co_yield s;
}(storePathOpt ? store.printStorePath(*storePathOpt) : "");
conn.to << (storePathOpt ? store.printStorePath(*storePathOpt) : "");
}
@@ -89,11 +87,9 @@ std::optional<ContentAddress> CommonProto::Serialise<std::optional<ContentAddres
return ContentAddress::parseOpt(readString(conn.from));
}
WireFormatGenerator CommonProto::Serialise<std::optional<ContentAddress>>::write(const Store & store, CommonProto::WriteConn conn, const std::optional<ContentAddress> & caOpt)
void CommonProto::Serialise<std::optional<ContentAddress>>::write(const Store & store, CommonProto::WriteConn conn, const std::optional<ContentAddress> & caOpt)
{
return [](std::string s) -> WireFormatGenerator {
co_yield s;
}(caOpt ? renderContentAddress(*caOpt) : "");
conn.to << (caOpt ? renderContentAddress(*caOpt) : "");
}
}
+3 -4
View File
@@ -48,10 +48,9 @@ struct CommonProto
* infer the type instead of having to write it down explicitly.
*/
template<typename T>
[[nodiscard]]
static WireFormatGenerator write(const Store & store, WriteConn conn, const T & t)
static void write(const Store & store, WriteConn conn, const T & t)
{
return CommonProto::Serialise<T>::write(store, conn, t);
CommonProto::Serialise<T>::write(store, conn, t);
}
};
@@ -59,7 +58,7 @@ struct CommonProto
struct CommonProto::Serialise< T > \
{ \
static T read(const Store & store, CommonProto::ReadConn conn); \
[[nodiscard]] static WireFormatGenerator write(const Store & store, CommonProto::WriteConn conn, const T & str); \
static void write(const Store & store, CommonProto::WriteConn conn, const T & str); \
}
template<>
+143 -130
View File
@@ -47,15 +47,10 @@ struct TunnelLogger : public Logger
Sync<State> state_;
/**
* Worker protocol version of the other side. May be newer than this daemon.
*/
const WorkerProto::Version clientVersion;
WorkerProto::Version clientVersion;
TunnelLogger(FdSink & to, WorkerProto::Version clientVersion)
: to(to), clientVersion(clientVersion) {
assert(clientVersion >= MIN_SUPPORTED_WORKER_PROTO_VERSION);
}
: to(to), clientVersion(clientVersion) { }
void enqueueMsg(const std::string & s)
{
@@ -134,6 +129,12 @@ struct TunnelLogger : public Logger
void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
const std::string & s, const Fields & fields, ActivityId parent) override
{
if (GET_PROTOCOL_MINOR(clientVersion) < 20) {
if (!s.empty())
log(lvl, s + "...");
return;
}
StringSink buf;
buf << STDERR_START_ACTIVITY << act << lvl << type << s << fields << parent;
enqueueMsg(buf.s);
@@ -141,6 +142,7 @@ struct TunnelLogger : public Logger
void stopActivity(ActivityId act) override
{
if (GET_PROTOCOL_MINOR(clientVersion) < 20) return;
StringSink buf;
buf << STDERR_STOP_ACTIVITY << act;
enqueueMsg(buf.s);
@@ -148,6 +150,7 @@ struct TunnelLogger : public Logger
void result(ActivityId act, ResultType type, const Fields & fields) override
{
if (GET_PROTOCOL_MINOR(clientVersion) < 20) return;
StringSink buf;
buf << STDERR_RESULT << act << type << fields;
enqueueMsg(buf.s);
@@ -160,7 +163,8 @@ struct TunnelSink : Sink
TunnelSink(Sink & to) : to(to) { }
void operator () (std::string_view data)
{
to << STDERR_WRITE << data;
to << STDERR_WRITE;
writeString(data, to);
}
};
@@ -263,8 +267,14 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
TrustedFlag trusted, RecursiveFlag recursive, WorkerProto::Version clientVersion,
Source & from, BufferedSink & to, WorkerProto::Op op)
{
WorkerProto::ReadConn rconn{from, clientVersion};
WorkerProto::WriteConn wconn{to, clientVersion};
WorkerProto::ReadConn rconn {
.from = from,
.version = clientVersion,
};
WorkerProto::WriteConn wconn {
.to = to,
.version = clientVersion,
};
switch (op) {
@@ -291,7 +301,18 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
}
auto res = store->queryValidPaths(paths, substitute);
logger->stopWork();
wconn.to << WorkerProto::write(*store, wconn, res);
WorkerProto::write(*store, wconn, res);
break;
}
case WorkerProto::Op::HasSubstitutes: {
auto path = store->parseStorePath(readString(from));
logger->startWork();
StorePathSet paths; // FIXME
paths.insert(path);
auto res = store->querySubstitutablePaths(paths);
logger->stopWork();
to << (res.count(path) != 0);
break;
}
@@ -300,78 +321,40 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
logger->startWork();
auto res = store->querySubstitutablePaths(paths);
logger->stopWork();
wconn.to << WorkerProto::write(*store, wconn, res);
break;
}
case WorkerProto::Op::HasSubstitutes: {
throw UnimplementedError("HasSubstitutes is not supported in Lix. This is not used if the declared server protocol is > 1.12 (Nix 1.0, 2012)");
WorkerProto::write(*store, wconn, res);
break;
}
case WorkerProto::Op::QueryPathHash: {
throw UnimplementedError("QueryPathHash is not supported in Lix, client usages were removed in 2016 in e0204f8d462041387651af388074491fd0bf36d6");
break;
}
case WorkerProto::Op::QueryReferences: {
throw UnimplementedError("QueryReferences is not supported in Lix, client usages were removed in 2016 in e0204f8d462041387651af388074491fd0bf36d6");
break;
}
case WorkerProto::Op::QueryDeriver: {
throw UnimplementedError("QueryDeriver is not supported in Lix, client usages were removed in 2016 in e0204f8d462041387651af388074491fd0bf36d6");
break;
}
case WorkerProto::Op::ExportPath: {
throw UnimplementedError("ExportPath is not supported in Lix, client usage were removed in 2017 in 27dc76c1a5dbe654465245ff5f6bc22e2c8902da");
break;
}
case WorkerProto::Op::ImportPaths: {
throw UnimplementedError("ImportPaths is not supported in Lix. This is not used if the declared server protocol is >= 1.18 (Nix 2.0, 2016)");
auto path = store->parseStorePath(readString(from));
logger->startWork();
auto hash = store->queryPathInfo(path)->narHash;
logger->stopWork();
to << hash.to_string(Base16, false);
break;
}
case WorkerProto::Op::QueryReferences:
case WorkerProto::Op::QueryReferrers:
case WorkerProto::Op::QueryValidDerivers:
case WorkerProto::Op::QueryDerivationOutputs: {
auto path = store->parseStorePath(readString(from));
logger->startWork();
StorePathSet paths;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
switch (op) {
case WorkerProto::Op::QueryReferrers: {
store->queryReferrers(path, paths);
break;
}
case WorkerProto::Op::QueryValidDerivers: {
paths = store->queryValidDerivers(path);
break;
}
case WorkerProto::Op::QueryDerivationOutputs: {
// Only sent if server presents proto version <= 1.21
REMOVE_AFTER_DROPPING_PROTO_MINOR(21);
paths = store->queryDerivationOutputs(path);
break;
}
default:
abort();
break;
}
#pragma GCC diagnostic pop
if (op == WorkerProto::Op::QueryReferences)
for (auto & i : store->queryPathInfo(path)->references)
paths.insert(i);
else if (op == WorkerProto::Op::QueryReferrers)
store->queryReferrers(path, paths);
else if (op == WorkerProto::Op::QueryValidDerivers)
paths = store->queryValidDerivers(path);
else paths = store->queryDerivationOutputs(path);
logger->stopWork();
wconn.to << WorkerProto::write(*store, wconn, paths);
WorkerProto::write(*store, wconn, paths);
break;
}
case WorkerProto::Op::QueryDerivationOutputNames: {
// Unused in CppNix >= 2.4 (removed in 045b07200c77bf1fe19c0a986aafb531e7e1ba54)
REMOVE_AFTER_DROPPING_PROTO_MINOR(31);
auto path = store->parseStorePath(readString(from));
logger->startWork();
auto names = store->readDerivation(path).outputNames();
@@ -385,7 +368,16 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
logger->startWork();
auto outputs = store->queryPartialDerivationOutputMap(path);
logger->stopWork();
wconn.to << WorkerProto::write(*store, wconn, outputs);
WorkerProto::write(*store, wconn, outputs);
break;
}
case WorkerProto::Op::QueryDeriver: {
auto path = store->parseStorePath(readString(from));
logger->startWork();
auto info = store->queryPathInfo(path);
logger->stopWork();
to << (info->deriver ? store->printStorePath(*info->deriver) : "");
break;
}
@@ -432,7 +424,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
}();
logger->stopWork();
wconn.to << WorkerProto::Serialise<ValidPathInfo>::write(*store, wconn, *pathInfo);
WorkerProto::Serialise<ValidPathInfo>::write(*store, wconn, *pathInfo);
} else {
HashType hashAlgo;
std::string baseName;
@@ -453,7 +445,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
hashAlgo = parseHashType(hashAlgoRaw);
}
GeneratorSource dumpSource{[&]() -> WireFormatGenerator {
auto dumpSource = sinkToSource([&](Sink & saved) {
if (method == FileIngestionMethod::Recursive) {
/* We parse the NAR dump through into `saved` unmodified,
so why all this extra work? We still parse the NAR so
@@ -463,33 +455,18 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
command. (We don't trust `addToStoreFromDump` to not
eagerly consume the entire stream it's given, past the
length of the Nar. */
co_yield copyNAR(from);
copyNAR(from, saved);
} else {
/* Incrementally parse the NAR file, stripping the
metadata, and streaming the sole file we expect into
`saved`. */
auto parser = nar::parse(from);
nar::File * file = nullptr;
while (auto entry = parser.next()) {
file = std::visit(
overloaded{
[](nar::MetadataString) -> nar::File * { return nullptr; },
[](nar::MetadataRaw) -> nar::File * { return nullptr; },
[](nar::File & f) -> nar::File * { return &f; },
[](auto &) -> nar::File * { throw Error("regular file expected"); },
},
*entry
);
if (file) {
break;
}
}
assert(file); // should never fail unless the nar is empty, which would be invalid
co_yield std::move(file->contents);
RetrieveRegularNARSink savedRegular { saved };
parseDump(savedRegular, from);
if (!savedRegular.regular) throw Error("regular file expected");
}
}()};
});
logger->startWork();
auto path = store->addToStoreFromDump(dumpSource, baseName, method, hashAlgo);
auto path = store->addToStoreFromDump(*dumpSource, baseName, method, hashAlgo);
logger->stopWork();
to << store->printStorePath(path);
@@ -525,21 +502,47 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
break;
}
case WorkerProto::Op::ExportPath: {
auto path = store->parseStorePath(readString(from));
readInt(from); // obsolete
logger->startWork();
TunnelSink sink(to);
store->exportPath(path, sink);
logger->stopWork();
to << 1;
break;
}
case WorkerProto::Op::ImportPaths: {
logger->startWork();
TunnelSource source(from, to);
auto paths = store->importPaths(source,
trusted ? NoCheckSigs : CheckSigs);
logger->stopWork();
Strings paths2;
for (auto & i : paths) paths2.push_back(store->printStorePath(i));
to << paths2;
break;
}
case WorkerProto::Op::BuildPaths: {
auto drvs = WorkerProto::Serialise<DerivedPaths>::read(*store, rconn);
BuildMode mode = buildModeFromInteger(readInt(from));
BuildMode mode = bmNormal;
if (GET_PROTOCOL_MINOR(clientVersion) >= 15) {
mode = buildModeFromInteger(readInt(from));
/* Repairing is not atomic, so disallowed for "untrusted"
clients.
/* Repairing is not atomic, so disallowed for "untrusted"
clients.
FIXME: layer violation in this message: the daemon code (i.e.
this file) knows whether a client/connection is trusted, but it
does not how how the client was authenticated. The mechanism
need not be getting the UID of the other end of a Unix Domain
Socket.
*/
if (mode == bmRepair && !trusted)
throw Error("repairing is not allowed because you are not in 'trusted-users'");
FIXME: layer violation in this message: the daemon code (i.e.
this file) knows whether a client/connection is trusted, but it
does not how how the client was authenticated. The mechanism
need not be getting the UID of the other end of a Unix Domain
Socket.
*/
if (mode == bmRepair && !trusted)
throw Error("repairing is not allowed because you are not in 'trusted-users'");
}
logger->startWork();
store->buildPaths(drvs, mode);
logger->stopWork();
@@ -563,7 +566,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
auto results = store->buildPathsWithResults(drvs, mode);
logger->stopWork();
wconn.to << WorkerProto::write(*store, wconn, results);
WorkerProto::write(*store, wconn, results);
break;
}
@@ -641,7 +644,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
auto res = store->buildDerivation(drvPath, drv, buildMode);
logger->stopWork();
wconn.to << WorkerProto::write(*store, wconn, res);
WorkerProto::write(*store, wconn, res);
break;
}
@@ -675,10 +678,8 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
break;
}
// Obsolete since 9947f1646a26b339fff2e02b77798e9841fac7f0 (included in CppNix 2.5.0).
// Obsolete.
case WorkerProto::Op::SyncWithGC: {
// CppNix 2.5.0 is 32
REMOVE_AFTER_DROPPING_PROTO_MINOR(31);
logger->startWork();
logger->stopWork();
to << 1;
@@ -745,11 +746,13 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
clientSettings.buildCores = readInt(from);
clientSettings.useSubstitutes = readInt(from);
unsigned int n = readInt(from);
for (unsigned int i = 0; i < n; i++) {
auto name = readString(from);
auto value = readString(from);
clientSettings.overrides.emplace(name, value);
if (GET_PROTOCOL_MINOR(clientVersion) >= 12) {
unsigned int n = readInt(from);
for (unsigned int i = 0; i < n; i++) {
auto name = readString(from);
auto value = readString(from);
clientSettings.overrides.emplace(name, value);
}
}
logger->startWork();
@@ -775,7 +778,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
else {
to << 1
<< (i->second.deriver ? store->printStorePath(*i->second.deriver) : "");
wconn.to << WorkerProto::write(*store, wconn, i->second.references);
WorkerProto::write(*store, wconn, i->second.references);
to << i->second.downloadSize
<< i->second.narSize;
}
@@ -798,7 +801,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
for (auto & i : infos) {
to << store->printStorePath(i.first)
<< (i.second.deriver ? store->printStorePath(*i.second.deriver) : "");
wconn.to << WorkerProto::write(*store, wconn, i.second.references);
WorkerProto::write(*store, wconn, i.second.references);
to << i.second.downloadSize << i.second.narSize;
}
break;
@@ -808,7 +811,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
logger->startWork();
auto paths = store->queryAllValidPaths();
logger->stopWork();
wconn.to << WorkerProto::write(*store, wconn, paths);
WorkerProto::write(*store, wconn, paths);
break;
}
@@ -819,14 +822,15 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
try {
info = store->queryPathInfo(path);
} catch (InvalidPath &) {
// The path being invalid isn't fatal here since it will just be
// sent as not present.
if (GET_PROTOCOL_MINOR(clientVersion) < 17) throw;
}
logger->stopWork();
if (info) {
to << 1;
wconn.to << WorkerProto::write(*store, wconn, static_cast<const UnkeyedValidPathInfo &>(*info));
if (GET_PROTOCOL_MINOR(clientVersion) >= 17)
to << 1;
WorkerProto::write(*store, wconn, static_cast<const UnkeyedValidPathInfo &>(*info));
} else {
assert(GET_PROTOCOL_MINOR(clientVersion) >= 17);
to << 0;
}
break;
@@ -865,7 +869,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
auto path = store->parseStorePath(readString(from));
logger->startWork();
logger->stopWork();
to << dumpPath(store->toRealPath(path));
dumpPath(store->toRealPath(path), to);
break;
}
@@ -899,7 +903,13 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
else {
std::unique_ptr<Source> source;
source = std::make_unique<TunnelSource>(from, to);
StringSink saved;
if (GET_PROTOCOL_MINOR(clientVersion) >= 21)
source = std::make_unique<TunnelSource>(from, to);
else {
copyNAR(from, saved);
source = std::make_unique<StringSource>(saved.s);
}
logger->startWork();
@@ -920,9 +930,9 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
uint64_t downloadSize, narSize;
store->queryMissing(targets, willBuild, willSubstitute, unknown, downloadSize, narSize);
logger->stopWork();
wconn.to << WorkerProto::write(*store, wconn, willBuild);
wconn.to << WorkerProto::write(*store, wconn, willSubstitute);
wconn.to << WorkerProto::write(*store, wconn, unknown);
WorkerProto::write(*store, wconn, willBuild);
WorkerProto::write(*store, wconn, willSubstitute);
WorkerProto::write(*store, wconn, unknown);
to << downloadSize << narSize;
break;
}
@@ -950,11 +960,11 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
if (GET_PROTOCOL_MINOR(clientVersion) < 31) {
std::set<StorePath> outPaths;
if (info) outPaths.insert(info->outPath);
wconn.to << WorkerProto::write(*store, wconn, outPaths);
WorkerProto::write(*store, wconn, outPaths);
} else {
std::set<Realisation> realisations;
if (info) realisations.insert(*info);
wconn.to << WorkerProto::write(*store, wconn, realisations);
WorkerProto::write(*store, wconn, realisations);
}
break;
}
@@ -1001,7 +1011,7 @@ void processConnection(
to.flush();
WorkerProto::Version clientVersion = readInt(from);
if (clientVersion < MIN_SUPPORTED_WORKER_PROTO_VERSION)
if (clientVersion < 0x10a)
throw Error("the Nix client version is too old");
auto tunnelLogger = new TunnelLogger(to, clientVersion);
@@ -1017,13 +1027,13 @@ void processConnection(
printMsgUsing(prevLogger, lvlDebug, "%d operations", opCount);
});
// FIXME: what is *supposed* to be in this even?
if (readInt(from)) {
if (GET_PROTOCOL_MINOR(clientVersion) >= 14 && readInt(from)) {
// Obsolete CPU affinity.
readInt(from);
}
readInt(from); // obsolete reserveSpace
if (GET_PROTOCOL_MINOR(clientVersion) >= 11)
readInt(from); // obsolete reserveSpace
if (GET_PROTOCOL_MINOR(clientVersion) >= 33)
to << nixVersion;
@@ -1034,8 +1044,11 @@ void processConnection(
auto temp = trusted
? store->isTrustedClient()
: std::optional { NotTrusted };
WorkerProto::WriteConn wconn {to, clientVersion};
wconn.to << WorkerProto::write(*store, wconn, temp);
WorkerProto::WriteConn wconn {
.to = to,
.version = clientVersion,
};
WorkerProto::write(*store, wconn, temp);
}
/* Send startup error messages to the client. */
+1 -1
View File
@@ -994,7 +994,7 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr
},
}, i.second.raw);
}
out << CommonProto::write(store,
CommonProto::write(store,
CommonProto::WriteConn { .to = out },
drv.inputSrcs);
out << drv.platform << drv.builder << drv.args;
+1 -1
View File
@@ -63,7 +63,7 @@ struct DummyStore : public virtual DummyStoreConfig, public virtual Store
RepairFlag repair) override
{ unsupported("addTextToStore"); }
WireFormatGenerator narFromPath(const StorePath & path) override
void narFromPath(const StorePath & path, Sink & sink) override
{ unsupported("narFromPath"); }
std::shared_ptr<const Realisation> queryRealisationUncached(const DrvOutput &) override
+7 -6
View File
@@ -33,7 +33,7 @@ void Store::exportPath(const StorePath & path, Sink & sink)
HashSink hashSink(htSHA256);
TeeSink teeSink(sink, hashSink);
teeSink << narFromPath(path);
narFromPath(path, teeSink);
/* Refuse to export paths that have changed. This prevents
filesystem corruption from spreading to other machines.
@@ -45,10 +45,11 @@ void Store::exportPath(const StorePath & path, Sink & sink)
teeSink
<< exportMagic
<< printStorePath(path)
<< CommonProto::write(*this,
CommonProto::WriteConn { .to = teeSink },
info->references)
<< printStorePath(path);
CommonProto::write(*this,
CommonProto::WriteConn { .to = teeSink },
info->references);
teeSink
<< (info->deriver ? printStorePath(*info->deriver) : "")
<< 0;
}
@@ -63,7 +64,7 @@ StorePaths Store::importPaths(Source & source, CheckSigsFlag checkSigs)
/* Extract the NAR from the source. */
StringSink saved;
saved << copyNAR(source);
copyNAR(source, saved);
uint32_t magic = readInt(source);
if (magic != exportMagic)
+60 -88
View File
@@ -686,8 +686,16 @@ struct curlFileTransfer : public FileTransfer
->callback.get_future();
}
box_ptr<Source> download(FileTransferRequest && request) override
void download(FileTransferRequest && request, Sink & sink) override
{
/* Note: we can't call 'sink' via request.dataCallback, because
that would cause the sink to execute on the fileTransfer
thread. If 'sink' is a coroutine, this will fail. Also, if the
sink is expensive (e.g. one that does decompression and writing
to the Nix store), it would stall the download thread too much.
Therefore we use a buffer to communicate data between the
download thread and the calling thread. */
struct State {
bool done = false, failed = false;
std::exception_ptr exc;
@@ -697,6 +705,13 @@ struct curlFileTransfer : public FileTransfer
auto _state = std::make_shared<Sync<State>>();
/* In case of an exception, wake up the download thread. */
Finally finally([&]() {
auto state(_state->lock());
state->failed |= std::uncaught_exceptions() != 0;
state->request.notify_one();
});
enqueueFileTransfer(
request,
[_state](std::exception_ptr ex) {
@@ -735,99 +750,50 @@ struct curlFileTransfer : public FileTransfer
}
);
struct InnerSource : Source
{
const std::shared_ptr<Sync<State>> _state;
std::unique_ptr<FinishSink> decompressor;
while (true) {
checkInterrupt();
std::string chunk;
std::string_view buffered;
explicit InnerSource(const std::shared_ptr<Sync<State>> & state) : _state(state) {}
~InnerSource()
/* Grab data if available, otherwise wait for the download
thread to wake us up. */
{
// wake up the download thread if it's still going and have it abort
auto state(_state->lock());
state->failed |= !state->done;
if (state->data.empty()) {
if (state->done) {
if (state->exc) std::rethrow_exception(state->exc);
if (decompressor) {
decompressor->finish();
}
return;
}
state.wait(state->avail);
if (state->data.empty()) continue;
}
chunk = std::move(state->data);
/* Reset state->data after the move, since we check data.empty() */
state->data = "";
if (!decompressor) {
decompressor = makeDecompressionSink(state->encoding, sink);
}
state->request.notify_one();
}
void awaitData(Sync<State>::Lock & state)
{
/* Grab data if available, otherwise wait for the download
thread to wake us up. */
while (buffered.empty()) {
if (state->data.empty()) {
if (state->done) {
if (state->exc) {
std::rethrow_exception(state->exc);
}
return;
}
state.wait(state->avail);
}
chunk = std::move(state->data);
buffered = chunk;
state->request.notify_one();
}
}
size_t read(char * data, size_t len) override
{
auto readPartial = [this](char * data, size_t len) {
const auto available = std::min(len, buffered.size());
memcpy(data, buffered.data(), available);
buffered.remove_prefix(available);
return available;
};
size_t total = readPartial(data, len);
while (total < len) {
{
auto state(_state->lock());
awaitData(state);
}
const auto current = readPartial(data + total, len - total);
total += current;
if (total == 0 || current == 0) {
break;
}
}
if (total == 0) {
throw EndOfFile("download finished");
}
return total;
}
};
struct DownloadSource : Source
{
InnerSource inner;
std::unique_ptr<Source> decompressor;
explicit DownloadSource(const std::shared_ptr<Sync<State>> & state) : inner(state) {}
size_t read(char * data, size_t len) override
{
checkInterrupt();
if (!decompressor) {
auto state(inner._state->lock());
inner.awaitData(state);
decompressor = makeDecompressionSource(state->encoding, inner);
}
return decompressor->read(data, len);
}
};
auto source = make_box_ptr<DownloadSource>(_state);
auto lock(_state->lock());
source->inner.awaitData(lock);
return source;
/* Flush the data to the sink and wake up the download thread
if it's blocked on a full buffer. We don't hold the state
lock while doing this to prevent blocking the download
thread if sink() takes a long time. */
(*decompressor)(chunk);
}
}
};
@@ -851,11 +817,17 @@ ref<FileTransfer> makeFileTransfer()
return makeCurlFileTransfer();
}
FileTransferResult FileTransfer::transfer(const FileTransferRequest & request)
FileTransferResult FileTransfer::download(const FileTransferRequest & request)
{
return enqueueFileTransfer(request).get();
}
FileTransferResult FileTransfer::upload(const FileTransferRequest & request)
{
/* Note: this method is the same as download, but helps in readability */
return enqueueFileTransfer(request).get();
}
template<typename... Args>
FileTransferError::FileTransferError(FileTransfer::Error error, std::optional<std::string> response, const Args & ... args)
: Error(args...), error(error), response(response)
+10 -9
View File
@@ -1,7 +1,6 @@
#pragma once
///@file
#include "box_ptr.hh"
#include "logging.hh"
#include "serialise.hh"
#include "types.hh"
@@ -100,18 +99,20 @@ struct FileTransfer
virtual std::future<FileTransferResult> enqueueFileTransfer(const FileTransferRequest & request) = 0;
/**
* Synchronously transfer a file.
* Synchronously download a file.
*/
FileTransferResult transfer(const FileTransferRequest & request);
FileTransferResult download(const FileTransferRequest & request);
/**
* Download a file, returning its contents through a source. Will not return
* before the transfer has fully started, ensuring that any errors thrown by
* the setup phase (e.g. HTTP 404 or similar errors) are not postponed to be
* thrown by the returned source. The source will only throw errors detected
* during the transfer itself (decompression errors, connection drops, etc).
* Synchronously upload a file.
*/
virtual box_ptr<Source> download(FileTransferRequest && request) = 0;
FileTransferResult upload(const FileTransferRequest & request);
/**
* Download a file, writing its data to a sink. The sink will be
* invoked on the thread of the caller.
*/
virtual void download(FileTransferRequest && request, Sink & sink) = 0;
enum Error { NotFound, Forbidden, Misc, Transient, Interrupted };
};
+1 -1
View File
@@ -429,7 +429,7 @@ void initLibStore() {
/* On macOS, don't use the per-session TMPDIR (as set e.g. by
sshd). This breaks build users because they don't have access
to the TMPDIR, in particular in nix-store --serve. */
if (defaultTempDir().starts_with("/var/folders/"))
if (getEnv("TMPDIR").value_or("/tmp").starts_with("/var/folders/"))
unsetenv("TMPDIR");
#endif
+5 -25
View File
@@ -582,36 +582,16 @@ public:
Setting<std::string> sandboxShmSize{
this, "50%", "sandbox-dev-shm-size",
R"(
*Linux only*
This option determines the maximum size of the `tmpfs` filesystem
mounted on `/dev/shm` in Linux sandboxes. For the format, see the
description of the `size` option of `tmpfs` in mount(8). The default
is `50%`.
This option determines the maximum size of the `tmpfs` filesystem
mounted on `/dev/shm` in Linux sandboxes. For the format, see the
description of the `size` option of `tmpfs` in mount(8). The default
is `50%`.
)"};
Setting<Path> sandboxBuildDir{this, "/build", "sandbox-build-dir",
R"(
*Linux only*
The build directory inside the sandbox.
This directory is backed by [`build-dir`](#conf-build-dir) on the host.
)"};
"The build directory inside the sandbox."};
#endif
Setting<std::optional<Path>> buildDir{this, std::nullopt, "build-dir",
R"(
The directory on the host, in which derivations' temporary build directories are created.
If not set, Nix will use the system temporary directory indicated by the `TMPDIR` environment variable.
Note that builds are often performed by the Nix daemon, so its `TMPDIR` is used, and not that of the Nix command line interface.
This is also the location where [`--keep-failed`](@docroot@/command-ref/opt-common.md#opt-keep-failed) leaves its files.
If Nix runs without sandbox, or if the platform does not support sandboxing with bind mounts (e.g. macOS), then the [`builder`](@docroot@/language/derivations.md#attr-builder)'s environment will contain this directory, instead of the virtual location [`sandbox-build-dir`](#conf-sandbox-build-dir).
)"};
Setting<PathSet> allowedImpureHostPrefixes{this, {}, "allowed-impure-host-deps",
"Which prefixes to allow derivations to ask for access to (primarily for Darwin)."};
+5 -5
View File
@@ -115,7 +115,7 @@ protected:
try {
FileTransferRequest request(makeRequest(path));
request.head = true;
getFileTransfer()->transfer(request);
getFileTransfer()->download(request);
return true;
} catch (FileTransferError & e) {
/* S3 buckets return 403 if a file doesn't exist and the
@@ -135,7 +135,7 @@ protected:
req.data = StreamToSourceAdapter(istream).drain();
req.mimeType = mimeType;
try {
getFileTransfer()->transfer(req);
getFileTransfer()->upload(req);
} catch (FileTransferError & e) {
throw UploadToHTTP("while uploading to HTTP binary cache at '%s': %s", cacheUri, e.msg());
}
@@ -150,12 +150,12 @@ protected:
}
box_ptr<Source> getFile(const std::string & path) override
void getFile(const std::string & path, Sink & sink) override
{
checkEnabled();
auto request(makeRequest(path));
try {
return getFileTransfer()->download(std::move(request));
getFileTransfer()->download(std::move(request), sink);
} catch (FileTransferError & e) {
if (e.error == FileTransfer::NotFound || e.error == FileTransfer::Forbidden)
throw NoSuchBinaryCacheFile("file '%s' does not exist in binary cache '%s'", path, getUri());
@@ -164,7 +164,7 @@ protected:
}
}
std::optional<std::string> getFileContents(const std::string & path) override
std::optional<std::string> getFile(const std::string & path) override
{
checkEnabled();
+8 -10
View File
@@ -185,7 +185,7 @@ struct LegacySSHStore : public virtual LegacySSHStoreConfig, public virtual Stor
<< printStorePath(info.path)
<< (info.deriver ? printStorePath(*info.deriver) : "")
<< info.narHash.to_string(Base16, false);
conn->to << ServeProto::write(*this, *conn, info.references);
ServeProto::write(*this, *conn, info.references);
conn->to
<< info.registrationTime
<< info.narSize
@@ -193,7 +193,7 @@ struct LegacySSHStore : public virtual LegacySSHStoreConfig, public virtual Stor
<< info.sigs
<< renderContentAddress(info.ca);
try {
conn->to << copyNAR(source);
copyNAR(source, conn->to);
} catch (...) {
conn->good = false;
throw;
@@ -206,7 +206,7 @@ struct LegacySSHStore : public virtual LegacySSHStoreConfig, public virtual Stor
<< ServeProto::Command::ImportPaths
<< 1;
try {
conn->to << copyNAR(source);
copyNAR(source, conn->to);
} catch (...) {
conn->good = false;
throw;
@@ -214,7 +214,7 @@ struct LegacySSHStore : public virtual LegacySSHStoreConfig, public virtual Stor
conn->to
<< exportMagic
<< printStorePath(info.path);
conn->to << ServeProto::write(*this, *conn, info.references);
ServeProto::write(*this, *conn, info.references);
conn->to
<< (info.deriver ? printStorePath(*info.deriver) : "")
<< 0
@@ -227,15 +227,13 @@ struct LegacySSHStore : public virtual LegacySSHStoreConfig, public virtual Stor
throw Error("failed to add path '%s' to remote host '%s'", printStorePath(info.path), host);
}
WireFormatGenerator narFromPath(const StorePath & path) override
void narFromPath(const StorePath & path, Sink & sink) override
{
auto conn(connections->get());
conn->to << ServeProto::Command::DumpStorePath << printStorePath(path);
conn->to.flush();
return [] (auto conn) -> WireFormatGenerator {
co_yield copyNAR(conn->from);
}(std::move(conn));
copyNAR(conn->from, sink);
}
std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override
@@ -366,7 +364,7 @@ public:
conn->to
<< ServeProto::Command::QueryClosure
<< includeOutputs;
conn->to << ServeProto::write(*this, *conn, paths);
ServeProto::write(*this, *conn, paths);
conn->to.flush();
for (auto & i : ServeProto::Serialise<StorePathSet>::read(*this, *conn))
@@ -382,7 +380,7 @@ public:
<< ServeProto::Command::QueryValidPaths
<< false // lock
<< maybeSubstitute;
conn->to << ServeProto::write(*this, *conn, paths);
ServeProto::write(*this, *conn, paths);
conn->to.flush();
return ServeProto::Serialise<StorePathSet>::read(*this, *conn);
+15 -27
View File
@@ -7,7 +7,6 @@
*/
#include "types.hh"
#include "serialise.hh"
namespace nix {
@@ -46,7 +45,7 @@ struct LengthPrefixedProtoHelper;
struct LengthPrefixedProtoHelper< Inner, T > \
{ \
static T read(const Store & store, typename Inner::ReadConn conn); \
[[nodiscard]] static WireFormatGenerator write(const Store & store, typename Inner::WriteConn conn, const T & str); \
static void write(const Store & store, typename Inner::WriteConn conn, const T & str); \
private: \
template<typename U> using S = typename Inner::template Serialise<U>; \
}
@@ -79,13 +78,13 @@ LengthPrefixedProtoHelper<Inner, std::vector<T>>::read(
}
template<class Inner, typename T>
WireFormatGenerator
void
LengthPrefixedProtoHelper<Inner, std::vector<T>>::write(
const Store & store, typename Inner::WriteConn conn, const std::vector<T> & resSet)
{
co_yield resSet.size();
conn.to << resSet.size();
for (auto & key : resSet) {
co_yield S<T>::write(store, conn, key);
S<T>::write(store, conn, key);
}
}
@@ -103,13 +102,13 @@ LengthPrefixedProtoHelper<Inner, std::set<T>>::read(
}
template<class Inner, typename T>
WireFormatGenerator
void
LengthPrefixedProtoHelper<Inner, std::set<T>>::write(
const Store & store, typename Inner::WriteConn conn, const std::set<T> & resSet)
{
co_yield resSet.size();
conn.to << resSet.size();
for (auto & key : resSet) {
co_yield S<T>::write(store, conn, key);
S<T>::write(store, conn, key);
}
}
@@ -129,14 +128,14 @@ LengthPrefixedProtoHelper<Inner, std::map<K, V>>::read(
}
template<class Inner, typename K, typename V>
WireFormatGenerator
void
LengthPrefixedProtoHelper<Inner, std::map<K, V>>::write(
const Store & store, typename Inner::WriteConn conn, const std::map<K, V> & resMap)
{
co_yield resMap.size();
conn.to << resMap.size();
for (auto & i : resMap) {
co_yield S<K>::write(store, conn, i.first);
co_yield S<V>::write(store, conn, i.second);
S<K>::write(store, conn, i.first);
S<V>::write(store, conn, i.second);
}
}
@@ -151,24 +150,13 @@ LengthPrefixedProtoHelper<Inner, std::tuple<Ts...>>::read(
}
template<class Inner, typename... Ts>
WireFormatGenerator
void
LengthPrefixedProtoHelper<Inner, std::tuple<Ts...>>::write(
const Store & store, typename Inner::WriteConn conn, const std::tuple<Ts...> & res)
{
auto fullArgs = std::apply(
[&](auto &... rest) {
return std::tuple<const Store &, typename Inner::WriteConn &, const Ts &...>(
std::cref(store), conn, rest...
);
},
res
);
return std::apply(
[]<typename... Us>(auto & store, auto conn, const Us &... args) -> WireFormatGenerator {
(co_yield S<Us>::write(store, conn, args), ...);
},
fullArgs
);
std::apply([&]<typename... Us>(const Us &... args) {
(S<Us>::write(store, conn, args), ...);
}, res);
}
}
+2 -2
View File
@@ -68,10 +68,10 @@ protected:
del.cancel();
}
box_ptr<Source> getFile(const std::string & path) override
void getFile(const std::string & path, Sink & sink) override
{
try {
return make_box_ptr<GeneratorSource>(readFileSource(binaryCacheDir + "/" + path));
readFile(binaryCacheDir + "/" + path, sink);
} catch (SysError & e) {
if (e.errNo == ENOENT)
throw NoSuchBinaryCacheFile("file '%s' does not exist in binary cache", path);
+4 -4
View File
@@ -23,7 +23,7 @@ struct LocalStoreAccessor : public FSAccessor
{
auto storePath = store->toStorePath(path).first;
if (requireValidPath && !store->isValidPath(storePath))
throw InvalidPath("path '%1%' does not exist in the store", store->printStorePath(storePath));
throw InvalidPath("path '%1%' is not a valid store path", store->printStorePath(storePath));
return store->getRealStoreDir() + std::string(path, store->storeDir.size());
}
@@ -78,11 +78,11 @@ ref<FSAccessor> LocalFSStore::getFSAccessor()
std::dynamic_pointer_cast<LocalFSStore>(shared_from_this())));
}
WireFormatGenerator LocalFSStore::narFromPath(const StorePath & path)
void LocalFSStore::narFromPath(const StorePath & path, Sink & sink)
{
if (!isValidPath(path))
throw Error("path '%s' does not exist in store", printStorePath(path));
return dumpPath(getRealStoreDir() + std::string(printStorePath(path), storeDir.size()));
throw Error("path '%s' is not valid", printStorePath(path));
dumpPath(getRealStoreDir() + std::string(printStorePath(path), storeDir.size()), sink);
}
const std::string LocalFSStore::drvsLogDir = "drvs";
+1 -1
View File
@@ -42,7 +42,7 @@ public:
LocalFSStore(const Params & params);
WireFormatGenerator narFromPath(const StorePath & path) override;
void narFromPath(const StorePath & path, Sink & sink) override;
ref<FSAccessor> getFSAccessor() override;
/**
+17 -15
View File
@@ -899,7 +899,7 @@ std::shared_ptr<const ValidPathInfo> LocalStore::queryPathInfoInternal(State & s
auto useQueryPathInfo(state.stmts->QueryPathInfo.use()(printStorePath(path)));
if (!useQueryPathInfo.next())
return nullptr;
return std::shared_ptr<ValidPathInfo>();
auto id = useQueryPathInfo.getInt(0);
@@ -907,7 +907,7 @@ std::shared_ptr<const ValidPathInfo> LocalStore::queryPathInfoInternal(State & s
try {
narHash = Hash::parseAnyPrefixed(useQueryPathInfo.getStr(1));
} catch (BadHash & e) {
throw BadStorePath("bad hash in store path '%s': %s", printStorePath(path), e.what());
throw Error("invalid-path entry for '%s': %s", printStorePath(path), e.what());
}
auto info = std::make_shared<ValidPathInfo>(path, narHash);
@@ -957,8 +957,8 @@ void LocalStore::updatePathInfo(State & state, const ValidPathInfo & info)
uint64_t LocalStore::queryValidPathId(State & state, const StorePath & path)
{
auto use(state.stmts->QueryPathInfo.use()(printStorePath(path)));
if (!use.next()) // TODO: I guess if SQLITE got corrupted..?
throw InvalidPath("path '%s' does not exist in the Lix database", printStorePath(path));
if (!use.next())
throw InvalidPath("path '%s' is not valid", printStorePath(path));
return use.getInt(0);
}
@@ -1406,7 +1406,7 @@ StorePath LocalStore::addToStoreFromDump(Source & source0, std::string_view name
auto narHash = std::pair { hash, size };
if (method != FileIngestionMethod::Recursive || hashAlgo != htSHA256) {
HashSink narSink { htSHA256 };
narSink << dumpPath(realPath);
dumpPath(realPath, narSink);
narHash = narSink.finish();
}
@@ -1461,7 +1461,7 @@ StorePath LocalStore::addTextToStore(
canonicalisePathMetaData(realPath, {});
StringSink sink;
sink << dumpString(s);
dumpString(s, sink);
auto narHash = hashString(htSHA256, sink.s);
optimisePath(realPath, repair);
@@ -1601,7 +1601,7 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
auto hashSink = HashSink(info->narHash.type);
hashSink << dumpPath(Store::toRealPath(i));
dumpPath(Store::toRealPath(i), hashSink);
auto current = hashSink.finish();
if (info->narHash != nullHash && info->narHash != current.first) {
@@ -1887,23 +1887,25 @@ ContentAddress LocalStore::hashCAPath(
const std::string_view pathHash
)
{
auto data = std::visit(overloaded {
[&](const TextIngestionMethod &) -> GeneratorSource {
return GeneratorSource(readFileSource(path));
HashModuloSink caSink ( hashType, std::string(pathHash) );
std::visit(overloaded {
[&](const TextIngestionMethod &) {
readFile(path, caSink);
},
[&](const FileIngestionMethod & m2) -> GeneratorSource {
[&](const FileIngestionMethod & m2) {
switch (m2) {
case FileIngestionMethod::Recursive:
return GeneratorSource(dumpPath(path));
dumpPath(path, caSink);
break;
case FileIngestionMethod::Flat:
return GeneratorSource(readFileSource(path));
readFile(path, caSink);
break;
}
assert(false);
},
}, method.raw);
return ContentAddress {
.method = method,
.hash = computeHashModulo(hashType, std::string(pathHash), data).first,
.hash = caSink.finish().first,
};
}
+11 -8
View File
@@ -23,7 +23,7 @@ std::map<StorePath, StorePath> makeContentAddressed(
std::string oldHashPart(path.hashPart());
StringSink sink;
sink << srcStore.narFromPath(path);
srcStore.narFromPath(path, sink);
StringMap rewrites;
@@ -43,10 +43,10 @@ std::map<StorePath, StorePath> makeContentAddressed(
sink.s = rewriteStrings(sink.s, rewrites);
auto narModuloHash = [&] {
StringSource source{sink.s};
return computeHashModulo(htSHA256, oldHashPart, source).first;
}();
HashModuloSink hashModuloSink(htSHA256, oldHashPart);
hashModuloSink(sink.s);
auto narModuloHash = hashModuloSink.finish().first;
ValidPathInfo info {
dstStore,
@@ -61,12 +61,15 @@ std::map<StorePath, StorePath> makeContentAddressed(
printInfo("rewriting '%s' to '%s'", pathS, dstStore.printStorePath(info.path));
const auto rewritten = rewriteStrings(sink.s, {{oldHashPart, std::string(info.path.hashPart())}});
StringSink sink2;
RewritingSink rsink2(oldHashPart, std::string(info.path.hashPart()), sink2);
rsink2(sink.s);
rsink2.flush();
info.narHash = hashString(htSHA256, rewritten);
info.narHash = hashString(htSHA256, sink2.s);
info.narSize = sink.s.size();
StringSource source(rewritten);
StringSource source(sink2.s);
dstStore.addToStore(info, source);
remappings.insert_or_assign(std::move(path), std::move(info.path));
+1 -1
View File
@@ -61,7 +61,7 @@ StorePathSet scanForReferences(
TeeSink sink { refsSink, toTee };
/* Look for the hashes in the NAR dump of the path. */
sink << dumpPath(path);
dumpPath(path, sink);
return refsSink.getResultPaths();
}
-40
View File
@@ -1,5 +1,4 @@
#include "local-store.hh"
#include "build/local-derivation-goal.hh"
#if __linux__
#include "platform/linux.hh"
@@ -20,43 +19,4 @@ std::shared_ptr<LocalStore> LocalStore::makeLocalStore(const Params & params)
return std::shared_ptr<LocalStore>(new FallbackLocalStore(params));
#endif
}
std::shared_ptr<LocalDerivationGoal> LocalDerivationGoal::makeLocalDerivationGoal(
const StorePath & drvPath,
const OutputsSpec & wantedOutputs,
Worker & worker,
BuildMode buildMode
)
{
#if __linux__
return std::make_shared<LinuxLocalDerivationGoal>(drvPath, wantedOutputs, worker, buildMode);
#elif __APPLE__
return std::make_shared<DarwinLocalDerivationGoal>(drvPath, wantedOutputs, worker, buildMode);
#else
return std::make_shared<FallbackLocalDerivationGoal>(drvPath, wantedOutputs, worker, buildMode);
#endif
}
std::shared_ptr<LocalDerivationGoal> LocalDerivationGoal::makeLocalDerivationGoal(
const StorePath & drvPath,
const BasicDerivation & drv,
const OutputsSpec & wantedOutputs,
Worker & worker,
BuildMode buildMode
)
{
#if __linux__
return std::make_shared<LinuxLocalDerivationGoal>(
drvPath, drv, wantedOutputs, worker, buildMode
);
#elif __APPLE__
return std::make_shared<DarwinLocalDerivationGoal>(
drvPath, drv, wantedOutputs, worker, buildMode
);
#else
return std::make_shared<FallbackLocalDerivationGoal>(
drvPath, drv, wantedOutputs, worker, buildMode
);
#endif
}
}
-26
View File
@@ -6,7 +6,6 @@
#include <sys/proc_info.h>
#include <sys/sysctl.h>
#include <libproc.h>
#include <spawn.h>
#include <regex>
@@ -221,29 +220,4 @@ void DarwinLocalStore::findPlatformRoots(UncheckedRoots & unchecked)
}
}
}
void DarwinLocalDerivationGoal::execBuilder(std::string builder, Strings args, Strings envStrs)
{
posix_spawnattr_t attrp;
if (posix_spawnattr_init(&attrp))
throw SysError("failed to initialize builder");
if (posix_spawnattr_setflags(&attrp, POSIX_SPAWN_SETEXEC))
throw SysError("failed to initialize builder");
if (drv->platform == "aarch64-darwin") {
// Unset kern.curproc_arch_affinity so we can escape Rosetta
int affinity = 0;
sysctlbyname("kern.curproc_arch_affinity", NULL, NULL, &affinity, sizeof(affinity));
cpu_type_t cpu = CPU_TYPE_ARM64;
posix_spawnattr_setbinpref_np(&attrp, 1, &cpu, NULL);
} else if (drv->platform == "x86_64-darwin") {
cpu_type_t cpu = CPU_TYPE_X86_64;
posix_spawnattr_setbinpref_np(&attrp, 1, &cpu, NULL);
}
posix_spawn(NULL, builder.c_str(), NULL, &attrp, stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data());
}
}
-16
View File
@@ -1,7 +1,6 @@
#pragma once
///@file
#include "build/local-derivation-goal.hh"
#include "gc-store.hh"
#include "local-store.hh"
@@ -33,19 +32,4 @@ private:
void findPlatformRoots(UncheckedRoots & unchecked) override;
};
/**
* Darwin-specific implementation of LocalDerivationGoal
*/
class DarwinLocalDerivationGoal : public LocalDerivationGoal
{
public:
using LocalDerivationGoal::LocalDerivationGoal;
private:
/**
* Set process flags to enter or leave rosetta, then execute the builder
*/
void execBuilder(std::string builder, Strings args, Strings envStrs) override;
};
}
-11
View File
@@ -1,7 +1,6 @@
#pragma once
///@file
#include "build/local-derivation-goal.hh"
#include "local-store.hh"
namespace nix {
@@ -29,14 +28,4 @@ public:
}
};
/**
* Fallback platform implementation of LocalDerivationGoal
* Exists so we can make LocalDerivationGoal constructor protected
*/
class FallbackLocalDerivationGoal : public LocalDerivationGoal
{
public:
using LocalDerivationGoal::LocalDerivationGoal;
};
}
-14
View File
@@ -1,4 +1,3 @@
#include "cgroup.hh"
#include "gc-store.hh"
#include "signals.hh"
#include "platform/linux.hh"
@@ -115,17 +114,4 @@ void LinuxLocalStore::findPlatformRoots(UncheckedRoots & unchecked)
readFileRoots("/proc/sys/kernel/fbsplash", unchecked);
readFileRoots("/proc/sys/kernel/poweroff_cmd", unchecked);
}
void LinuxLocalDerivationGoal::killSandbox(bool getStats)
{
if (cgroup) {
auto stats = destroyCgroup(*cgroup);
if (getStats) {
buildResult.cpuUser = stats.cpuUser;
buildResult.cpuSystem = stats.cpuSystem;
}
} else {
LocalDerivationGoal::killSandbox(getStats);
}
}
}
-13
View File
@@ -1,7 +1,6 @@
#pragma once
///@file
#include "build/local-derivation-goal.hh"
#include "gc-store.hh"
#include "local-store.hh"
@@ -33,16 +32,4 @@ private:
void findPlatformRoots(UncheckedRoots & unchecked) override;
};
/**
* Linux-specific implementation of LocalDerivationGoal
*/
class LinuxLocalDerivationGoal : public LocalDerivationGoal
{
public:
using LocalDerivationGoal::LocalDerivationGoal;
private:
void killSandbox(bool getStatus) override;
};
}
+2 -2
View File
@@ -55,7 +55,7 @@ std::pair<ref<FSAccessor>, Path> RemoteFSAccessor::fetch(const Path & path_, boo
auto [storePath, restPath] = store->toStorePath(path);
if (requireValidPath && !store->isValidPath(storePath))
throw InvalidPath("path '%1%' does not exist in remote store", store->printStorePath(storePath));
throw InvalidPath("path '%1%' is not a valid store path", store->printStorePath(storePath));
auto i = nars.find(std::string(storePath.hashPart()));
if (i != nars.end()) return {i->second, restPath};
@@ -97,7 +97,7 @@ std::pair<ref<FSAccessor>, Path> RemoteFSAccessor::fetch(const Path & path_, boo
}
StringSink sink;
sink << store->narFromPath(storePath);
store->narFromPath(storePath, sink);
return {addToCache(storePath.hashPart(), std::move(sink.s)), restPath};
}
+13 -4
View File
@@ -27,8 +27,11 @@ struct RemoteStore::Connection
FdSource from;
/**
* The worker protocol version of the connected daemon. This may be newer
* than this Lix supports.
* Worker protocol version used for the connection.
*
* Despite its name, I think it is actually the maximum version both
* sides support. (If the maximum doesn't exist, we would fail to
* establish a connection and produce a value of this type.)
*/
WorkerProto::Version daemonVersion;
@@ -68,7 +71,10 @@ struct RemoteStore::Connection
*/
operator WorkerProto::ReadConn ()
{
return WorkerProto::ReadConn {from, daemonVersion};
return WorkerProto::ReadConn {
.from = from,
.version = daemonVersion,
};
}
/**
@@ -81,7 +87,10 @@ struct RemoteStore::Connection
*/
operator WorkerProto::WriteConn ()
{
return WorkerProto::WriteConn {to, daemonVersion};
return WorkerProto::WriteConn {
.to = to,
.version = daemonVersion,
};
}
virtual ~Connection();
+188 -111
View File
@@ -75,14 +75,17 @@ void RemoteStore::initConnection(Connection & conn)
conn.from >> conn.daemonVersion;
if (GET_PROTOCOL_MAJOR(conn.daemonVersion) != GET_PROTOCOL_MAJOR(PROTOCOL_VERSION))
throw Error("Nix daemon protocol version not supported");
if (GET_PROTOCOL_MINOR(conn.daemonVersion) < MIN_SUPPORTED_MINOR_WORKER_PROTO_VERSION)
if (GET_PROTOCOL_MINOR(conn.daemonVersion) < 10)
throw Error("the Nix daemon version is too old");
conn.to << PROTOCOL_VERSION;
// Obsolete CPU affinity.
conn.to << 0;
if (GET_PROTOCOL_MINOR(conn.daemonVersion) >= 14) {
// Obsolete CPU affinity.
conn.to << 0;
}
conn.to << false; // obsolete reserveSpace
if (GET_PROTOCOL_MINOR(conn.daemonVersion) >= 11)
conn.to << false; // obsolete reserveSpace
if (GET_PROTOCOL_MINOR(conn.daemonVersion) >= 33) {
conn.to.flush();
@@ -123,22 +126,24 @@ void RemoteStore::setOptions(Connection & conn)
<< settings.buildCores
<< settings.useSubstitutes;
std::map<std::string, Config::SettingInfo> overrides;
settings.getSettings(overrides, true); // libstore settings
fileTransferSettings.getSettings(overrides, true);
overrides.erase(settings.keepFailed.name);
overrides.erase(settings.keepGoing.name);
overrides.erase(settings.tryFallback.name);
overrides.erase(settings.maxBuildJobs.name);
overrides.erase(settings.maxSilentTime.name);
overrides.erase(settings.buildCores.name);
overrides.erase(settings.useSubstitutes.name);
overrides.erase(loggerSettings.showTrace.name);
overrides.erase(experimentalFeatureSettings.experimentalFeatures.name);
overrides.erase(settings.pluginFiles.name);
conn.to << overrides.size();
for (auto & i : overrides)
conn.to << i.first << i.second.value;
if (GET_PROTOCOL_MINOR(conn.daemonVersion) >= 12) {
std::map<std::string, Config::SettingInfo> overrides;
settings.getSettings(overrides, true); // libstore settings
fileTransferSettings.getSettings(overrides, true);
overrides.erase(settings.keepFailed.name);
overrides.erase(settings.keepGoing.name);
overrides.erase(settings.tryFallback.name);
overrides.erase(settings.maxBuildJobs.name);
overrides.erase(settings.maxSilentTime.name);
overrides.erase(settings.buildCores.name);
overrides.erase(settings.useSubstitutes.name);
overrides.erase(loggerSettings.showTrace.name);
overrides.erase(experimentalFeatureSettings.experimentalFeatures.name);
overrides.erase(settings.pluginFiles.name);
conn.to << overrides.size();
for (auto & i : overrides)
conn.to << i.first << i.second.value;
}
auto ex = conn.processStderr();
if (ex) std::rethrow_exception(ex);
@@ -202,13 +207,20 @@ bool RemoteStore::isValidPathUncached(const StorePath & path)
StorePathSet RemoteStore::queryValidPaths(const StorePathSet & paths, SubstituteFlag maybeSubstitute)
{
auto conn(getConnection());
conn->to << WorkerProto::Op::QueryValidPaths;
conn->to << WorkerProto::write(*this, *conn, paths);
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 27) {
conn->to << maybeSubstitute;
if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 12) {
StorePathSet res;
for (auto & i : paths)
if (isValidPath(i)) res.insert(i);
return res;
} else {
conn->to << WorkerProto::Op::QueryValidPaths;
WorkerProto::write(*this, *conn, paths);
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 27) {
conn->to << maybeSubstitute;
}
conn.processStderr();
return WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
}
conn.processStderr();
return WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
}
@@ -224,10 +236,20 @@ StorePathSet RemoteStore::queryAllValidPaths()
StorePathSet RemoteStore::querySubstitutablePaths(const StorePathSet & paths)
{
auto conn(getConnection());
conn->to << WorkerProto::Op::QuerySubstitutablePaths;
conn->to << WorkerProto::write(*this, *conn, paths);
conn.processStderr();
return WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 12) {
StorePathSet res;
for (auto & i : paths) {
conn->to << WorkerProto::Op::HasSubstitutes << printStorePath(i);
conn.processStderr();
if (readInt(conn->from)) res.insert(i);
}
return res;
} else {
conn->to << WorkerProto::Op::QuerySubstitutablePaths;
WorkerProto::write(*this, *conn, paths);
conn.processStderr();
return WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
}
}
@@ -237,25 +259,45 @@ void RemoteStore::querySubstitutablePathInfos(const StorePathCAMap & pathsMap, S
auto conn(getConnection());
if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 12) {
for (auto & i : pathsMap) {
SubstitutablePathInfo info;
conn->to << WorkerProto::Op::QuerySubstitutablePathInfo << printStorePath(i.first);
conn.processStderr();
unsigned int reply = readInt(conn->from);
if (reply == 0) continue;
auto deriver = readString(conn->from);
if (deriver != "")
info.deriver = parseStorePath(deriver);
info.references = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
info.downloadSize = readLongLong(conn->from);
info.narSize = readLongLong(conn->from);
infos.insert_or_assign(i.first, std::move(info));
}
} else {
conn->to << WorkerProto::Op::QuerySubstitutablePathInfos;
if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 22) {
StorePathSet paths;
for (auto & path : pathsMap)
paths.insert(path.first);
WorkerProto::write(*this, *conn, paths);
} else
WorkerProto::write(*this, *conn, pathsMap);
conn.processStderr();
size_t count = readNum<size_t>(conn->from);
for (size_t n = 0; n < count; n++) {
SubstitutablePathInfo & info(infos[parseStorePath(readString(conn->from))]);
auto deriver = readString(conn->from);
if (deriver != "")
info.deriver = parseStorePath(deriver);
info.references = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
info.downloadSize = readLongLong(conn->from);
info.narSize = readLongLong(conn->from);
}
conn->to << WorkerProto::Op::QuerySubstitutablePathInfos;
if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 22) {
StorePathSet paths;
for (auto & path : pathsMap)
paths.insert(path.first);
conn->to << WorkerProto::write(*this, *conn, paths);
} else
conn->to << WorkerProto::write(*this, *conn, pathsMap);
conn.processStderr();
size_t count = readNum<size_t>(conn->from);
for (size_t n = 0; n < count; n++) {
SubstitutablePathInfo & info(infos[parseStorePath(readString(conn->from))]);
auto deriver = readString(conn->from);
if (deriver != "")
info.deriver = parseStorePath(deriver);
info.references = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
info.downloadSize = readLongLong(conn->from);
info.narSize = readLongLong(conn->from);
}
}
@@ -267,15 +309,15 @@ std::shared_ptr<const ValidPathInfo> RemoteStore::queryPathInfoUncached(const St
try {
conn.processStderr();
} catch (Error & e) {
// Ugly backwards compatibility hack. TODO(fj#325): remove.
// Ugly backwards compatibility hack.
if (e.msg().find("is not valid") != std::string::npos)
return nullptr;
throw InvalidPath(std::move(e.info()));
throw;
}
bool valid; conn->from >> valid;
if (!valid) return nullptr;
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 17) {
bool valid; conn->from >> valid;
if (!valid) throw InvalidPath("path '%s' is not valid", printStorePath(path));
}
return std::make_shared<ValidPathInfo>(
StorePath{path},
WorkerProto::Serialise<UnkeyedValidPathInfo>::read(*this, *conn));
@@ -304,10 +346,9 @@ StorePathSet RemoteStore::queryValidDerivers(const StorePath & path)
StorePathSet RemoteStore::queryDerivationOutputs(const StorePath & path)
{
if (GET_PROTOCOL_MINOR(getProtocol()) >= 22) {
if (GET_PROTOCOL_MINOR(getProtocol()) >= 0x16) {
return Store::queryDerivationOutputs(path);
}
REMOVE_AFTER_DROPPING_PROTO_MINOR(21);
auto conn(getConnection());
conn->to << WorkerProto::Op::QueryDerivationOutputs << printStorePath(path);
conn.processStderr();
@@ -317,7 +358,7 @@ StorePathSet RemoteStore::queryDerivationOutputs(const StorePath & path)
std::map<std::string, std::optional<StorePath>> RemoteStore::queryPartialDerivationOutputMap(const StorePath & path, Store * evalStore_)
{
if (GET_PROTOCOL_MINOR(getProtocol()) >= 22) {
if (GET_PROTOCOL_MINOR(getProtocol()) >= 0x16) {
if (!evalStore_) {
auto conn(getConnection());
conn->to << WorkerProto::Op::QueryDerivationOutputMap << printStorePath(path);
@@ -337,7 +378,6 @@ std::map<std::string, std::optional<StorePath>> RemoteStore::queryPartialDerivat
return outputs;
}
} else {
REMOVE_AFTER_DROPPING_PROTO_MINOR(21);
auto & evalStore = evalStore_ ? *evalStore_ : *this;
// Fallback for old daemon versions.
// For floating-CA derivations (and their co-dependencies) this is an
@@ -377,7 +417,7 @@ ref<const ValidPathInfo> RemoteStore::addCAToStore(
<< WorkerProto::Op::AddToStore
<< name
<< caMethod.render(hashType);
conn->to << WorkerProto::write(*this, *conn, references);
WorkerProto::write(*this, *conn, references);
conn->to << repair;
// The dump source may invoke the store, so we need to make some room.
@@ -402,7 +442,7 @@ ref<const ValidPathInfo> RemoteStore::addCAToStore(
name, printHashType(hashType));
std::string s = dump.drain();
conn->to << WorkerProto::Op::AddTextToStore << name << s;
conn->to << WorkerProto::write(*this, *conn, references);
WorkerProto::write(*this, *conn, references);
conn.processStderr();
},
[&](const FileIngestionMethod & fim) -> void {
@@ -422,7 +462,7 @@ ref<const ValidPathInfo> RemoteStore::addCAToStore(
dump.drainInto(conn->to);
} else {
std::string contents = dump.drain();
conn->to << dumpString(contents);
dumpString(contents, conn->to);
}
}
conn.processStderr();
@@ -458,21 +498,51 @@ void RemoteStore::addToStore(const ValidPathInfo & info, Source & source,
{
auto conn(getConnection());
conn->to << WorkerProto::Op::AddToStoreNar
<< printStorePath(info.path)
<< (info.deriver ? printStorePath(*info.deriver) : "")
<< info.narHash.to_string(Base16, false);
conn->to << WorkerProto::write(*this, *conn, info.references);
conn->to << info.registrationTime << info.narSize
<< info.ultimate << info.sigs << renderContentAddress(info.ca)
<< repair << !checkSigs;
if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 18) {
conn->to << WorkerProto::Op::ImportPaths;
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 23) {
conn.withFramedSink([&](Sink & sink) {
sink << copyNAR(source);
auto source2 = sinkToSource([&](Sink & sink) {
sink << 1 // == path follows
;
copyNAR(source, sink);
sink
<< exportMagic
<< printStorePath(info.path);
WorkerProto::WriteConn nested { .to = sink, .version = conn->daemonVersion };
WorkerProto::write(*this, nested, info.references);
sink
<< (info.deriver ? printStorePath(*info.deriver) : "")
<< 0 // == no legacy signature
<< 0 // == no path follows
;
});
} else {
conn.processStderr(0, &source);
conn.processStderr(0, source2.get());
auto importedPaths = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
assert(importedPaths.size() <= 1);
}
else {
conn->to << WorkerProto::Op::AddToStoreNar
<< printStorePath(info.path)
<< (info.deriver ? printStorePath(*info.deriver) : "")
<< info.narHash.to_string(Base16, false);
WorkerProto::write(*this, *conn, info.references);
conn->to << info.registrationTime << info.narSize
<< info.ultimate << info.sigs << renderContentAddress(info.ca)
<< repair << !checkSigs;
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 23) {
conn.withFramedSink([&](Sink & sink) {
copyNAR(source, sink);
});
} else if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 21) {
conn.processStderr(0, &source);
} else {
copyNAR(source, conn->to);
conn.processStderr(0, nullptr);
}
}
}
@@ -483,27 +553,20 @@ void RemoteStore::addMultipleToStore(
RepairFlag repair,
CheckSigsFlag checkSigs)
{
auto remoteVersion = getProtocol();
GeneratorSource source{[](auto self, auto & pathsToCopy, auto remoteVersion) -> WireFormatGenerator {
NullSink null; // TODO remove .to from WriteConn instead
co_yield pathsToCopy.size();
auto source = sinkToSource([&](Sink & sink) {
sink << pathsToCopy.size();
for (auto & [pathInfo, pathSource] : pathsToCopy) {
co_yield WorkerProto::Serialise<ValidPathInfo>::write(*self,
WorkerProto::WriteConn {null, remoteVersion},
WorkerProto::Serialise<ValidPathInfo>::write(*this,
WorkerProto::WriteConn {
.to = sink,
.version = 16,
},
pathInfo);
try {
char buf[65536];
while (true) {
const auto read = pathSource->read(buf, sizeof(buf));
co_yield std::span{buf, read};
}
} catch (EndOfFile &) {
}
pathSource->drainInto(sink);
}
}(this, pathsToCopy, remoteVersion)};
});
addMultipleToStore(source, repair, checkSigs);
addMultipleToStore(*source, repair, checkSigs);
}
void RemoteStore::addMultipleToStore(
@@ -540,11 +603,10 @@ void RemoteStore::registerDrvOutput(const Realisation & info)
auto conn(getConnection());
conn->to << WorkerProto::Op::RegisterDrvOutput;
if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 31) {
REMOVE_AFTER_DROPPING_PROTO_MINOR(30);
conn->to << info.id.to_string();
conn->to << std::string(info.outPath.to_string());
} else {
conn->to << WorkerProto::write(*this, *conn, info);
WorkerProto::write(*this, *conn, info);
}
conn.processStderr();
}
@@ -605,8 +667,15 @@ void RemoteStore::buildPaths(const std::vector<DerivedPath> & drvPaths, BuildMod
auto conn(getConnection());
conn->to << WorkerProto::Op::BuildPaths;
conn->to << WorkerProto::write(*this, *conn, drvPaths);
conn->to << buildMode;
assert(GET_PROTOCOL_MINOR(conn->daemonVersion) >= 13);
WorkerProto::write(*this, *conn, drvPaths);
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 15)
conn->to << buildMode;
else
/* Old daemons did not take a 'buildMode' parameter, so we
need to validate it here on the client side. */
if (buildMode != bmNormal)
throw Error("repairing or checking is not supported when building through the Nix daemon");
conn.processStderr();
readInt(conn->from);
}
@@ -623,12 +692,11 @@ std::vector<KeyedBuildResult> RemoteStore::buildPathsWithResults(
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 34) {
conn->to << WorkerProto::Op::BuildPathsWithResults;
conn->to << WorkerProto::write(*this, *conn, paths);
WorkerProto::write(*this, *conn, paths);
conn->to << buildMode;
conn.processStderr();
return WorkerProto::Serialise<std::vector<KeyedBuildResult>>::read(*this, *conn);
} else {
REMOVE_AFTER_DROPPING_PROTO_MINOR(33);
// Avoid deadlock.
conn_.reset();
@@ -748,7 +816,7 @@ void RemoteStore::collectGarbage(const GCOptions & options, GCResults & results)
conn->to
<< WorkerProto::Op::CollectGarbage << options.action;
conn->to << WorkerProto::write(*this, *conn, options.pathsToDelete);
WorkerProto::write(*this, *conn, options.pathsToDelete);
conn->to << options.ignoreLiveness
<< options.maxFreed
/* removed options */
@@ -798,14 +866,25 @@ void RemoteStore::queryMissing(const std::vector<DerivedPath> & targets,
StorePathSet & willBuild, StorePathSet & willSubstitute, StorePathSet & unknown,
uint64_t & downloadSize, uint64_t & narSize)
{
auto conn(getConnection());
conn->to << WorkerProto::Op::QueryMissing;
conn->to << WorkerProto::write(*this, *conn, targets);
conn.processStderr();
willBuild = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
willSubstitute = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
unknown = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
conn->from >> downloadSize >> narSize;
{
auto conn(getConnection());
if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 19)
// Don't hold the connection handle in the fallback case
// to prevent a deadlock.
goto fallback;
conn->to << WorkerProto::Op::QueryMissing;
WorkerProto::write(*this, *conn, targets);
conn.processStderr();
willBuild = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
willSubstitute = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
unknown = WorkerProto::Serialise<StorePathSet>::read(*this, *conn);
conn->from >> downloadSize >> narSize;
return;
}
fallback:
return Store::queryMissing(targets, willBuild, willSubstitute,
unknown, downloadSize, narSize);
}
@@ -856,14 +935,12 @@ RemoteStore::Connection::~Connection()
}
}
WireFormatGenerator RemoteStore::narFromPath(const StorePath & path)
void RemoteStore::narFromPath(const StorePath & path, Sink & sink)
{
auto conn(connections->get());
conn->to << WorkerProto::Op::NarFromPath << printStorePath(path);
conn->processStderr();
return [](auto conn) -> WireFormatGenerator {
co_yield copyNAR(conn->from);
}(std::move(conn));
copyNAR(conn->from, sink);
}
ref<FSAccessor> RemoteStore::getFSAccessor()
@@ -907,7 +984,7 @@ std::exception_ptr RemoteStore::Connection::processStderr(Sink * sink, Source *
if (!source) throw Error("no source");
size_t len = readNum<size_t>(from);
auto buf = std::make_unique<char[]>(len);
to << std::string_view((const char *) buf.get(), source->read(buf.get(), len));
writeString({(const char *) buf.get(), source->read(buf.get(), len)}, to);
to.flush();
}
+1 -1
View File
@@ -183,7 +183,7 @@ protected:
virtual ref<FSAccessor> getFSAccessor() override;
virtual WireFormatGenerator narFromPath(const StorePath & path) override;
virtual void narFromPath(const StorePath & path, Sink & sink) override;
private:
+9 -10
View File
@@ -55,14 +55,12 @@ class AwsLogger : public Aws::Utils::Logging::FormattedLogSystem
void ProcessFormattedStatement(Aws::String && statement) override
{
// FIXME: workaround for truly excessive log spam in debug level: https://github.com/aws/aws-sdk-cpp/pull/3003
if ((statement.find("(SSLDataIn)") != std::string::npos || statement.find("(SSLDataOut)") != std::string::npos) && verbosity <= lvlDebug) {
return;
}
debug("AWS: %s", chomp(statement));
}
#if !(AWS_VERSION_MAJOR <= 1 && AWS_VERSION_MINOR <= 7 && AWS_VERSION_PATCH <= 115)
void Flush() override {}
#endif
};
static void initAWS()
@@ -102,7 +100,12 @@ S3Helper::S3Helper(
: std::dynamic_pointer_cast<Aws::Auth::AWSCredentialsProvider>(
std::make_shared<Aws::Auth::ProfileConfigFileAWSCredentialsProvider>(profile.c_str())),
*config,
// FIXME: https://github.com/aws/aws-sdk-cpp/issues/759
#if AWS_VERSION_MAJOR == 1 && AWS_VERSION_MINOR < 3
false,
#else
Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never,
#endif
endpoint.empty()))
{
}
@@ -455,7 +458,7 @@ struct S3BinaryCacheStoreImpl : virtual S3BinaryCacheStoreConfig, public virtual
uploadFile(path, istream, mimeType, "");
}
box_ptr<Source> getFile(const std::string & path) override
void getFile(const std::string & path, Sink & sink) override
{
stats.get++;
@@ -469,11 +472,7 @@ struct S3BinaryCacheStoreImpl : virtual S3BinaryCacheStoreConfig, public virtual
printTalkative("downloaded 's3://%s/%s' (%d bytes) in %d ms",
bucketName, path, res.data->size(), res.durationMs);
return make_box_ptr<GeneratorSource>(
[](std::string data) -> Generator<Bytes> {
co_yield std::span{data.data(), data.size()};
}(std::move(*res.data))
);
sink(*res.data);
} else
throw NoSuchBinaryCacheFile("file '%s' does not exist in binary cache '%s'", path, getUri());
}

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