Compare commits

..
Author SHA1 Message Date
Raito Bezarius 609bc41e6f release: 2.95.2 "Kakigōri"
Release produced with releng/create_release.xsh

Change-Id: Ie89de7f246515d34f2da91de05942c7d3dcadb60
2026-05-04 18:43:45 +02:00
Raito Bezarius 89999119dc release: release notes for 2.95.2
Release created with releng/create_release.xsh

Change-Id: I8f7770c1a04ccac61a5c40d2453af28ad2810594
2026-05-04 18:43:36 +02:00
eldritch horrorsandRaito Bezarius 71043329f1 libutil: fix nar parser buffer overflow
string data shares a buffer with the binary string length field. size
calculations for string read buffers always include the length field;
sufficiently large length fields can cause these calculations to wrap.
a malicious nar could use this for OOB writes in the daemon (as root).

since we use strings only as tags for archive members and for symlinks
with their OS-dependent length limits we can simply limit string size.
1 MiB should be sufficient for all symlinks, and tags are always tiny.

Change-Id: I89fb05f73c1dbeda45d91244aba4cd526a3d83e1
2026-05-04 18:39:33 +02:00
Raito Bezarius ecaf293c4f releng/keys: update the way to receive the ephemeral key
I don't understand how `ssh -l lix-releng` is supposed to work if it
doesn't say which host to target.

Change-Id: I791f3f3f49ecd5884c9e86b5d3b617fc139e031f
Signed-off-by: Raito Bezarius <raito@lix.systems>
2026-05-04 18:36:47 +02:00
Raito Bezarius 09cb1fbca9 releng/environment: update staging parameters
These parameters are now created on https://s3.afnix.fr.

Change-Id: I96b6fd913429ee46d04c412cb141edd288665ced
Signed-off-by: Raito Bezarius <raito@lix.systems>
2026-05-04 18:36:42 +02:00
YurekaandRaito Bezarius 23620d0b7a releng: Adapt for AFNix S3
Change-Id: I29dbd62dcc70595ba3f2ac2a466a5c26a28aea99
(cherry picked from commit 0c63036c7d)
2026-05-04 18:31:59 +02:00
Florian KlinkandRaito Bezarius 351dbdfdca libcstore: Fix null deref in writeDebugInfo for non-directory NARs
When index-debug-info is enabled and the store path being copied is a
regular file (not a directory), std::get_if<nar_index::Directory>
returns nullptr since the NAR root is a File variant. The loop then
immediately dereferences buildIdDir->contents on the null pointer,
causing a segfault.

Add a null check at the top of the loop to break early when the NAR
root is not a directory.

Change-Id: I3a6e792b84cc12c837ecaddf4fee889e1bcb6397
(cherry picked from commit 6c7ccc2588)
2026-04-27 13:24:38 +00:00
Tom Hubrechtandeldritch horrors 128159a717 repl: Fix the use command
The culprit was a space added at the wrong place, which messed with c++
raw strings.

Fixes #1178

Change-Id: Ic1e09cb7215d9a6dd2d13fd92242649b0e1fcd13
(cherry picked from commit cbeb4fcd69)
2026-04-07 14:06:19 +00:00
Raito Bezarius 33f713fa5d 2.95.2: start a fresh minor release
Change-Id: I6e6791eeafd2c7ffb29bbf1738eb78ec497f2cc9
Signed-off-by: Raito Bezarius <raito@lix.systems>
2026-03-19 17:19:32 +01:00
Raito Bezarius f92aaa9f59 release: 2.95.1 "Kakigōri"
Release produced with releng/create_release.xsh

Change-Id: I07101abfa767a38e61d693b4b3c8cb8493d9fe1e
2026-03-19 11:24:52 +01:00
Raito Bezarius e70bbff278 release: release notes for 2.95.1
Release created with releng/create_release.xsh

Change-Id: I8361f8049cba271390280bbdefb627496df9f21e
2026-03-19 11:24:48 +01:00
Raito Bezarius d2f8263fb8 2.95.1: release version
Change-Id: If826beca905f372ce857f966c6c26ee1cfd2229b
Signed-off-by: Raito Bezarius <raito@lix.systems>
2026-03-19 11:24:48 +01:00
sterniandeldritch horrors 15421962e6 libcmd: add support for lowdown >= 3.0.0
lowdown 3.0.0 merged some flags into one to save on bits and did not add
any aliases for backward compatibility.

As with the changes for lowdown >= 1.4, we define a preprocessor flag to
gate the changes on and add a job to CI to ensure that lowdown < 3.0
keeps working (which is used by NixOS 25.11).

Unfortunately, we need to jump through some hoops to prevent nix and lix
from upstream Nixpkgs from being rebuilt due to a changed lowdown. Since
both implementation's packaging in Nixpkgs has their own package set /
fix point now, we can't simply inherit them from `prev` since they will
always be (re-)computed from the `final` fix point. As a consequence,
we need to expose our changed lowdown version at a non-default attribute
or break the builds of Nixpkgs derivations we test against.

Change-Id: I20a3e2fdaa05906f032ff66911c42867557fdd11
(cherry picked from commit af0390c27b)
2026-03-18 18:01:34 +00:00
Yureka a10fd676f6 lix-doc: remove rust_dynamic_args
This causes a build error with lto, and according to Jade is not
strictly needed anymore.

Change-Id: I41e53a57f40711061effe08f78545011a4b51754
(cherry picked from commit 96db7c79cf)
2026-03-18 17:43:01 +00:00
blokykandeldritch horrors bc506e4f67 docs: fix indent of builtins and nix.conf descriptions in manual
The manual for the builtins and nix.conf currently has inconsistent
indentation, which causes some of the descriptions to end up being
partially treatedas code blocks in markdown (and thus the manual).

This was simply caused by the template string for the docs having
too much indentation before the description is inserted, so this fixes
that 16-bytes mistake.

Change-Id: Ia264e3b1abb20430109029d07a2d2b0a1a726bd4
(cherry picked from commit 8294cd534b)
2026-03-18 16:43:49 +00:00
eldritch horrors f156a27cf4 fix the static build
- launch-builder-linux.cc was missing an include for musl
  and used function that are not defined in the launchers
- musl caches pids used for raise, breaking sandbox setup
- the mtls contrib plugin won't build, didn't try fixing,
  static builds can't really use plugins reliably anyway.

Change-Id: I5ab1664e45ea977e5bcf05e41d825e6014e62146
(cherry picked from commit 022e43aa7f)
2026-03-17 13:43:50 +00:00
Linus Heckemannandeldritch horrors 7166c00efc libexpr: print flake config warning to stderr
Fixes #1155

Change-Id: Ie63f9200f7c06b1eec6c52518d6f523f6a6a6964
(cherry picked from commit f87d753987)
2026-03-17 12:48:02 +00:00
103 changed files with 1249 additions and 2443 deletions
+6 -8
View File
@@ -57,10 +57,6 @@ blitz:
display_name: Julian Stecklina
github: blitz
blokyk:
display_name: blokyk
github: blokyk
cole-h:
display_name: Cole Helbling
github: cole-h
@@ -77,6 +73,9 @@ detroyejr:
display_name: Jonathan De Troye
github: detroyejr
edef:
github: edef1c
edolstra:
display_name: Eelco Dolstra
github: edolstra
@@ -253,13 +252,12 @@ rootile:
display_name: rootile (Rutile)
forgejo: rootile
sandydoo:
github: sandydoo
seppel3210:
github: Seppel3210
sterni:
forgejo: sterni
github: sternenseemann
stevalkr:
github: stevalkr
-10
View File
@@ -1,10 +0,0 @@
---
synopsis: "allow setting nested attributes via `--arg`/`--argstr`"
cls: [5338]
category: "Features"
credits: [ma27]
issues: [fj#496]
---
Passing `--arg config.allowUnfree true` to e.g. `nix-build` now results in `config` with value
`{ allowUnfree = true; }` passed to the expression.
-10
View File
@@ -1,10 +0,0 @@
---
synopsis: "libexpr: allow empty attr-names in parseAttrPath if they are quoted"
cls: [5375]
category: "Miscellany"
credits: [ma27]
---
Empty strings are now allowed in attribute paths as consumed by e.g. `nix-build`.
I.e. `nix-build -A 'foo."".bar'` works now.
The quotes are necessary, i.e. `nix-build -A foo..bar` will throw an error.
-14
View File
@@ -1,14 +0,0 @@
---
synopsis: "builtins.break doesn't break expression anymore"
issues: [1165]
cls: [5422]
category: "Fixes"
credits: [blokyk]
---
Wrapping an expression in `builtins.break` used to break some builtins like
`map` and the `is*` functions, which could modify the execution path of code
inadvertently, made debugging nix harder than it already is, and in some cases
even crashed the interpreter. Now, using `break` should be completely
transparent to whatever function receives it as an input, preventing the
above-mentioned issues.
-9
View File
@@ -1,9 +0,0 @@
---
synopsis: "flake config warnings are now printed to stderr"
issues: [1155]
cls: [5379]
category: "Fixes"
credits: [lheckemann]
---
The settings listed in a flake-config confirmation prompt are now printed to stderr rather than stdout, which allows `nix print-dev-env` to emit valid bash again even in the presence of untrusted settings.
-10
View File
@@ -1,10 +0,0 @@
---
synopsis: "Lix now requires lowdown 1.4.0 or later"
issues: []
cls: [5374]
category: Packaging
credits: [sterni]
---
Support for linking against `lowdown < 1.4.0` has been removed from Lix since
all supported Nixpkgs channels distribute lowdown 2.0.4 or later.
@@ -1,11 +0,0 @@
---
synopsis: "Shadowing internal files through the Nix search path is now an error"
issues: [998]
cls: [4632, 5370]
category: "Breaking Changes"
credits: [thubrecht, jade, horrors]
---
As Lix uses the path `<nix/fetchurl.nix>` for bootstrapping purposes, the ability to shadow it by adding `nix=/some/path` (or `/other/path` that contains a `nix` directory) to the search path is not desirable.
Lix 2.95 deprecated this behavior with a warning, Lix 2.96 now turns it into a hard error if the `nix-path-shadow` deprecated feature isn't enabled. This deprecated feature is slated to be removed in Lix 2.98.
-18
View File
@@ -1,18 +0,0 @@
---
synopsis: "Allow moving between stack frames relative to current debugger frame"
issues: [1156]
cls: [5411]
category: "Improvements"
credits: [blokyk]
---
Debugging functional programs often involve switching between a bunch of stack
frames to get the full context of what's happening and who's calling who.
Before this change, going up or down the stack in the nix debugger with `:st`
meant remembering the absolute index of each stack frame, instead of their
positions relative to one another; this got tiring *fast*.
Now, you can prepend `:st`'s argument with a + or - sign to indicate you want to
move relative to the current stack frame. For example, typing `:st +3` when you
were on frame `10` will go frame `13`; vice-versa, typing `:st -4` on frame `6`
will go to frame `2`.
-14
View File
@@ -1,14 +0,0 @@
---
synopsis: "invalid arguments to :st now print an error"
cls: [5386]
category: "Improvements"
credits: [blokyk]
---
When using the debugger, the `:st` command used to traverse the call stack would
silently fail and put the debugger in an invalid state if the argument given to
it wasn't a valid stack frame index.
This change adds an error message warning the user if the given index wasn't a
valid frame (telling them the range of valid indices), as well as if it wasn't
even a valid integer to begin with.
-6
View File
@@ -177,12 +177,6 @@ Most commands in Lix accept the following command-line options:
You can override this using `--arg`, e.g., `nix-env --install --attr pkgname --arg system \"i686-freebsd\"`.
(Note that since the argument is a Nix string literal, you have to escape the quotes.)
Additionally, dots are interpreted as attribute-path separators.
I.e. `nix-instantiate '<nixpkgs>' -A hello-unfree --arg config.allowUnfree true` will result in an argument `config` with value `{ allowUnfree = true; }` being passed to `<nixpkgs>`.
Please note that merging of different arguments is rejected.
I.e. `--arg config '{ cudaSupport = true; }' --arg config.allowUnfree true` will not work whereas `--arg config.cudaSupport true --arg config.allowUnfree true` is accepted.
- <span id="opt-argstr">[`--argstr`](#opt-argstr)</span> *name* *value*
This option is like `--arg`, only the value is not a Nix expression but a string.
+12 -60
View File
@@ -51,64 +51,28 @@ $ nix-shell -A native-clangStdenvPackages
### Building from the development shell
We have a [justfile](just.systems) for extra convenient building.
It defaults to using `./build` as the build directory, and `$out` (`./outputs/out`) as the install directory.
For most cases, you can clean-build, install, and run the tests with:
```bash
$ just setup --wipe && just test
```
> **Note**
>
> The `--wipe` argument to `meson setup` conveniently works whether you have an existing build directory or not.
>
> However, it is *mostly*, but not *exactly* equivalent to deleting the build directory first.
> In particular, previously specified `-D` build options are **preserved** with `--wipe` (for some reason).
> For example, if you fetch and checkout a new version of Lix, and that new version *removes* a Meson build option from `./meson.options`, *and* a previous invocation in that build directory explicitly set that option, then `meson setup --wipe build` will error, complaining about the unknown option.
> For these cases, `just clean` will give you a well-and-truly-this-time-for-real clean build.
Because the integration tests require installation to work, `just test` automatically also calls `just install`, and Meson helpfully will automatically build any targets that need building when trying to install them.
You can override the build directory or install directory by setting the justfile [variables](https://just.systems/man/en/setting-variables-from-the-command-line.html) `outdir` and `builddir` on the command-line:
```bash
$ just builddir=build-before-bisect outdir=out-before-bisect setup
$ just builddir=build-before-bisect test
```
You'll have to set `builddir` for every target, but `outdir` only needs to be set for `setup`.
Run a clean build and test with `just clean setup build install test`.
You can also run the unit tests and integration tests separately:
```bash
$ just setup
$ just test-unit
$ just test-integration
$ just setup build test-unit
$ just install test-integration
```
Most justfile targets forward all further arguments to the underlying Meson invocation.
Many justfile aliases have a `-custom` variant which pass extra arguments to `meson`.
For example, to work on both Lix and nix-eval-jobs you can run:
```bash
$ just setup -Dnix-eval-jobs=enabled
```
$ just setup-custom -Dnix-eval-jobs=enabled
$ # or
$ mesonFlags=-Dnix-eval-jobs=enabled just setup
```
Note that only targets which *don't* accept extra arguments can have other targets following them.
`just clean setup` is equivalent to `just clean && just setup`, but `just build test` runs the `build` target with the argument `test`.
This means that if you want to, for example, build with lower parallelism, and then test, you will have to do something like this:
```bash
$ just build -j4
$ just test
```
Finally, the rewrite of the integration test suite, functional2, also has its own justfile target which allows passing extra arguments to pytest.
For example, to collect and list all functional2 tests without running them, you can pass pytest's `--collect-only` argument:
```bash
$ just test-functional2 --collect-only
```
Note that only targets which don't accept extra arguments can be used when
running multiple targets at once; `just setup build` is fine, but `just
setup-custom build` is an error. The `test` target is usually the last one to
run, so it always accepts extra arguments.
You can also build Lix manually:
@@ -444,18 +408,6 @@ You can build it yourself:
# xdg-open ./result/coverage/index.html
```
Or, in a dev shell, set `-Dcoverage=true` when running `meson setup`.
Coverage data goes into `build/profraw` when you run executables in the dev shell.
Then, run `ninja -C build coverage-report` to produce an HTML report of coverage in `build/coverage/index.html` alongside a LLVM `.lcov` file.
> [!NOTE]
> We use the [llvm source-based coverage], which has better precision than using clang with gcov, which is debuginfo based (but likely worse performance, which is fine).
>
> It should be noted that Meson [allegedly has coverage support][meson-coverage], but it only supports gcov-style coverage, so we don't use it.
[llvm source-based coverage]: https://clang.llvm.org/docs/SourceBasedCodeCoverage.html
[meson-coverage]: https://mesonbuild.com/Unit-tests.html#coverage
Metrics about the change in line/function coverage over time will be available in the future (FIXME(lix-hydra)).
## Add a release note {#release-notes}
-6
View File
@@ -17,12 +17,6 @@ the attributes of which specify the inputs of the build.
string. This is used as a symbolic name for the package by
`nix-env`, and it is appended to the output paths of the derivation.
> **Note**
>
> Names can only contain alphanumerical characters (0-9, a-z, A-Z)
> as well as `+`, `-`, `.`, `_`, `?` and `=`. Names must be neither
> `.` nor `..`, and must not start with `.-` or `..-`.
- There must be an attribute named [`builder`]{#attr-builder} that identifies the
program that is executed to perform the build. It can be either a
derivation or a source (a local file reference, e.g.,
+1 -1
View File
@@ -5,7 +5,7 @@
FIXME(Lix): This chapter is quite outdated with respect to recommended practices in 2024 and needs updating.
The commands in here will work, however, and the installation section is up to date.
For more updated guidance, see the links on <https://wiki.lix.systems/books/lix-users/page/nix-resources>
For more updated guidance, see the links on <https://lix.systems/resources/>
</div>
+49
View File
@@ -1,4 +1,53 @@
# Lix 2.95 "Kakigōri" (2026-03-13)
# Lix 2.95.2 (2026-05-04)
## Fixes
- Fix unsigned overflow leading to out-of-band write in the NAR parser [cl/5550](https://gerrit.lix.systems/c/lix/+/5550)
The NAR parser contained an unsigned integer overflow that could be used by an
attacker to write arbitrary data to an unknown memory location and possibly
achieve code execution. A successful attack on the system-wide Lix daemon
could lead to privilege escalation to root. Any process that involves NAR
serialization could trigger this issue, including (but not limited to)
- local user interaction, whether the users are trusted or untrusted
- malicious substituters sending malformed NARs
- remote builders sending malformed build results
- remote daemons sending malformed inputs when requesting remote builds
Successful attacks using this bug require ASLR weakening of some sort, whether
by architecture constraints (e.g. on 32 bit systems, where little randomization
is possible) or system configuration (e.g. low ASLR entropy when loading
libraries), and millions of attempts. Local attacks can be mounted in less than
an hour. Remote builds typically require a fresh SSH connection for each build
and are thus less susceptible. Only one attempt can be made by substituters for
every build using substituters, they are thus not a likely vector for attacks.
At the time of writing, MITRE has not assigned this a CVE yet.
Many thanks to [eldritch horrors](https://git.lix.systems/pennae), [Raito Bezarius](https://git.lix.systems/raito), [edef](https://github.com/edef1c), and [sandydoo](https://github.com/sandydoo) for this.
# Lix 2.95.1 (2026-03-19)
## Fixes
- fix static builds [cl/5385](https://gerrit.lix.systems/c/lix/+/5385)
Static builds using musl were broken in 2.95.0 and should work again now.
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
- flake config warnings are now printed to stderr [lix#1155](https://git.lix.systems/lix-project/lix/issues/1155) [cl/5379](https://gerrit.lix.systems/c/lix/+/5379)
The settings listed in a flake-config confirmation prompt are now printed to stderr rather than stdout, which allows `nix print-dev-env` to emit valid bash again even in the presence of untrusted settings.
Many thanks to [lheckemann](https://git.lix.systems/lheckemann) for this.
# Lix 2.95.0 (2026-03-13)
+29 -52
View File
@@ -185,6 +185,9 @@
});
};
# Forward from the previous stage as we dont want it to pick the lowdown override
nixUnstable = prev.nixUnstable;
check-headers = final.buildPackages.callPackage ./maintainers/check-headers.nix { };
check-syscalls = final.buildPackages.callPackage ./maintainers/check-syscalls.nix { };
@@ -249,19 +252,17 @@
# We don't change the default version in order to not change the hash
# of Nix/Lix from upstream Nixpkgs.
lowdown_3_0 =
if (lib.versions.major prev.lowdown.version == "3") then
prev.lowdown
else
prev.lowdown.overrideAttrs (
finalAttrs: _prevAttrs: {
version = "3.0.0";
assert lib.versionOlder prev.lowdown.version "3.0.0";
prev.lowdown.overrideAttrs (
finalAttrs: _prevAttrs: {
version = "3.0.0";
src = final.fetchurl {
url = "https://kristaps.bsd.lv/lowdown/snapshots/lowdown-${finalAttrs.version}.tar.gz";
sha512 = "94e97234d598382c3c3dc27f9bfdb3a3a2fcf7dbb6a8df3c85ee09f27f792449034a41d49d9cfd3d8450d2de01b8562c20c3d120e65c81af4d7d6c9454119e93";
};
}
);
src = final.fetchurl {
url = "https://kristaps.bsd.lv/lowdown/snapshots/lowdown-${finalAttrs.version}.tar.gz";
sha512 = "94e97234d598382c3c3dc27f9bfdb3a3a2fcf7dbb6a8df3c85ee09f27f792449034a41d49d9cfd3d8450d2de01b8562c20c3d120e65c81af4d7d6c9454119e93";
};
}
);
capnproto = prev.capnproto.overrideAttrs (old: {
patches =
@@ -286,57 +287,33 @@
overlays.default = overlayFor (p: p.clangStdenv);
hydraJobs = {
# Aggregate job that is finished in Hydra _after_ all constituent jobs (here: grouped by system)
# succeed.
# This is used to run CD scripts once all builds are finished on Hydra.
release = forAllSystems (
system:
let
pkgs = nixpkgsFor.${system}.native;
in
pkgs.runCommand "release"
{
_hydraAggregate = true;
constituents = lib.filter (x: x != null) (
lib.mapAttrsToListRecursiveCond
(_: val: !(lib.isDerivation val || builtins.any (system': val ? ${system'}) systems))
(
path: drv:
if drv ? ${system} then
lib.concatStringsSep "." (path ++ [ system ])
else if drv.system or null == system then
lib.concatStringsSep "." path
else
null
)
(
removeAttrs self.hydraJobs [
"devShell"
"release"
"rl-next"
]
)
);
}
''
touch $out
''
);
# Binary package for various platforms.
build = forAllSystems (system: self.packages.${system}.nix);
# Ensure support for lowdown < 3.0 doesn't regress for NixOS 25.11
build-lowdown_2_0 = lib.genAttrs [ "aarch64-linux" ] (
build-lowdown_2_0.aarch64-linux = lib.genAttrs [ "aarch64-linux" ] (
system:
assert lib.versionOlder nixpkgsFor.${system}.native.lowdown.version "3.0.0";
self.packages.${system}.nix.override {
lowdown = nixpkgsFor.${system}.native.lowdown;
lowdown-unsandboxed = nixpkgsFor.${system}.native.lowdown-unsandboxed;
}
);
buildStatic = lib.genAttrs linux64BitSystems (system: self.packages.${system}.nix-static);
# Building Lix twice in CI is expensive, but we can catch a lot of static
# build regressions by at least making sure it evals and configures.
configure-static = lib.genAttrs linux64BitSystems (
system:
self.packages.${system}.nix-static.overrideAttrs {
dontBuild = true;
installPhase = ''
runHook preInstall
echo "configure-static complete. exiting with success"
mkdir -p "$out"
exit 0
'';
}
);
devShell = forAllSystems (system: {
default = self.devShells.${system}.default;
+22 -20
View File
@@ -1,10 +1,4 @@
# https://just.systems/man/en/
#
# Take a look at ./doc/manual/src/contributing/hacking.md for a detailed
# explanation on how to use this file!
outdir := x"${out:-$PWD/outputs/out}"
builddir := "build"
# List all available targets
list:
@@ -12,32 +6,40 @@ list:
# Clean build artifacts
clean:
rm -rf {{ builddir }}
rm -rf build
# Prepare meson for building.
setup *OPTIONS:
meson setup {{ builddir }} --reconfigure --prefix="{{outdir}}" $mesonFlags {{ OPTIONS }}
# Prepare meson for building with extra options
setup-custom *OPTIONS:
meson setup build --prefix="$PWD/outputs/out" $mesonFlags {{ OPTIONS }}
# Prepare meson for building
setup: (setup-custom)
# Build lix with extra options
build *OPTIONS:
meson compile -C {{ builddir }} {{ OPTIONS }}
build-custom *OPTIONS:
meson compile -C build {{ OPTIONS }}
# Build lix
build: (build-custom)
alias compile := build
# `meson install` will automatically build anything that needs to be built to install it.
[doc("Install Lix for local development")]
install *OPTIONS:
meson install --quiet -C {{ builddir }} {{ OPTIONS }}
# Install lix for local development with extra options
install-custom *OPTIONS: (build-custom OPTIONS)
meson install -C build
# Run all tests tests (installs first).
test *OPTIONS: (install)
meson test -C {{ builddir }} --print-errorlogs --max-lines 10000 {{ OPTIONS }}
# Install lix for local development
install: (install-custom)
# Run tests (usually requires `install`) with extra options
test *OPTIONS:
meson test -C build --print-errorlogs --max-lines 10000 {{ OPTIONS }}
# Run unit tests only
test-unit *OPTIONS: (test "--suite" "check")
# Run integration tests only
test-integration *OPTIONS: (test "--suite" "installcheck" OPTIONS)
test-integration *OPTIONS: install (test "--suite" "installcheck")
# Run functional2 tests using pytest directly, allowing for additional arguments to be passed to pytest e.g. for more granular test selection
test-functional2 *OPTIONS:
+1 -1
View File
@@ -213,7 +213,7 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
auto autoArgsWithInNixShell = autoArgs;
if (runEnv) {
auto newArgs = evaluator->buildBindings(autoArgsWithInNixShell->size() + 1);
newArgs.insert("inNixShell", {NewValueAs::boolean, true});
newArgs.alloc("inNixShell") = {NewValueAs::boolean, true};
for (auto & i : *autoArgs) newArgs.insert(i);
autoArgsWithInNixShell = newArgs.finish();
}
+3 -2
View File
@@ -154,7 +154,8 @@ static void getAllExprs(Evaluator & state,
Value vArg = {NewValueAs::string, path2.canonical().abs()};
if (seen.size() == maxAttrs)
throw Error("too many Nix expressions in directory '%1%'", path);
attrs.insert(attrName, {NewValueAs::app, state.mem, state.builtins.get("import"), vArg});
attrs.alloc(attrName
) = {NewValueAs::app, state.mem, state.builtins.get("import"), vArg};
}
else if (st.type == InputAccessor::tDirectory)
/* `path2' is a directory (with no default.nix in it);
@@ -179,7 +180,7 @@ static Value loadSourceExpr(EvalState & state, const SourcePath & path_)
directory). */
else if (st.type == InputAccessor::tDirectory) {
auto attrs = state.ctx.buildBindings(maxAttrs);
attrs.insert("_combineChannels", Value::EMPTY_LIST);
attrs.alloc("_combineChannels") = Value::EMPTY_LIST;
StringSet seen;
getAllExprs(state.ctx, path, seen, attrs);
return {NewValueAs::attrs, attrs};
+5 -1
View File
@@ -42,7 +42,11 @@ void processExpr(EvalState & state, const Strings & attrPaths,
NixStringContext context;
if (evalOnly) {
Value vRes = autoArgs.empty() ? v : state.autoCallFunction(autoArgs, v, noPos);
Value vRes;
if (autoArgs.empty())
vRes = v;
else
vRes = state.autoCallFunction(autoArgs, v, noPos);
if (output == okRaw)
std::cout << *state.coerceToString(noPos, vRes, context, "while generating the nix-instantiate output", StringCoercionMode::Strict);
// We intentionally don't output a newline here. The default PS1 for Bash in NixOS starts with a newline
+17 -18
View File
@@ -46,31 +46,30 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
auto attrs = state.ctx.buildBindings(7 + outputs.size());
attrs.insert(state.ctx.symbols.sym_type, {NewValueAs::string, "derivation"});
attrs.insert(state.ctx.symbols.sym_name, {NewValueAs::string, i.queryName(state)});
attrs.alloc(state.ctx.symbols.sym_type) = {NewValueAs::string, "derivation"};
attrs.alloc(state.ctx.symbols.sym_name) = {NewValueAs::string, i.queryName(state)};
auto system = i.querySystem(state);
if (!system.empty())
attrs.insert(state.ctx.symbols.sym_system, {NewValueAs::string, system});
attrs.insert(
state.ctx.symbols.sym_outPath,
{NewValueAs::string, state.ctx.store->printStorePath(i.queryOutPath(state))}
);
attrs.alloc(state.ctx.symbols.sym_system) = {NewValueAs::string, system};
attrs.alloc(state.ctx.symbols.sym_outPath) = {
NewValueAs::string, state.ctx.store->printStorePath(i.queryOutPath(state))
};
if (drvPath)
attrs.insert(
state.ctx.symbols.sym_drvPath, {NewValueAs::string, state.ctx.store->printStorePath(*drvPath)}
);
attrs.alloc(state.ctx.symbols.sym_drvPath) = {
NewValueAs::string, state.ctx.store->printStorePath(*drvPath)
};
// Copy each output meant for installation.
auto & vOutputs = attrs.alloc(state.ctx.symbols.sym_outputs);
auto outputsList = state.ctx.mem.newList(outputs.size());
attrs.insert(state.ctx.symbols.sym_outputs, {NewValueAs::list, outputsList});
vOutputs = {NewValueAs::list, outputsList};
for (const auto & [m, j] : enumerate(outputs)) {
outputsList->elems[m] = {NewValueAs::string, j.first};
auto outputAttrs = state.ctx.buildBindings(2);
outputAttrs.insert(
state.ctx.symbols.sym_outPath,
{NewValueAs::string, state.ctx.store->printStorePath(*j.second)}
);
attrs.insert(j.first, {NewValueAs::attrs, outputAttrs});
outputAttrs.alloc(state.ctx.symbols.sym_outPath) = {
NewValueAs::string, state.ctx.store->printStorePath(*j.second)
};
attrs.alloc(j.first) = {NewValueAs::attrs, outputAttrs};
/* This is only necessary when installing store paths, e.g.,
`nix-env -i /nix/store/abcd...-foo'. */
@@ -88,7 +87,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
meta.insert(state.ctx.symbols.create(j), *v);
}
attrs.insert(state.ctx.symbols.sym_meta, {NewValueAs::attrs, meta});
attrs.alloc(state.ctx.symbols.sym_meta) = {NewValueAs::attrs, meta};
manifest->elems[n++] = {NewValueAs::attrs, attrs};
@@ -112,7 +111,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
/* Construct a Nix expression that calls the user environment
builder with the manifest as argument. */
auto attrs = state.ctx.buildBindings(3);
attrs.insert("manifest", state.ctx.paths.mkStorePathString(manifestFile));
state.ctx.paths.mkStorePathString(manifestFile, attrs.alloc("manifest"));
attrs.insert(state.ctx.symbols.create("derivations"), vManifest);
Value args = {NewValueAs::attrs, attrs};
+39 -87
View File
@@ -1,6 +1,3 @@
#include "libexpr/value.hh"
#include "libutil/strings.hh"
#include "lix/libexpr/attr-path.hh"
#include "lix/libexpr/eval-settings.hh"
#include "lix/libcmd/common-eval-args.hh"
#include "lix/libmain/shared.hh"
@@ -13,10 +10,31 @@
#include "lix/libcmd/command.hh"
#include "lix/libutil/async.hh"
#include "lix/libutil/error.hh"
#include <deque>
#include "lix/libutil/regex.hh"
#include <regex>
namespace nix {
static std::regex const identifierRegex = regex::parse("^[A-Za-z_][A-Za-z0-9_'-]*$");
static void checkValidNixIdentifier(const std::string & name)
{
std::smatch match;
if (!std::regex_match(name, match, identifierRegex)) {
throw UsageError(
"This invocation specifies a value for argument '%s' "
"which isn't a valid Nix identifier. "
"The project is dropping support for this so that it's possible to make e.g. "
"'%s' evaluating to '%s' in the future. "
"If you depend on this behavior, please reach out in "
"<https://git.lix.systems/lix-project/lix/issues/496> so we can discuss your use-case.",
name,
"--arg config.allowUnfree true",
"{ config.allowUnfree = true; }"
);
}
}
MixEvalArgs::MixEvalArgs()
{
addFlag(
@@ -24,7 +42,10 @@ MixEvalArgs::MixEvalArgs()
.description = "Pass the value *expr* as the argument *name* to Nix functions.",
.category = category,
.labels = {"name", "expr"},
.handler = {[&](std::string name, std::string expr) { autoArgs[name] = ExprArgument(expr); }}}
.handler = {[&](std::string name, std::string expr) {
checkValidNixIdentifier(name);
autoArgs[name] = 'E' + expr;
}}}
);
addFlag({
@@ -32,7 +53,10 @@ MixEvalArgs::MixEvalArgs()
.description = "Pass the string *string* as the argument *name* to Nix functions.",
.category = category,
.labels = {"name", "string"},
.handler = {[&](std::string name, std::string s) { autoArgs[name] = StringArgument(s); }},
.handler = {[&](std::string name, std::string s) {
checkValidNixIdentifier(name);
autoArgs[name] = 'S' + s;
}},
});
addFlag({
@@ -155,90 +179,18 @@ MixEvalArgs::MixEvalArgs()
});
}
struct AutoArgsContainer
{
std::map<Symbol, std::variant<Value, AutoArgsContainer>> data;
Bindings * toBindings(Evaluator & state)
{
auto bb = state.buildBindings(data.size());
for (auto & [sym, v] : data) {
bb.insert(
sym,
std::visit(
overloaded{
[&](Value & v) { return v; },
[&](AutoArgsContainer & aac) -> Value {
return {NewValueAs::attrs, aac.toBindings(state)};
}
},
v
)
);
}
return bb.finish();
}
};
static void addAutoArgRecursive(
AutoArgsContainer & container,
Evaluator & state,
std::vector<std::string> && path,
Value & val,
const std::string_view pathStr
)
{
auto * data = &container.data;
auto size = path.size();
for (auto [i, pathCmp] : enumerate(path)) {
auto next = state.symbols.create(pathCmp);
auto entry = data->find(next);
if (entry == data->end()) {
if (i == size - 1) {
(*data)[next] = val;
} else {
(*data)[next] = AutoArgsContainer{};
data = &std::get<AutoArgsContainer>((*data)[next]).data;
}
} else {
std::visit(
overloaded{
[&](Value & v) {
throw Error(
"Cannot set %s via --arg/--argstr when it's the path-extension of another "
"auto-argument!",
pathStr
);
},
[&](AutoArgsContainer & v) { data = &v.data; }
},
entry->second
);
}
}
}
Bindings * MixEvalArgs::getAutoArgs(Evaluator & state)
{
AutoArgsContainer aac;
for (auto & [name, value] : autoArgs) {
Value v = std::visit(
overloaded{
[&](StringArgument & str) -> Value { return {NewValueAs::string, (std::string_view) str.value}; },
[&](ExprArgument & e) -> Value {
return state.evalLazily(state.parseExprFromString(e.expr, CanonPath::fromCwd()));
}
},
value
);
addAutoArgRecursive(aac, state, parseAttrPath(name, false), v, name);
auto res = state.buildBindings(autoArgs.size());
for (auto & i : autoArgs) {
Value v;
if (i.second[0] == 'E')
v = state.evalLazily(state.parseExprFromString(i.second.substr(1), CanonPath::fromCwd()));
else
v = {NewValueAs::string, ((std::string_view) i.second).substr(1)};
res.insert(state.symbols.create(i.first), v);
}
return aac.toBindings(state);
return res.finish();
}
kj::Promise<Result<EvalPaths::PathResult<SourcePath, ThrownError>>>
+1 -10
View File
@@ -14,15 +14,6 @@ class EvalState;
class Bindings;
struct SourcePath;
struct StringArgument
{
std::string value;
};
struct ExprArgument
{
std::string expr;
};
struct MixEvalArgs : virtual Args, virtual MixRepair
{
static constexpr auto category = "Common evaluation options";
@@ -36,7 +27,7 @@ struct MixEvalArgs : virtual Args, virtual MixRepair
std::optional<std::string> evalStoreUrl;
private:
std::map<std::string, std::variant<StringArgument, ExprArgument>> autoArgs;
std::map<std::string, std::string> autoArgs;
};
/** @brief Resolve an argument that is generally a file, but could be something that
+11 -11
View File
@@ -446,18 +446,18 @@ Installables SourceExprCommand::parseInstallables(
throw UsageError("'--file' and '--expr' are exclusive");
auto evaluator = getEvaluator();
Value vFile;
Value vFile = [&](NeverAsync = {}) {
if (file == "-") {
auto & e = evaluator->parseStdin();
return state.eval(e);
} else if (file) {
return state.evalFile(state.aio.blockOn(lookupFileArg(*evaluator, *file)).unwrap());
} else {
auto & e = evaluator->parseExprFromString(*expr, CanonPath::fromCwd());
return state.eval(e);
}
}();
if (file == "-") {
auto & e = evaluator->parseStdin();
vFile = state.eval(e);
}
else if (file)
vFile = state.evalFile(state.aio.blockOn(lookupFileArg(*evaluator, *file)).unwrap());
else {
auto & e = evaluator->parseExprFromString(*expr, CanonPath::fromCwd());
vFile = state.eval(e);
}
for (auto & s : ss) {
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(s);
+8
View File
@@ -55,6 +55,7 @@ std::string renderMarkdownToTerminal(std::string_view markdown, StandardOutputSt
struct lowdown_opts opts{
.type = LOWDOWN_TERM,
#ifdef LOWDOWN_SEPARATE_TERM_OPTS
.term =
{
.cols = lowdown_cols,
@@ -64,7 +65,14 @@ std::string renderMarkdownToTerminal(std::string_view markdown, StandardOutputSt
.vmargin = 0,
.centre = 0,
},
// maxdepth needs to be part of the ifdefs to match declaration order
.maxdepth = 20,
#else
.maxdepth = 20,
.cols = lowdown_cols,
.hmargin = 0,
.vmargin = 0,
#endif /* LOWDOWN_SEPARATE_TERM_OPTS */
.feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES,
#ifdef LOWDOWN_CONSOLIDATED_OFLAGS
.oflags = LOWDOWN_NOLINK,
+22 -103
View File
@@ -1,13 +1,10 @@
#include <algorithm>
#include <cstdio>
#include <editline.h>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <optional>
#include <string_view>
#include "libutil/logging.hh"
#include "lix/libexpr/value.hh"
#include "lix/libutil/box_ptr.hh"
#include "lix/libcmd/repl-interacter.hh"
@@ -714,111 +711,29 @@ void NixRepl::initDebugBuiltinCommands()
addCommand(
"show-trace",
// this command has a bit of nuance to its function and error states.
// it can either:
// 1. be called without any argument
// -> just display the current stack frame (still have to walk up the stack :/)
// 2. be called with an absolute index
// -> try to go to that frame
// -> if it doesn't exist, print an "arg out of range" error
// 3. be called with a relative index
// -> if the final offset is in-bounds, go to that frame
// -> otherwise: clamp the index, i.e. go to 0/$max instead of out-of-bounds
//
// because the collection of frames is lazy and isn't a random-access list,
// we need to iterate the whole stack for most of these if we want to have
// good error messages; this is the biggest reason why this function is so
// long/complex compared to its role
//
[](NixRepl & repl, const std::string & arg) {
auto setTrace = [&](size_t traceIdx, const DebugTrace * trace) {
repl.debugTraceIndex = traceIdx;
std::cout << "\n" << ANSI_BLUE << traceIdx << ANSI_NORMAL << ": ";
showDebugTrace(std::cout, repl.evaluator.positions, *trace);
std::cout << std::endl;
printEnvBindings(repl.state, trace->expr, trace->env);
repl.loadDebugTraceEnv(*trace);
};
try {
repl.debugTraceIndex = stoi(arg);
} catch (...) {
}
// tries to find a trace at a given index.
// - if it is found, it returns the requested trace, along with its
// index, which will be *the same* as requested
// - otherwise, it returns the last (=outermost) trace, along with
// its index, which will be *different* than the one requested
auto tryFindTrace = [&](size_t traceIdx) -> std::pair<size_t, const DebugTrace *> {
size_t lastIndex = 0;
const DebugTrace * lastTrace;
auto traces = repl.evaluator.debug->traces();
for (const auto & [idx, i] : enumerate(traces)) {
lastTrace = i;
lastIndex = idx;
if (idx == traceIdx) {
return std::pair(idx, i);
}
auto traces = repl.evaluator.debug->traces();
for (const auto & [idx, i] : enumerate(traces)) {
if (idx == repl.debugTraceIndex) {
std::cout << "\n" << ANSI_BLUE << idx << ANSI_NORMAL << ": ";
showDebugTrace(std::cout, repl.evaluator.positions, *i);
std::cout << std::endl;
printEnvBindings(repl.state, i->expr, i->env);
repl.loadDebugTraceEnv(*i);
break;
}
return std::pair(lastIndex, lastTrace);
};
bool isRelativeIdx = false;
int requestedTraceIdx;
if (arg.length() == 0) {
// if there's no argument, just re-print the current frame
requestedTraceIdx = repl.debugTraceIndex;
} else {
std::optional<int> maybeIdx = string2Int<int>(arg);
if (!maybeIdx) {
throw Error("argument '%s' is not a valid integer", arg);
}
isRelativeIdx = arg.starts_with('+') || arg.starts_with('-');
requestedTraceIdx =
isRelativeIdx ? maybeIdx.value() + repl.debugTraceIndex : maybeIdx.value();
}
auto [actualTraceIdx, trace] = tryFindTrace((size_t) requestedTraceIdx);
// if we *did* find the frame we wanted originally, all is well
// in the world and we can just load it and exit
if (actualTraceIdx == (size_t) requestedTraceIdx) {
setTrace(actualTraceIdx, trace);
return ProcessLineResult::PromptAgain;
}
// if we couldn't immediately find the requested trace on the "happy path", then either:
// a) it was an absolute index but didn't exist
// -> print a specific error showing the exact valid range
if (!isRelativeIdx) {
throw Error(
"stack index must be between %ld and %ld (inclusive), but was %ld",
0,
actualTraceIdx, // tryFindTrace sets *idx to the final (max) frame index if it fails
requestedTraceIdx
);
}
// b) it was a relative index
// -> clamp the index to the bounds and print a warning
if (requestedTraceIdx < 0) {
// just load frame 0 but print a warning about the bounds
std::tie(actualTraceIdx, trace) = tryFindTrace(0);
setTrace(actualTraceIdx, trace);
printTaggedWarning("stopped at stack frame %ld, cannot go any deeper", 0);
return ProcessLineResult::PromptAgain;
} else {
// (if we're here, then requestedTraceIdx > $max, since tryFindTrace failed)
// load the max frame (that `tryFindFrame` kindly already got for us),
// but print a warning that we can't go any further
setTrace(actualTraceIdx, trace);
printTaggedWarning("stopped at stack frame %ld, cannot go any higher", actualTraceIdx);
return ProcessLineResult::PromptAgain;
}
return ProcessLineResult::PromptAgain;
},
{.aliases = {"st"},
.debugModeOnly = true,
.help = "Show current trace. If an integer is provided, this switches to that stack "
"beforehand. If the integer has an explicit + or - sign, it is treated as"
"relative to the current stack index.",
"beforehand.",
.section = "Debug mode",
.positionalArgsSpecifiers = {{.placeholderText = "integer index", .optional = true}}}
);
@@ -1402,10 +1317,12 @@ void NixRepl::loadFlake(const std::string & flakeRefS)
.kind = ReplLoadKind::Flake,
};
Value v;
try {
loaded.remove(loadable);
loaded.push_back(loadable);
Value v = flake::callFlake(
v = flake::callFlake(
state,
flake::lockFlake(
state,
@@ -1533,6 +1450,7 @@ Value NixRepl::getReplOverlaysEvalFunction()
}
auto evalReplInitFilesPath = CanonPath::root + "repl-overlays.nix";
*replOverlaysEvalFunction = Value{};
auto code =
#include "repl-overlays.nix.gen.hh"
;
@@ -1542,15 +1460,16 @@ Value NixRepl::getReplOverlaysEvalFunction()
evaluator.builtins.staticEnv
);
*replOverlaysEvalFunction = state.eval(expr);
**replOverlaysEvalFunction = state.eval(expr);
return **replOverlaysEvalFunction;
}
Value NixRepl::replOverlays()
{
Value replInits;
auto replInitStorage = evaluator.mem.newList(evalSettings.replOverlays.get().size());
Value replInits = {NewValueAs::list, replInitStorage};
replInits = {NewValueAs::list, replInitStorage};
size_t i = 0;
for (auto path : evalSettings.replOverlays.get()) {
+13 -30
View File
@@ -7,7 +7,8 @@
namespace nix {
std::vector<std::string> parseAttrPath(std::string_view const s, bool allowRhsTrailingDot)
std::vector<std::string> parseAttrPath(std::string_view const s)
{
std::vector<std::string> res;
std::string cur;
@@ -15,22 +16,6 @@ std::vector<std::string> parseAttrPath(std::string_view const s, bool allowRhsTr
auto i = s.begin();
while (i != s.end()) {
if (*i == '.') {
if (!haveData) {
if (res.empty()) {
throw ParseError(
"Leading dot in attribute selection path '%1%' is not allowed! If the attribute name "
"is an empty string, use '\"\".foo.bar'",
s
);
} else {
throw ParseError(
"consecutive dots not allowed in selection path '%1%', use 'foo.\"\".bar' to denote "
"an "
"empty attribute name",
s
);
}
}
res.push_back(cur);
haveData = false;
cur.clear();
@@ -51,11 +36,7 @@ std::vector<std::string> parseAttrPath(std::string_view const s, bool allowRhsTr
}
++i;
}
if (haveData) {
res.push_back(cur);
} else if (!allowRhsTrailingDot) {
throw ParseError("Trailing dot on the right-hand side of path expr '%1%' is not allowed!", s);
};
if (haveData) res.push_back(cur);
return res;
}
@@ -109,6 +90,9 @@ findAlongAttrPath(EvalState & state, const std::string & attrPath, Bindings & au
according to what is specified in the attrPath. */
if (!attrIndex) {
if (attr.empty())
throw Error("empty attribute name in selection path '%1%'", attrPath);
if (v.type() != nAttrs) {
auto pathPart =
std::vector<std::string>(tokens.begin(), tokens.begin() + attrPathIdx);
@@ -179,14 +163,13 @@ findAlongAttrPath(EvalState & state, const std::string & attrPath, Bindings & au
std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what)
{
Value v2 = [&]() {
try {
auto dummyArgs = state.ctx.mem.allocBindings(0);
return findAlongAttrPath(state, "meta.position", *dummyArgs, v).first;
} catch (Error &) {
throw NoPositionInfo("package '%s' has no source location information", what);
}
}();
Value v2;
try {
auto dummyArgs = state.ctx.mem.allocBindings(0);
v2 = findAlongAttrPath(state, "meta.position", *dummyArgs, v).first;
} catch (Error &) {
throw NoPositionInfo("package '%s' has no source location information", what);
}
// FIXME: is it possible to extract the Pos object instead of doing this
// toString + parsing?
+1 -1
View File
@@ -25,7 +25,7 @@ std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v
* Such an attr path is a dot-separated sequence of attribute names, which are possibly quoted.
* No escaping is performed; attribute names containing double quotes are unrepresentable.
*/
std::vector<std::string> parseAttrPath(std::string_view const s, bool allowRhsTrailingDot = true);
std::vector<std::string> parseAttrPath(std::string_view const s);
/**
* Converts an attr path from a list of strings into a string once more.
+11 -2
View File
@@ -23,11 +23,20 @@ Bindings * EvalMemory::allocBindings(size_t capacity)
return new (allocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) Bindings();
}
void BindingsBuilder::insert(std::string_view name, Value value, PosIdx pos)
Value & BindingsBuilder::alloc(Symbol name, PosIdx pos)
{
return insert(symbols.create(name), value, pos);
bindings->push_back(Attr(name, {}, pos));
return (bindings->end() - 1)->value;
}
Value & BindingsBuilder::alloc(std::string_view name, PosIdx pos)
{
return alloc(symbols.create(name), pos);
}
void Bindings::sort()
{
if (size_) std::sort(begin(), end());
+7 -7
View File
@@ -25,8 +25,7 @@ struct Attr
PosIdx pos;
mutable Value value;
Attr(Symbol name, Value value, PosIdx pos = noPos) : name(name), pos(pos), value(value) {}
[[deprecated]]
Attr() {};
Attr() { };
bool operator < (const Attr & a) const
{
return name < a.name;
@@ -73,9 +72,8 @@ public:
const Attr * get(Symbol name)
{
iterator i = std::lower_bound(begin(), end(), name, [](const Attr & value, const Symbol & compare) {
return value.name < compare;
});
Attr key(name, {});
iterator i = std::lower_bound(begin(), end(), key);
if (i != end() && i->name == name) return &*i;
return nullptr;
}
@@ -142,8 +140,6 @@ public:
insert(Attr(name, value, pos));
}
void insert(std::string_view name, Value value, PosIdx pos = noPos);
void insert(const Attr & attr)
{
push_back(attr);
@@ -155,6 +151,10 @@ public:
bindings->push_back(attr);
}
Value & alloc(Symbol name, PosIdx pos = noPos);
Value & alloc(std::string_view name, PosIdx pos = noPos);
[[nodiscard("must use created bindings")]]
Bindings * finish()
{
+1 -2
View File
@@ -10,8 +10,7 @@ present in *args*. All are optional except `path`:
- name\
The name of the path when added to the store. This can used to
reference paths that have
[nix-illegal characters in their names](./derivations.md),
reference paths that have nix-illegal characters in their names,
like `@`.
- filter\
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: scopedImport
implementation: "[](EvalState & state, Value ** args) -> Value { return import(state, *args[1], args[0]); }"
implementation: "[](EvalState & state, Value ** args, Value & v) { import(state, *args[1], args[0], v); }"
args: [scope, path]
renameInGlobalScope: false
---
+65 -49
View File
@@ -162,18 +162,18 @@ Value ExprSet::eval(EvalState & state, Env & env)
* http://github.com/NixOS/nix/issues/7012. Any accesses to the output attrset will thus infrec.
*/
Value vBackup = v;
Symbol nameSym;
Value nameVal;
{
KJ_DEFER(v = vBackup);
v = Value{NewValueAs::blackhole};
Value nameVal = i.nameExpr->eval(state, *dynamicEnv);
nameVal = i.nameExpr->eval(state, *dynamicEnv);
state.forceValue(nameVal, i.pos);
if (nameVal.type() == nNull) {
continue;
}
state.forceStringNoCtx(nameVal, i.pos, "while evaluating the name of a dynamic attribute");
nameSym = state.ctx.symbols.create(nameVal.str());
}
auto nameSym = state.ctx.symbols.create(nameVal.str());
auto j = v.attrs()->get(nameSym);
if (j) {
state.ctx.errors
@@ -187,29 +187,7 @@ Value ExprSet::eval(EvalState & state, Env & env)
.debugThrow();
}
// clang-format off
/* This line is so wrong that it is best kept in here with the documentation why it is wrong,
* lest some naive soul may add it once again some year in the future.
* See the following witness as to why it is wrong:
*
* nix-repl> fun = (name: { ${name} = x: x; }) # This function creates a dynamic attribute with a variable name
* Added fun.
* nix-repl> revSeq = x: y: builtins.seq x (builtins.seq y x) # evaluate x, then y in sequence, then return x
* Added revSeq.
* nix-repl> fun "foo" # The code seemingly works
* { foo = «lambda foo @ «string»:1:26»; }
* nix-repl> fun "bar" #
* { bar = «lambda bar @ «string»:1:26»; }
* nix-repl> revSeq (fun "foo") (fun "bar") # Until it doesn't
* { foo = «lambda bar @ «string»:1:26»; }
*
* What happened? Expressions are AST bound, therefore all lambdas share the same Expr and thus *the same name*.
* Using `setName` here updates the name of *all* lambdas from that expression, not just of the value at hand.
* And this is why all expressions must be treated as immutable after parsing.
*/
/* i.valueExpr->setName(nameSym); */
// clang-format on
i.valueExpr->setName(nameSym);
/* Keep sorted order so find can catch duplicates */
v.attrs()->push_back(Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), i.pos));
v.attrs()->sort(); // FIXME: inefficient
@@ -351,7 +329,45 @@ Value ExprOpUpdate::eval(EvalState & state, Env & env)
Value v2 = e2->eval(state, env);
state.checkAttrs(v2, env, *e2);
return state.updateAttrs(v1, v2);
state.ctx.stats.nrOpUpdates++;
if (v1.attrs()->size() == 0) {
return v2;
}
if (v2.attrs()->size() == 0) {
return v1;
}
auto attrs = state.ctx.buildBindings(v1.attrs()->size() + v2.attrs()->size());
/* Merge the sets, preferring values from the second set. Make
sure to keep the resulting vector in sorted order. */
Bindings::iterator i = v1.attrs()->begin();
Bindings::iterator j = v2.attrs()->begin();
while (i != v1.attrs()->end() && j != v2.attrs()->end()) {
if (i->name == j->name) {
attrs.insert(*j);
++i;
++j;
} else if (i->name < j->name) {
attrs.insert(*i++);
} else {
attrs.insert(*j++);
}
}
while (i != v1.attrs()->end()) {
attrs.insert(*i++);
}
while (j != v2.attrs()->end()) {
attrs.insert(*j++);
}
Value v = {NewValueAs::attrs, attrs.alreadySorted()};
state.ctx.stats.nrOpUpdateValuesCopied += v.attrs()->size();
return v;
}
Value ExprOpConcatLists::eval(EvalState & state, Env & env)
@@ -413,12 +429,12 @@ Value ExprConcatStrings::eval(EvalState & state, Env & env)
};
// List of returned strings. References to these Values must NOT be persisted.
SmallTemporaryValueVector<conservativeStackReservation> values;
values.reserve(es.size());
SmallTemporaryValueVector<conservativeStackReservation> values(es.size());
Value * vTmpP = values.data();
for (auto & [i_pos, i] : es) {
values.push_back(i->eval(state, env));
Value & vTmp = values.back();
Value & vTmp = *vTmpP++;
vTmp = i->eval(state, env);
/* If the first element is a path, then the result will also
be a path, we don't copy anything (yet - that's done later,
@@ -511,7 +527,9 @@ Value ExprConcatStrings::eval(EvalState & state, Env & env)
Value ExprPos::eval(EvalState & state, Env & env)
{
return state.mkPos(pos);
Value v;
state.mkPos(v, pos);
return v;
}
Value ExprBlackHole::eval(EvalState & state, Env & env)
@@ -601,20 +619,19 @@ Value ExprSelect::eval(EvalState & state, Env & env)
// Position for the current selector in this select chain.
PosIdx posCurrentSyntax;
Value baseSelectee = [&]() {
try {
// Evaluate the original thing we're selecting on.
return e->eval(state, env);
} catch (Error & e) {
// clang-format off
e.addTrace(state.ctx.positions[getPos()], HintFmt(
"while evaluating an expression to select '%s' on it",
showAttrPath(state.ctx.symbols, attrPath)
));
// clang-format on
throw;
}
}();
Value baseSelectee;
try {
// Evaluate the original thing we're selecting on.
baseSelectee = e->eval(state, env);
} catch (Error & e) {
// clang-format off
e.addTrace(state.ctx.positions[getPos()], HintFmt(
"while evaluating an expression to select '%s' on it",
showAttrPath(state.ctx.symbols, attrPath)
));
// clang-format on
throw;
}
try {
// With the original selectee evaluated, we'll walk the selection path starting
@@ -700,10 +717,9 @@ Value ExprCall::eval(EvalState & state, Env & env)
// 5: under 10
// This excluded attrset lambdas (`{...}:`). Contributions of mixed lambdas appears insignificant at ~150
// total.
SmallValueVector<4> vArgs;
vArgs.reserve(args.size());
SmallValueVector<4> vArgs(args.size());
for (size_t i = 0; i < args.size(); ++i) {
vArgs.push_back(args[i]->maybeThunk(state, env));
vArgs[i] = args[i]->maybeThunk(state, env);
}
return state.callFunction(vFun, vArgs, pos);
+60 -93
View File
@@ -297,29 +297,30 @@ EvalPaths::EvalPaths(
}
}
#if LIX_MAJOR >= 3 || (LIX_MAJOR == 2 && LIX_MINOR >= 98)
#if LIX_MAJOR >= 3 || (LIX_MAJOR == 2 && LIX_MINOR >= 96)
#warning \
"The feature nix-path-shadow was deprecated in 2.95 with a warning, error in 2.96, and we should consider removing the bypass in 2.98"
"The feature nix-path-shadow was deprecated in 2.95 with a warning, which needs to be turned into an error in 2.96"
#endif
if (!featureSettings.isEnabled(DeprecatedFeature::NixPathShadow)) {
for (auto & [prefix, path] : searchPath_.elements) {
// Match on the 'nix' prefix
if (prefix.s == "nix") {
throw EvalError(HintFmt(
"The prefix '%s' is reserved for internal use by Lix in the Nix search "
"path, its usage is deprecated and will be forbidden in the future.\n"
"Use %s to silence this error.\n"
"This is due to adding '%s=%s' in the Nix search path, either through the "
"environment variable '%s' or by passing the flag %s to the nix "
"invocation.",
"nix",
"--extra-deprecated-features nix-path-shadow",
prefix.s,
path.s,
"NIX_PATH",
"-I"
));
logWarning(
{.msg = HintFmt(
"The prefix '%s' is reserved for internal use by Lix in the Nix search "
"path, its usage is deprecated and will be forbidden in the future.\n"
"Use %s to silence this warning.\n"
"This is due to adding '%s=%s' in the Nix search path, either through the "
"environment variable '%s' or by passing the flag %s to the nix "
"invocation.",
"nix",
"--extra-deprecated-features nix-path-shadow",
prefix.s,
path.s,
"NIX_PATH",
"-I"
)}
);
} else
// Match prefixless paths that contain a `nix` directory
if (auto res =
@@ -332,19 +333,21 @@ EvalPaths::EvalPaths(
});
}))
{
throw EvalError(HintFmt(
"Shadowing '%s' by configuring the nix-path is deprecated and "
"will be forbidden in the future.\n"
"Use %s to silence this error.\n"
"This is due to adding '%s' to the nix-path without a prefix, "
"either by passing the flag '-I %s' to the nix invocation or by "
"adding this path to the environment variable '%s'.",
"<nix/...>",
"--extra-deprecated-features nix-path-shadow",
path.s,
path.s,
"NIX_PATH"
));
logWarning(
{.msg = HintFmt(
"Shadowing '%s' by configuring the nix-path is deprecated and "
"will be forbidden in the future.\n"
"Use %s to silence this warning.\n"
"This is due to adding '%s' to the nix-path without a prefix, "
"either by passing the flag '-I %s' to the nix invocation or by "
"adding this path to the environment variable '%s'.",
"<nix/...>",
"--extra-deprecated-features nix-path-shadow",
path.s,
path.s,
"NIX_PATH"
)}
);
}
}
}
@@ -434,11 +437,11 @@ void EvalPaths::allowPath(const StorePath & storePath)
allowPath(store->toRealPath(storePath));
}
Value EvalPaths::allowAndSetStorePathString(const StorePath & storePath)
void EvalPaths::allowAndSetStorePathString(const StorePath & storePath, Value & v)
{
allowPath(storePath);
return mkStorePathString(storePath);
mkStorePathString(storePath, v);
}
CheckedSourcePath EvalPaths::checkSourcePath(const SourcePath & path_)
@@ -733,13 +736,13 @@ void mapStaticEnvBindings(const SymbolTable & st, const StaticEnv & se, const En
// add 'with' bindings.
Bindings::iterator j = env.values[0].attrs()->begin();
while (j != env.values[0].attrs()->end()) {
vm.insert_or_assign(std::string(st[j->name]), j->value);
vm[std::string(st[j->name])] = j->value;
++j;
}
} else {
// iterate through staticenv bindings and add them.
for (auto & i : se.vars)
vm.insert_or_assign(std::string(st[i.first]), env.values[i.second]);
vm[std::string(st[i.first])] = env.values[i.second];
}
}
}
@@ -851,23 +854,22 @@ Value Evaluator::evalLazily(Expr & e)
return {NewValueAs::thunk, mem, builtins.env, e};
}
Value EvalState::mkPos(PosIdx p)
void EvalState::mkPos(Value & v, PosIdx p)
{
auto origin = ctx.positions.originOf(p);
if (auto path = std::get_if<CheckedSourcePath>(&origin)) {
auto attrs = ctx.buildBindings(3);
attrs.insert(ctx.symbols.sym_file, {NewValueAs::string, path->to_string()});
auto [line, col] = makePositionThunks(*this, p);
attrs.insert(ctx.symbols.sym_line, line);
attrs.insert(ctx.symbols.sym_column, col);
return {NewValueAs::attrs, attrs};
attrs.alloc(ctx.symbols.sym_file) = {NewValueAs::string, path->to_string()};
makePositionThunks(*this, p, attrs.alloc(ctx.symbols.sym_line), attrs.alloc(ctx.symbols.sym_column));
v = {NewValueAs::attrs, attrs};
} else
return Value::VNULL;
v.mkNull();
}
Value EvalPaths::mkStorePathString(const StorePath & p)
void EvalPaths::mkStorePathString(const StorePath & p, Value & v)
{
return {
v = {
NewValueAs::string,
store->printStorePath(p),
NixStringContext{
@@ -883,9 +885,13 @@ std::string EvalState::mkOutputStringRaw(
return ctx.store->printStorePath(staticOutputPath);
}
Value EvalState::mkOutputString(const SingleDerivedPath::Built & b, const StorePath & staticOutputPath)
void EvalState::mkOutputString(
Value & value,
const SingleDerivedPath::Built & b,
const StorePath & staticOutputPath)
{
return {NewValueAs::string, mkOutputStringRaw(staticOutputPath), NixStringContext{b}};
value = {NewValueAs::string, mkOutputStringRaw(staticOutputPath), NixStringContext{b}};
}
@@ -1202,11 +1208,11 @@ Value EvalState::callFunction(Value & fun, std::span<Value> args, const PosIdx p
for (unsigned i = 0; i < argsLeft; i++) {
pargs[i] = &args[i];
}
vCur = fn->fun(*this, pargs.data());
fn->fun(*this, pargs.data(), vCur);
} catch (ThrownError & e) {
// Distinguish between an error that simply happened while "throw"
// was being evaluated and an explicit thrown error.
if (fn->name == "throw" && !e.hasTrace()) {
if (fn->name == "throw") {
e.addTrace(ctx.positions[pos], "caused by explicit %s", "throw");
} else {
e.addTrace(ctx.positions[pos], "while calling the '%s' builtin", fn->name);
@@ -1257,7 +1263,7 @@ Value EvalState::callFunction(Value & fun, std::span<Value> args, const PosIdx p
// 1. Unify this and above code. Heavily redundant.
// 2. Create a fake env (arg1, arg2, etc.) and a fake expr (arg1: arg2: etc: builtins.name arg1 arg2 etc)
// so the debugger allows to inspect the wrong parameters passed to the builtin.
vCur = fn->fun(*this, vArgs.data());
fn->fun(*this, vArgs.data(), vCur);
} catch (Error & e) {
e.addTrace(ctx.positions[pos], "while calling the '%1%' builtin", fn->name);
throw;
@@ -1358,49 +1364,9 @@ https://docs.lix.systems/manual/lix/stable/language/constructs.html#functions)",
return callFunction(fun, vAttrs, pos);
}
Value EvalState::updateAttrs(const Value & v1, const Value & v2)
{
ctx.stats.nrOpUpdates++;
if (v1.attrs()->size() == 0) {
return v2;
}
if (v2.attrs()->size() == 0) {
return v1;
}
auto attrs = ctx.buildBindings(v1.attrs()->size() + v2.attrs()->size());
/* Merge the sets, preferring values from the second set. Make
sure to keep the resulting vector in sorted order. */
Bindings::iterator i = v1.attrs()->begin();
Bindings::iterator j = v2.attrs()->begin();
while (i != v1.attrs()->end() && j != v2.attrs()->end()) {
if (i->name == j->name) {
attrs.insert(*j);
++i;
++j;
} else if (i->name < j->name) {
attrs.insert(*i++);
} else {
attrs.insert(*j++);
}
}
while (i != v1.attrs()->end()) {
attrs.insert(*i++);
}
while (j != v2.attrs()->end()) {
attrs.insert(*j++);
}
Value v = {NewValueAs::attrs, attrs.alreadySorted()};
ctx.stats.nrOpUpdateValuesCopied += v.attrs()->size();
return v;
}
Value EvalState::concatLists(std::span<Value> lists, const PosIdx pos, std::string_view errorCtx)
void EvalState::concatLists(
Value & v, std::span<Value> lists, const PosIdx pos, std::string_view errorCtx
)
{
ctx.stats.nrListConcats++;
@@ -1416,10 +1382,12 @@ Value EvalState::concatLists(std::span<Value> lists, const PosIdx pos, std::stri
}
if (nonEmpty && len == nonEmpty->listSize()) {
return *nonEmpty;
v = *nonEmpty;
return;
}
auto list = ctx.mem.newList(len);
v = {NewValueAs::list, list};
auto out = list->elems;
for (size_t n = 0, pos = 0; n < lists.size(); ++n) {
auto l = lists[n].listSize();
@@ -1428,7 +1396,6 @@ Value EvalState::concatLists(std::span<Value> lists, const PosIdx pos, std::stri
}
pos += l;
}
return {NewValueAs::list, list};
}
// always force this to be separate, otherwise forceValue may inline it and take
+11 -6
View File
@@ -370,7 +370,7 @@ public:
/**
* Allow access to a store path and return it as a string.
*/
Value allowAndSetStorePathString(const StorePath & storePath);
void allowAndSetStorePathString(const StorePath & storePath, Value & v);
/**
* Check whether access to a path is allowed and throw an error if
@@ -449,7 +449,7 @@ public:
* The string is the printed store path with a context containing a
* single `NixStringContextElem::Opaque` element of that store path.
*/
Value mkStorePathString(const StorePath & storePath);
void mkStorePathString(const StorePath & storePath, Value & v);
};
struct EvalStatistics
@@ -800,7 +800,7 @@ public:
*/
Value autoCallFunction(Bindings & args, Value & fun, PosIdx pos);
Value mkPos(PosIdx pos);
void mkPos(Value & v, PosIdx pos);
/**
* Create a string representing a `SingleDerivedPath::Built`.
@@ -809,13 +809,18 @@ public:
* single `NixStringContextElem::Built` element of the drv path and
* output name.
*
* @param value Value we are settings
*
* @param b the drv whose output we are making a string for, and the
* output
*
* @param staticOutputPath Output path for that string.
* Will be printed to form string.
*/
Value mkOutputString(const SingleDerivedPath::Built & b, const StorePath & staticOutputPath);
void mkOutputString(
Value & value,
const SingleDerivedPath::Built & b,
const StorePath & staticOutputPath);
/**
* Create a string representing a `SingleDerivedPath`.
@@ -826,8 +831,8 @@ public:
const SingleDerivedPath & p,
Value & v);
Value updateAttrs(const Value & v1, const Value & v2);
Value concatLists(std::span<Value> lists, const PosIdx pos, std::string_view errorCtx);
void
concatLists(Value & v, std::span<Value> lists, const PosIdx pos, std::string_view errorCtx);
private:
+16 -15
View File
@@ -8,24 +8,25 @@ namespace nix {
class EvalState;
struct Value;
Value prim_addDrvOutputDependencies(EvalState & state, Value ** args);
Value prim_fetchTree(EvalState & state, Value ** args);
Value prim_fetchGit(EvalState & state, Value ** args);
Value prim_fetchMercurial(EvalState & state, Value ** args);
Value prim_fetchTarball(EvalState & state, Value ** args);
Value prim_fetchurl(EvalState & state, Value ** args);
Value prim_fromTOML(EvalState & state, Value ** args);
Value prim_appendContext(EvalState & state, Value ** args);
Value prim_getContext(EvalState & state, Value ** args);
Value prim_hasContext(EvalState & state, Value ** args);
Value prim_unsafeDiscardOutputDependency(EvalState & state, Value ** args);
Value prim_unsafeDiscardStringContext(EvalState & state, Value ** args);
void prim_addDrvOutputDependencies(EvalState & state, Value * * args, Value & v);
void prim_fetchTree(EvalState & state, Value * * args, Value & v);
void prim_fetchGit(EvalState & state, Value * * args, Value & v);
void prim_fetchMercurial(EvalState & state, Value ** args, Value & v);
void prim_fetchTarball(EvalState & state, Value * * args, Value & v);
void prim_fetchurl(EvalState & state, Value * * args, Value & v);
void prim_fromTOML(EvalState & state, Value * * args, Value & v);
void prim_appendContext(EvalState & state, Value ** args, Value & v);
void prim_getContext(EvalState & state, Value * * args, Value & v);
void prim_hasContext(EvalState & state, Value * * args, Value & v);
void prim_unsafeDiscardOutputDependency(EvalState & state, Value * * args, Value & v);
void prim_unsafeDiscardStringContext(EvalState & state, Value ** args, Value & v);
namespace flake {
Value prim_flakeRefToString(EvalState & state, Value ** args);
Value prim_getFlake(EvalState & state, Value ** args);
Value prim_parseFlakeRef(EvalState & state, Value ** args);
void prim_flakeRefToString(EvalState & state, Value * * args, Value & v);
void prim_getFlake(EvalState & state, Value * * args, Value & v);
void prim_parseFlakeRef(EvalState & state, Value * * args, Value & v);
}
}
+25 -16
View File
@@ -942,7 +942,10 @@ LockedFlake lockFlake(
Value callFlake(EvalState & state, const LockedFlake & lockedFlake)
{
Value vLocks = {NewValueAs::string, lockedFlake.lockFile.to_string()};
Value vLocks;
Value vRootSubdir;
vLocks = {NewValueAs::string, lockedFlake.lockFile.to_string()};
Value vRootSrc = emitTreeAttrs(
state.ctx,
@@ -952,13 +955,14 @@ Value callFlake(EvalState & state, const LockedFlake & lockedFlake)
lockedFlake.flake.forceDirty
);
Value vRootSubdir = {NewValueAs::string, lockedFlake.flake.lockedRef.subdir};
vRootSubdir = {NewValueAs::string, lockedFlake.flake.lockedRef.subdir};
if (!state.ctx.caches.vCallFlake) {
state.ctx.caches.vCallFlake = allocRootValue(state.eval(state.ctx.parseExprFromString(
state.ctx.caches.vCallFlake = allocRootValue({});
*state.ctx.caches.vCallFlake = state.eval(state.ctx.parseExprFromString(
#include "call-flake.nix.gen.hh"
, CanonPath::root
)));
));
}
Value vTmp1 = state.callFunction(*state.ctx.caches.vCallFlake, vLocks, noPos);
@@ -966,14 +970,14 @@ Value callFlake(EvalState & state, const LockedFlake & lockedFlake)
return state.callFunction(vTmp2, vRootSubdir, noPos);
}
Value prim_getFlake(EvalState & state, Value ** args)
void prim_getFlake(EvalState & state, Value * * args, Value & v)
{
std::string flakeRefS(state.forceStringNoCtx(*args[0], noPos, "while evaluating the argument passed to builtins.getFlake"));
auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
if (evalSettings.pureEval && !flakeRef.input.isLocked())
throw Error("cannot call 'getFlake' on unlocked flake reference '%s' (use --impure to override)", flakeRefS);
return callFlake(
v = callFlake(
state,
lockFlake(
state,
@@ -988,7 +992,10 @@ Value prim_getFlake(EvalState & state, Value ** args)
);
}
Value prim_parseFlakeRef(EvalState & state, Value ** args)
void prim_parseFlakeRef(
EvalState & state,
Value * * args,
Value & v)
{
std::string flakeRefS(state.forceStringNoCtx(*args[0], noPos,
"while evaluating the argument passed to builtins.parseFlakeRef"));
@@ -996,26 +1003,28 @@ Value prim_parseFlakeRef(EvalState & state, Value ** args)
auto binds = state.ctx.buildBindings(attrs.size());
for (const auto & [key, value] : attrs) {
auto s = state.ctx.symbols.create(key);
Value vv = std::visit(
auto & vv = binds.alloc(s);
std::visit(
overloaded{
[](const std::string & value) -> Value { return {NewValueAs::string, value}; },
[](const uint64_t & value) -> Value { return {NewValueAs::integer, NixInt::Inner(value)}; },
[](const Explicit<bool> & value) -> Value { return {NewValueAs::boolean, value.t}; }
[&vv](const std::string & value) { vv = {NewValueAs::string, value}; },
[&vv](const uint64_t & value) { vv = {NewValueAs::integer, NixInt::Inner(value)}; },
[&vv](const Explicit<bool> & value) { vv = {NewValueAs::boolean, value.t}; }
},
value
);
binds.insert(s, vv);
}
return {NewValueAs::attrs, binds};
v = {NewValueAs::attrs, binds};
}
Value prim_flakeRefToString(EvalState & state, Value ** args)
void prim_flakeRefToString(
EvalState & state,
Value * * args,
Value & v)
{
state.forceAttrs(*args[0], noPos,
"while evaluating the argument passed to builtins.flakeRefToString");
fetchers::Attrs attrs;
for (const auto & attr : *args[0]->attrs()) {
state.forceValue(attr.value, noPos);
auto t = attr.value.type();
if (t == nInt) {
auto intValue = attr.value.integer().value;
@@ -1042,7 +1051,7 @@ Value prim_flakeRefToString(EvalState & state, Value ** args)
}
}
auto flakeRef = FlakeRef::fromAttrs(attrs);
return {NewValueAs::string, flakeRef.to_string()};
v = {NewValueAs::string, flakeRef.to_string()};
}
}
+35 -41
View File
@@ -14,39 +14,24 @@ class JSONSax : nlohmann::json_sax<JSON> {
class JSONState {
protected:
std::unique_ptr<JSONState> parent;
JSONState() = default;
public:
virtual std::unique_ptr<JSONState> resolve(EvalState &) = 0;
explicit JSONState(std::unique_ptr<JSONState> && p) : parent(std::move(p)) {}
JSONState(JSONState & p) = delete;
virtual Value & finalValue()
{
assert(false && "tried to read a final value from a non-toplevel json parser state");
}
virtual ~JSONState() {}
virtual void addValue(Value v) = 0;
};
class TopLevelJSONState : public JSONState
{
RootValue v;
public:
std::unique_ptr<JSONState> resolve(EvalState &) override
virtual std::unique_ptr<JSONState> resolve(EvalState &)
{
assert(false && "tried to close toplevel json parser state");
}
TopLevelJSONState() = default;
TopLevelJSONState(TopLevelJSONState & p) = delete;
Value & finalValue() override
explicit JSONState(std::unique_ptr<JSONState> && p) : parent(std::move(p)) {}
JSONState() = default;
JSONState(JSONState & p) = delete;
Value & value()
{
assert(v && "tried to read nonexistent final value from json parser");
if (!v) {
v = allocRootValue({});
}
return *v;
}
void addValue(Value v) override
{
assert(!this->v && "duplicate value in toplevel JSON scope");
this->v = allocRootValue(v);
}
virtual ~JSONState() {}
virtual void add() {}
};
class JSONObjectState : public JSONState {
@@ -58,12 +43,13 @@ class JSONSax : nlohmann::json_sax<JSON> {
auto attrs2 = state.ctx.buildBindings(attrs.size());
for (auto & i : attrs)
attrs2.insert(i.first, i.second);
parent->addValue({NewValueAs::attrs, attrs2.alreadySorted()});
parent->value() = {NewValueAs::attrs, attrs2.alreadySorted()};
return std::move(parent);
}
void addValue(Value v) override
void add() override
{
attrs.insert_or_assign(_key, v);
attrs.insert_or_assign(_key, value());
v = nullptr;
}
public:
void key(string_t & name, EvalState & state)
@@ -77,15 +63,16 @@ class JSONSax : nlohmann::json_sax<JSON> {
std::unique_ptr<JSONState> resolve(EvalState & state) override
{
auto list = state.ctx.mem.newList(values.size());
parent->addValue({NewValueAs::list, list});
parent->value() = {NewValueAs::list, list};
for (size_t n = 0; n < values.size(); ++n) {
list->elems[n] = values[n];
}
return std::move(parent);
}
void addValue(Value v) override
void add() override
{
values.push_back(v);
values.push_back(*v);
v = nullptr;
}
public:
JSONListState(std::unique_ptr<JSONState> && p, std::size_t reserve) : JSONState(std::move(p))
@@ -98,28 +85,31 @@ class JSONSax : nlohmann::json_sax<JSON> {
std::unique_ptr<JSONState> rs;
public:
JSONSax(EvalState & state) : state(state), rs(new TopLevelJSONState()) {};
JSONSax(EvalState & state) : state(state), rs(new JSONState()) {};
Value result()
{
return rs->finalValue();
return rs->value();
}
bool null() override
{
rs->addValue(Value::VNULL);
rs->value().mkNull();
rs->add();
return true;
}
bool boolean(bool val) override
{
rs->addValue({NewValueAs::boolean, val});
rs->value() = {NewValueAs::boolean, val};
rs->add();
return true;
}
bool number_integer(number_integer_t val) override
{
rs->addValue({NewValueAs::integer, val});
rs->value() = {NewValueAs::integer, val};
rs->add();
return true;
}
@@ -131,19 +121,22 @@ public:
return number_float(static_cast<number_float_t>(val_), "");
}
NixInt::Inner val = val_;
rs->addValue({NewValueAs::integer, val});
rs->value() = {NewValueAs::integer, val};
rs->add();
return true;
}
bool number_float(number_float_t val, const string_t & s) override
{
rs->addValue({NewValueAs::floating, val});
rs->value() = {NewValueAs::floating, val};
rs->add();
return true;
}
bool string(string_t & val) override
{
rs->addValue({NewValueAs::string, val});
rs->value() = {NewValueAs::string, val};
rs->add();
return true;
}
@@ -170,6 +163,7 @@ public:
bool end_object() override {
rs = rs->resolve(state);
rs->add();
return true;
}
@@ -190,13 +184,13 @@ public:
}
};
Value parseJSON(EvalState & state, const std::string_view & s_)
void parseJSON(EvalState & state, const std::string_view & s_, Value & v)
{
JSONSax parser(state);
bool res = JSON::sax_parse(s_, &parser);
if (!res)
throw JSONParseError("Invalid JSON Value");
return parser.result();
v = parser.result();
}
}
+2 -1
View File
@@ -12,5 +12,6 @@ struct Value;
MakeError(JSONParseError, Error);
Value parseJSON(EvalState & state, const std::string_view & s);
void parseJSON(EvalState & state, const std::string_view & s, Value & v);
}
+18 -34
View File
@@ -130,10 +130,6 @@ public:
virtual void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) = 0;
virtual Value eval(EvalState & state, Env & env);
virtual Value maybeThunk(EvalState & state, Env & env);
/* Lambdas have a name associated with them, when they are declared in a binding:
* `identity = x: x` will print the resulting value as `«lambda identity @ «string»:1:14»`.
* This is set in the parser. After parsing, all expressions are immutable.
*/
virtual void setName(Symbol name);
PosIdx getPos() const { return pos; }
@@ -179,7 +175,7 @@ struct ExprLiteral : Expr
{
protected:
Value v;
ExprLiteral(const PosIdx pos, Value v) : Expr(pos), v(v) {};
ExprLiteral(const PosIdx pos) : Expr(pos) {};
public:
Value maybeThunk(EvalState & state, Env & env) override;
JSON toJSON(const SymbolTable & symbols) const override;
@@ -187,46 +183,34 @@ public:
void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) override { ev.visit(*this, ptr); }
};
struct ExprInt : private std::tuple<Value::Int>, ExprLiteral
struct ExprInt : ExprLiteral
{
ExprInt(const PosIdx pos, NixInt n)
: tuple({{Value::Acb::tInt}, n})
, ExprLiteral(
pos,
Value::isTaggableInteger(n) ? Value{NewValueAs::integer, n} : Value(std::get<Value::Int>(*this))
)
Value::Int i;
ExprInt(const PosIdx pos, NixInt n) : ExprLiteral(pos), i{{Value::Acb::tInt}, n}
{
v = Value::isTaggableInteger(n) ? Value{NewValueAs::integer, n} : Value(i);
}
ExprInt(const PosIdx pos, NixInt::Inner n) : ExprInt(pos, NixInt(n)) {}
};
struct ExprFloat : private std::tuple<Value::Float>, ExprLiteral
struct ExprFloat : ExprLiteral
{
Value::Float f;
ExprFloat(const PosIdx pos, NewValueAs::floating_t, double f)
: tuple({{Value::Acb::tFloat}, f})
, ExprLiteral(pos, Value(std::get<Value::Float>(*this)))
: ExprLiteral(pos)
, f{{Value::Acb::tFloat}, f}
{
v = Value(this->f);
}
};
struct ExprStringBase
struct ExprString : ExprLiteral
{
std::unique_ptr<Value::Str, Value::Str::Deleter> contents;
Value::String strcb;
protected:
ExprStringBase(std::string_view s, const char ** context = nullptr)
: contents(Value::Str::copy(s))
, strcb{.content = contents.get(), .context = context}
{
}
};
struct ExprString : private ExprStringBase, ExprLiteral
{
ExprString(const PosIdx pos, std::string s)
: ExprStringBase(s)
, ExprLiteral(pos, Value{NewValueAs::string, &strcb})
Value::String strcb{.content = contents.get(), .context = nullptr};
ExprString(const PosIdx pos, std::string s) : ExprLiteral(pos), contents(Value::Str::copy(s))
{
v = {NewValueAs::string, &strcb};
}
std::string_view str() const
@@ -235,11 +219,11 @@ struct ExprString : private ExprStringBase, ExprLiteral
}
};
struct ExprPath : private ExprStringBase, ExprLiteral
struct ExprPath : ExprLiteral
{
ExprPath(const PosIdx pos, std::string s)
: ExprStringBase(s, Value::String::path)
, ExprLiteral(pos, Value{NewValueAs::path, &strcb})
std::unique_ptr<Value::Str, Value::Str::Deleter> contents;
Value::String strcb{.content = contents.get(), .context = Value::String::path};
ExprPath(const PosIdx pos, std::string s) : ExprLiteral(pos), contents(Value::Str::copy(s))
{
v = Value{NewValueAs::path, &strcb};
}
+334 -348
View File
File diff suppressed because it is too large Load Diff
+4 -3
View File
@@ -48,12 +48,13 @@ public:
/**
* Load a ValueInitializer from a DSO and return whatever it initializes
*/
Value prim_importNative(EvalState & state, Value ** args);
void prim_importNative(EvalState & state, Value * * args, Value & v);
/**
* Execute a program and parse its output
*/
Value prim_exec(EvalState & state, Value ** args);
void prim_exec(EvalState & state, Value * * args, Value & v);
void makePositionThunks(EvalState & state, const PosIdx pos, Value & line, Value & column);
std::tuple<Value, Value> makePositionThunks(EvalState & state, const PosIdx pos);
}
+19 -16
View File
@@ -7,21 +7,22 @@
namespace nix {
Value prim_unsafeDiscardStringContext(EvalState & state, Value ** args)
void prim_unsafeDiscardStringContext(EvalState & state, Value ** args, Value & v)
{
NixStringContext context;
auto s = state.coerceToString(noPos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardStringContext");
return {NewValueAs::string, *s};
v = {NewValueAs::string, *s};
}
Value prim_hasContext(EvalState & state, Value ** args)
void prim_hasContext(EvalState & state, Value * * args, Value & v)
{
NixStringContext context;
state.forceString(*args[0], context, noPos, "while evaluating the argument passed to builtins.hasContext");
return {NewValueAs::boolean, !context.empty()};
v = {NewValueAs::boolean, !context.empty()};
}
Value prim_unsafeDiscardOutputDependency(EvalState & state, Value ** args)
void prim_unsafeDiscardOutputDependency(EvalState & state, Value * * args, Value & v)
{
NixStringContext context;
auto s = state.coerceToString(noPos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardOutputDependency");
@@ -38,10 +39,11 @@ Value prim_unsafeDiscardOutputDependency(EvalState & state, Value ** args)
}
}
return {NewValueAs::string, *s, context2};
v = {NewValueAs::string, *s, context2};
}
Value prim_addDrvOutputDependencies(EvalState & state, Value ** args)
void prim_addDrvOutputDependencies(EvalState & state, Value * * args, Value & v)
{
NixStringContext context;
auto s = state.coerceToString(noPos, *args[0], context, "while evaluating the argument passed to builtins.addDrvOutputDependencies");
@@ -80,7 +82,7 @@ Value prim_addDrvOutputDependencies(EvalState & state, Value ** args)
}, context.begin()->raw) }),
};
return {NewValueAs::string, *s, context2};
v = {NewValueAs::string, *s, context2};
}
@@ -103,7 +105,7 @@ Value prim_addDrvOutputDependencies(EvalState & state, Value ** args)
Note that for a given path any combination of the above attributes
may be present.
*/
Value prim_getContext(EvalState & state, Value ** args)
void prim_getContext(EvalState & state, Value * * args, Value & v)
{
struct ContextInfo {
bool path = false;
@@ -134,19 +136,20 @@ Value prim_getContext(EvalState & state, Value ** args)
for (const auto & info : contextInfos) {
auto infoAttrs = state.ctx.buildBindings(3);
if (info.second.path)
infoAttrs.insert(state.ctx.symbols.sym_path, {NewValueAs::boolean, true});
infoAttrs.alloc(state.ctx.symbols.sym_path) = {NewValueAs::boolean, true};
if (info.second.allOutputs)
infoAttrs.insert(sAllOutputs, {NewValueAs::boolean, true});
infoAttrs.alloc(sAllOutputs) = {NewValueAs::boolean, true};
if (!info.second.outputs.empty()) {
auto & outputsVal = infoAttrs.alloc(state.ctx.symbols.sym_outputs);
auto content = state.ctx.mem.newList(info.second.outputs.size());
infoAttrs.insert(state.ctx.symbols.sym_outputs, {NewValueAs::list, content});
outputsVal = {NewValueAs::list, content};
for (const auto & [i, output] : enumerate(info.second.outputs))
content->elems[i] = {NewValueAs::string, output};
}
attrs.insert(state.ctx.store->printStorePath(info.first), {NewValueAs::attrs, infoAttrs});
attrs.alloc(state.ctx.store->printStorePath(info.first)) = {NewValueAs::attrs, infoAttrs};
}
return {NewValueAs::attrs, attrs};
v = {NewValueAs::attrs, attrs};
}
@@ -155,7 +158,7 @@ Value prim_getContext(EvalState & state, Value ** args)
See the commentary above unsafeGetContext for details of the
context representation.
*/
Value prim_appendContext(EvalState & state, Value ** args)
void prim_appendContext(EvalState & state, Value ** args, Value & v)
{
NixStringContext context;
auto orig = state.forceString(*args[0], context, noPos, "while evaluating the first argument passed to builtins.appendContext");
@@ -229,6 +232,6 @@ Value prim_appendContext(EvalState & state, Value ** args)
}
}
return {NewValueAs::string, orig, context};
v = {NewValueAs::string, orig, context};
}
}
+7 -7
View File
@@ -5,7 +5,7 @@
namespace nix {
Value prim_fetchMercurial(EvalState & state, Value ** args)
void prim_fetchMercurial(EvalState & state, Value ** args, Value & v)
{
std::string url;
std::optional<Hash> rev;
@@ -88,18 +88,18 @@ Value prim_fetchMercurial(EvalState & state, Value ** args)
auto [tree, input2] = state.aio.blockOn(input.fetch(state.ctx.store));
auto attrs2 = state.ctx.buildBindings(8);
attrs2.insert(state.ctx.symbols.sym_outPath, state.ctx.paths.mkStorePathString(tree.storePath));
state.ctx.paths.mkStorePathString(tree.storePath, attrs2.alloc(state.ctx.symbols.sym_outPath));
if (input2.getRef())
attrs2.insert("branch", {NewValueAs::string, *input2.getRef()});
attrs2.alloc("branch") = {NewValueAs::string, *input2.getRef()};
// Backward compatibility: set 'rev' to
// 0000000000000000000000000000000000000000 for a dirty tree.
auto rev2 = input2.getRev().value_or(Hash(HashType::SHA1));
attrs2.insert("rev", {NewValueAs::string, rev2.gitRev()});
attrs2.insert("shortRev", {NewValueAs::string, rev2.gitRev().substr(0, 12)});
attrs2.alloc("rev") = {NewValueAs::string, rev2.gitRev()};
attrs2.alloc("shortRev") = {NewValueAs::string, rev2.gitRev().substr(0, 12)};
if (auto revCount = input2.getRevCount())
attrs2.insert("revCount", {NewValueAs::integer, NixInt::Inner(*revCount)});
attrs2.alloc("revCount") = {NewValueAs::integer, NixInt::Inner(*revCount)};
v = {NewValueAs::attrs, attrs2};
state.ctx.paths.allowPath(tree.storePath);
return {NewValueAs::attrs, attrs2};
}
}
+37 -41
View File
@@ -26,51 +26,49 @@ Value emitTreeAttrs(
auto attrs = state.buildBindings(10);
attrs.insert(state.symbols.sym_outPath, state.paths.mkStorePathString(tree.storePath));
state.paths.mkStorePathString(tree.storePath, attrs.alloc(state.symbols.sym_outPath));
// FIXME: support arbitrary input attributes.
auto narHash = input.getNarHash();
assert(narHash);
attrs.insert("narHash", {NewValueAs::string, narHash->to_string()});
attrs.alloc("narHash") = {NewValueAs::string, narHash->to_string()};
if (input.getType() == "git")
attrs.insert(
"submodules",
{NewValueAs::boolean, fetchers::maybeGetBoolAttr(input.attrs, "submodules").value_or(false)}
);
attrs.alloc("submodules") = {
NewValueAs::boolean, fetchers::maybeGetBoolAttr(input.attrs, "submodules").value_or(false)
};
if (!forceDirty) {
if (auto rev = input.getRev()) {
attrs.insert("rev", {NewValueAs::string, rev->gitRev()});
attrs.insert("shortRev", {NewValueAs::string, rev->gitShortRev()});
attrs.alloc("rev") = {NewValueAs::string, rev->gitRev()};
attrs.alloc("shortRev") = {NewValueAs::string, rev->gitShortRev()};
} else if (emptyRevFallback) {
// Backwards compat for `builtins.fetchGit`: dirty repos return an empty sha1 as rev
auto emptyHash = Hash(HashType::SHA1);
attrs.insert("rev", {NewValueAs::string, emptyHash.gitRev()});
attrs.insert("shortRev", {NewValueAs::string, emptyHash.gitShortRev()});
attrs.alloc("rev") = {NewValueAs::string, emptyHash.gitRev()};
attrs.alloc("shortRev") = {NewValueAs::string, emptyHash.gitShortRev()};
}
if (auto revCount = input.getRevCount())
attrs.insert("revCount", {NewValueAs::integer, NixInt::Inner(*revCount)});
attrs.alloc("revCount") = {NewValueAs::integer, NixInt::Inner(*revCount)};
else if (emptyRevFallback)
attrs.insert("revCount", {NewValueAs::integer, 0});
attrs.alloc("revCount") = {NewValueAs::integer, 0};
}
if (auto dirtyRev = fetchers::maybeGetStrAttr(input.attrs, "dirtyRev")) {
attrs.insert("dirtyRev", {NewValueAs::string, *dirtyRev});
attrs.insert(
"dirtyShortRev", {NewValueAs::string, *fetchers::maybeGetStrAttr(input.attrs, "dirtyShortRev")}
);
attrs.alloc("dirtyRev") = {NewValueAs::string, *dirtyRev};
attrs.alloc("dirtyShortRev") = {
NewValueAs::string, *fetchers::maybeGetStrAttr(input.attrs, "dirtyShortRev")
};
}
if (auto lastModified = input.getLastModified()) {
attrs.insert("lastModified", {NewValueAs::integer, *lastModified});
attrs.insert(
"lastModifiedDate",
{NewValueAs::string, fmt("%s", std::put_time(std::gmtime(&*lastModified), "%Y%m%d%H%M%S"))}
);
attrs.alloc("lastModified") = {NewValueAs::integer, *lastModified};
attrs.alloc("lastModifiedDate") = {
NewValueAs::string, fmt("%s", std::put_time(std::gmtime(&*lastModified), "%Y%m%d%H%M%S"))
};
}
return {NewValueAs::attrs, attrs};
@@ -108,14 +106,14 @@ struct FetchTreeParams {
bool allowNameArgument = false;
};
static Value fetchTree(
static void fetchTree(
EvalState & state,
const PosIdx pos,
Value ** args,
Value * * args,
Value & v,
std::optional<std::string> type,
const FetchTreeParams & params = FetchTreeParams{}
)
{
) {
fetchers::Input input;
NixStringContext context;
@@ -225,17 +223,16 @@ static Value fetchTree(
state.ctx.paths.allowPath(tree.storePath);
return emitTreeAttrs(state.ctx, tree, input2, params.emptyRevFallback, false);
v = emitTreeAttrs(state.ctx, tree, input2, params.emptyRevFallback, false);
}
Value prim_fetchTree(EvalState & state, Value ** args)
void prim_fetchTree(EvalState & state, Value * * args, Value & v)
{
return fetchTree(state, noPos, args, std::nullopt, FetchTreeParams{.allowNameArgument = false});
fetchTree(state, noPos, args, v, std::nullopt, FetchTreeParams { .allowNameArgument = false });
}
static Value fetch(
EvalState & state, const PosIdx pos, Value ** args, const std::string & who, bool unpack, std::string name
)
static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v,
const std::string & who, bool unpack, std::string name)
{
std::optional<std::string> url;
std::optional<Hash> expectedHash;
@@ -300,7 +297,8 @@ static Value fetch(
});
if (state.aio.blockOn(state.ctx.store->isValidPath(expectedPath))) {
return state.ctx.paths.allowAndSetStorePathString(expectedPath);
state.ctx.paths.allowAndSetStorePathString(expectedPath, v);
return;
}
}
@@ -331,24 +329,22 @@ static Value fetch(
}
}
return state.ctx.paths.allowAndSetStorePathString(storePath);
state.ctx.paths.allowAndSetStorePathString(storePath, v);
}
Value prim_fetchurl(EvalState & state, Value ** args)
void prim_fetchurl(EvalState & state, Value * * args, Value & v)
{
return fetch(state, noPos, args, "fetchurl", false, "");
fetch(state, noPos, args, v, "fetchurl", false, "");
}
Value prim_fetchTarball(EvalState & state, Value ** args)
void prim_fetchTarball(EvalState & state, Value * * args, Value & v)
{
return fetch(state, noPos, args, "fetchTarball", true, "source");
fetch(state, noPos, args, v, "fetchTarball", true, "source");
}
Value prim_fetchGit(EvalState & state, Value ** args)
void prim_fetchGit(EvalState & state, Value * * args, Value & v)
{
return fetchTree(
state, noPos, args, "git", FetchTreeParams{.emptyRevFallback = true, .allowNameArgument = true}
);
fetchTree(state, noPos, args, v, "git", FetchTreeParams { .emptyRevFallback = true, .allowNameArgument = true });
}
}
+24 -15
View File
@@ -7,7 +7,7 @@
namespace nix {
Value prim_fromTOML(EvalState & state, Value ** args)
void prim_fromTOML(EvalState & state, Value ** args, Value & val)
{
auto toml = state.forceStringNoCtx(
*args[0], noPos, "while evaluating the argument passed to builtins.fromTOML"
@@ -15,53 +15,62 @@ Value prim_fromTOML(EvalState & state, Value ** args)
std::istringstream tomlStream(std::string{toml});
auto visit = [&](this const auto & self, toml::value t) -> Value {
auto visit = [&](this const auto & self, Value & v, toml::value t) -> void {
switch (t.type()) {
case toml::value_t::table: {
auto table = toml::get<toml::table>(t);
auto attrs = state.ctx.buildBindings(table.size());
for (auto & elem : table) {
attrs.insert(elem.first, self(elem.second));
self(attrs.alloc(elem.first), elem.second);
}
return {NewValueAs::attrs, attrs};
}
v = {NewValueAs::attrs, attrs};
} break;
case toml::value_t::array: {
auto array = toml::get<std::vector<toml::value>>(t);
size_t size = array.size();
auto list = state.ctx.mem.newList(size);
v = {NewValueAs::list, list};
for (size_t i = 0; i < size; ++i) {
list->elems[i] = self(array[i]);
self(list->elems[i], array[i]);
}
return {NewValueAs::list, list};
}
} break;
case toml::value_t::boolean:
return {NewValueAs::boolean, toml::get<bool>(t)};
v = {NewValueAs::boolean, toml::get<bool>(t)};
break;
case toml::value_t::integer:
return {NewValueAs::integer, toml::get<int64_t>(t)};
v = {NewValueAs::integer, toml::get<int64_t>(t)};
break;
case toml::value_t::floating:
return {NewValueAs::floating, toml::get<NixFloat>(t)};
v = {NewValueAs::floating, toml::get<NixFloat>(t)};
break;
case toml::value_t::string:
return {NewValueAs::string, toml::get<std::string>(t)};
v = {NewValueAs::string, toml::get<std::string>(t)};
break;
case toml::value_t::local_datetime:
case toml::value_t::offset_datetime:
case toml::value_t::local_date:
case toml::value_t::local_time:
// NOLINTNEXTLINE(lix-foreign-exceptions)
throw std::runtime_error("Dates and times are not supported");
break;
case toml::value_t::empty:
return Value::VNULL;
v.mkNull();
break;
}
};
try {
return visit(
visit(
val,
toml::parse(
tomlStream,
"fromTOML", /* the "filename" */
toml::spec::v(1, 0, 0) // Be explicit that we are parsing TOML 1.0.0 without extensions
toml::spec::v(
1, 0, 0
) // Be explicit that we are parsing TOML 1.0.0 without extensions
)
);
} catch (std::exception & e) { // NOLINT(lix-foreign-exceptions) // TODO: toml::syntax_error
+3 -3
View File
@@ -17,10 +17,10 @@ Each file is called with three arguments:
except that it's available in
[`pure-eval`](@docroot@/command-ref/conf-file.html#conf-pure-eval)
mode).
2. The final top-level bindings produced by calling all
`repl-overlays`.
3. The top-level bindings produced by the previous `repl-overlays`
2. The top-level bindings produced by the previous `repl-overlays`
value (or the default top-level bindings).
3. The final top-level bindings produced by calling all
`repl-overlays`.
For example, the following file would alias `pkgs` to
`legacyPackages.${info.currentSystem}` (if that attribute is defined):
-2
View File
@@ -12,10 +12,8 @@ namespace nix
static const Value::List emptyListData{.size = 0};
Value Value::EMPTY_LIST{Value::list_t{}, &emptyListData};
Value Value::EMPTY_SET{attrs_t{}, &Bindings::EMPTY};
const Value::Null Value::NULL_ACB = {{Value::Acb::tNull}};
Value Value::VNULL{null_t{}};
static_assert(alignof(Value::String) >= Value::TAG_ALIGN);
static_assert(alignof(Bindings) >= Value::TAG_ALIGN);
+11 -3
View File
@@ -32,7 +32,7 @@ struct Value;
/**
* Function that implements a primop.
*/
using PrimOpImpl = Value(EvalState & state, Value ** args);
using PrimOpImpl = void(EvalState & state, Value ** args, Value & v);
/**
* Info about a primitive operation, and its implementation
@@ -356,8 +356,6 @@ public:
* Empty list constant.
*/
static Value EMPTY_LIST;
static Value EMPTY_SET;
static Value VNULL;
struct String;
struct Acb;
@@ -766,6 +764,16 @@ public:
*/
inline ValueType type(bool invalidIsThunk = false) const;
inline void mkNull()
{
*this = {NewValueAs::null};
}
inline void mkExternal(ExternalValueBase * e)
{
*this = {NewValueAs::external, *e};
}
bool isList() const
{
return internalType() == tList;
+1 -1
View File
@@ -384,7 +384,7 @@ struct Parser
buffer.clear(); \
std::move(str); \
})
#define READ_STRING() READ_STRING_LIMITED(std::numeric_limits<size_t>::max())
#define READ_STRING() READ_STRING_LIMITED(1048576)
#define READ_PADDING(size) \
do { \
if ((size) % 8) { \
+1 -1
View File
@@ -37,7 +37,7 @@ private:
}
public:
ChunkedVector(uint32_t reserve = 1)
ChunkedVector(uint32_t reserve)
{
chunks.reserve(reserve);
addChunk();
+2 -10
View File
@@ -39,13 +39,13 @@ private:
}
public:
using typename base::const_iterator, typename base::const_reverse_iterator, typename base::value_type;
using typename base::const_iterator, typename base::value_type;
LinearMap() = default;
LinearMap(size_t expectedSize)
{
reserve(expectedSize);
}
using base::size, base::reserve, base::clear, base::cbegin, base::cend, base::crbegin, base::crend;
using base::size, base::reserve, base::clear, base::cbegin, base::cend;
/* Insert an element at the correct position, shifting later elements back by
* one place. Returns `true` if a previous element with that key was
@@ -148,13 +148,5 @@ public:
{
return cend();
}
const_reverse_iterator rbegin() const
{
return crbegin();
}
const_reverse_iterator rend() const
{
return crend();
}
};
} // namespace nix
-65
View File
@@ -83,69 +83,4 @@ On startup, it loads the Nix expressions named *files* and adds them
into the lexical scope. You can load addition files using the `:l
<filename>` command, or reload all files using `:r`.
# Adding default variables in REPL sessions
It is possible to automatically load variables from a list of files
into each new REPL session using the
[`repl-overlays`](@docroot@/command-ref/conf-file.html#conf-repl-overlays)
configuration option.
Each file should contain a Nix function taking three taking and
returning an
[attribute set](@docroot@/language/values.html#attribute-set).
These three arguments are:
1. An [attribute set](@docroot@/language/values.html#attribute-set)
containing at least a `currentSystem` attribute (this is identical
to
[`builtins.currentSystem`](@docroot@/language/builtin-constants.md#builtins-currentSystem),
except that it's available in
[`pure-eval`](@docroot@/command-ref/conf-file.html#conf-pure-eval)
mode).
2. The final top-level bindings produced by calling all
`repl-overlays`.
3. The top-level bindings produced by the previous `repl-overlays`
value (or the default top-level bindings).
## Examples
* Aliasing `legacyPackages.${currentSystem}` to `pkgs`
A file named `/home/alice/my-overlays.nix` containing the following code
would, if `legacyPackages` exists, add a variable named `pkgs` into
the global REPL scope, which returns the value of
`legacyPackages.${currentSystem}`.
```nix
info: final: prev:
if prev ? legacyPackages
&& prev.legacyPackages ? ${info.currentSystem}
then
{
pkgs = prev.legacyPackages.${info.currentSystem};
}
else
{ }
```
This file can be loaded automatically for every REPL session by
adding it to the value of
[`repl-overlays`](@docroot@/command-ref/conf-file.html#conf-repl-overlays)
inside `nix.conf`. For a single session, it is possible to add it
using `--option repl-overlays /home/alice/my-overlay.nix`:
```console
# nix repl --option repl-overlays /home/alice/my-overlay.nix nixpkgs
nix-repl> pkgs == legacyPackages.${builtins.currentSystem}
true
nix-repl> pkgs.hello
«derivation /nix/store/qdzln99hynf92vrz8sz91hlf1dmb1vdy-hello-2.12.2.drv»
```
See the `nix.conf`
[`repl-overlays`](@docroot@/command-ref/conf-file.html#conf-repl-overlays)
documentation for more information.
)""
+6 -26
View File
@@ -376,7 +376,12 @@ curl = dependency('libcurl', 'curl', required : true, include_type : 'system')
editline = dependency('libeditline', 'editline', version : '>=1.14', required : true, include_type : 'system')
lowdown = dependency('lowdown', version : '>=1.4.0', required : true, include_type : 'system')
lowdown = dependency('lowdown', version : '>=0.9.0', required : true, include_type : 'system')
# TODO(sterni): drop the corresponding #ifdef after NixOS 25.05 is EOL which still distributes lowdown < 1.4.0
if lowdown.version().version_compare('>= 1.4.0')
add_project_arguments('-DLOWDOWN_SEPARATE_TERM_OPTS', language: 'cpp')
endif
# TODO(sterni): drop the corresponding #ifdef after NixOS 25.11 is EOL which still distributes lowdown < 3.0.0
if lowdown.version().version_compare('>= 3.0.0')
@@ -688,11 +693,6 @@ if cxx.get_id() in ['clang', 'gcc']
language : 'cpp',
)
endif
add_project_arguments(
# likewise for rust
'--remap-path-prefix=../lix=lix',
language : 'rust',
)
if is_darwin
fs.copyfile(
@@ -718,24 +718,10 @@ capnpc_wrapper = custom_target(
output : 'capnpc_wrapper',
)
coverage_test_env = {}
coverage = get_option('coverage')
if coverage
if cxx.get_id() != 'clang'
error('-Dcoverage=true is llvm-only')
endif
# not necessarily just for our own tests, so we don't gate on tests being
# enabled
subdir('tests/coverage/args')
endif
subdir('lix')
subdir('scripts')
subdir('misc')
coverage_objects = [nix, liblix_all]
if enable_docs
subdir('doc/manual')
endif
@@ -753,9 +739,3 @@ endif
subdir('meson/clang-tidy')
subproject('nix-eval-jobs', required : enable_nix_eval_jobs)
if coverage
# targets defined by coverage
subdir('tests/coverage')
endif
-4
View File
@@ -108,7 +108,3 @@ option('disable-fibers', type : 'boolean', value : false,
option('builtin-dep-closure', type : 'array',
description : 'dependency closure used for builtin builder sandboxes. the install paths are included automatically.',
)
option('coverage', type : 'boolean',
description : 'Use LLVM\'s source-based coverage while building and testing Lix'
)
+1
View File
@@ -32,6 +32,7 @@
libcpuid,
libseccomp,
libsystemtap,
linuxPackages,
lix-clang-tidy ? null,
llvmPackages,
lsof,
+1 -1
View File
@@ -37,7 +37,7 @@ def setup_creds(env: RelengEnvironment):
key = keys.get_ephemeral_key(env)
$AWS_SECRET_ACCESS_KEY = key.secret_key
$AWS_ACCESS_KEY_ID = key.id
$AWS_DEFAULT_REGION = 'garage'
$AWS_DEFAULT_REGION = env.s3_region
$AWS_ENDPOINT_URL = env.s3_endpoint
+20 -11
View File
@@ -5,11 +5,12 @@ import functools
import subprocess
import dataclasses
S3_HOST = 's3.lix.systems'
S3_HOST = 's3-admin.afnix.fr'
S3_USER = 'lix-releng'
DEFAULT_STORE_URI_BITS = {
'region': 'garage',
'endpoint': 's3.lix.systems',
'region': 'global',
'endpoint': 's3.afnix.fr',
'want-mass-query': 'true',
'write-nar-listing': 'true',
'ls-compression': 'zstd',
@@ -54,7 +55,9 @@ class RelengEnvironment:
git_repo: Callable[[], str]
git_repo_is_gerrit: bool
s3_endpoint: str
s3_region: str
s3_ssh_host: str | None
s3_ssh_user: str | None
docker_targets: list[DockerTarget]
@@ -86,17 +89,19 @@ LOCAL = RelengEnvironment(
git_repo_is_gerrit=False,
docker_targets=[],
s3_endpoint = 'http://localhost:3900',
s3_region = 'garage',
s3_ssh_host = None,
s3_ssh_user = None,
)
STAGING = RelengEnvironment(
name='staging',
colour=functools.partial(sgr, GREEN),
docs_bucket='s3://staging-docs',
cache_bucket='s3://staging-cache',
docs_bucket='s3://docs.staging.lix.systems',
cache_bucket='s3://cache.staging.lix.systems',
cache_store_overlay={'secret-key': 'staging.key'},
releases_bucket='s3://staging-releases',
releases_bucket='s3://releases.staging.lix.systems',
git_repo=lambda: 'ssh://git@git.lix.systems/lix-project/lix-releng-staging',
git_repo_is_gerrit=False,
docker_targets=[
@@ -106,8 +111,10 @@ STAGING = RelengEnvironment(
DockerTarget('ghcr.io/lix-project/lix-releng-staging',
tags=['{version}', '{major}']),
],
s3_endpoint = 'https://s3.lix.systems',
s3_endpoint = 'https://s3.afnix.fr',
s3_region = 'garage',
s3_ssh_host = S3_HOST,
s3_ssh_user = S3_USER,
)
GERRIT_REMOTE_RE = re.compile(r'^ssh://(\w+@)?gerrit.lix.systems:2022/lix$')
@@ -127,13 +134,13 @@ def guess_gerrit_remote():
PROD = RelengEnvironment(
name='production',
colour=functools.partial(sgr, RED),
docs_bucket='s3://docs',
cache_bucket='s3://cache',
docs_bucket='s3://docs.lix.systems',
cache_bucket='s3://cache.lix.systems',
# FIXME: we should decrypt this with age into a tempdir in the future, but
# the issue is how to deal with the recipients file. For now, we should
# just delete it after doing a release.
cache_store_overlay={'secret-key': 'prod.key'},
releases_bucket='s3://releases',
releases_bucket='s3://releases.lix.systems',
git_repo=guess_gerrit_remote,
git_repo_is_gerrit=True,
docker_targets=[
@@ -142,8 +149,10 @@ PROD = RelengEnvironment(
tags=['{version}', '{major}']),
DockerTarget('ghcr.io/lix-project/lix', tags=['{version}', '{major}']),
],
s3_endpoint = 'https://s3.lix.systems',
s3_endpoint = 'https://s3.afnix.fr',
s3_region = 'global',
s3_ssh_host = S3_HOST,
s3_ssh_user = S3_USER,
)
ENVIRONMENTS = {
@@ -1,12 +1,12 @@
# SPDX-FileCopyrightText: 2024 Jade Lovelace
# SPDX-FileCopyrightText: 2026 Yureka Lilian <yureka@cyberchaos.dev>
# SPDX-License-Identifier: MIT
import argparse
import json
import sys
import datetime
import dataclasses
import re
from typing import Any, Literal, Optional
from typing import Any
import requests
import os
import logging
@@ -14,27 +14,34 @@ import logging
log = logging.getLogger(__name__)
log.setLevel(logging.INFO)
fmt = logging.Formatter('{asctime} {levelname} {name}: {message}',
datefmt='%b %d %H:%M:%S',
style='{')
fmt = logging.Formatter(
"{asctime} {levelname} {name}: {message}",
datefmt="%b %d %H:%M:%S",
style="{",
)
if not any(isinstance(h, logging.StreamHandler) for h in log.handlers):
hand = logging.StreamHandler()
hand.setFormatter(fmt)
log.addHandler(hand)
API_BASE = os.environ.get('GARAGE_ADMIN_API_BASE', 'http://localhost:3903')
API_KEY = os.environ['GARAGE_ADMIN_TOKEN']
API_BASE = os.environ.get("GARAGE_ADMIN_API_BASE", "http://localhost:3903")
API_KEY = os.environ["GARAGE_ADMIN_TOKEN"]
BUCKET_REGEX_STR = os.environ.get("BUCKET_REGEX", ".*")
BUCKET_REGEX = re.compile(BUCKET_REGEX_STR)
def api(method, endpoint: str, resp_json=True, **kwargs) -> Any:
log.info('http %s %s', method, endpoint)
if not endpoint.startswith('https'):
log.info("http %s %s", method, endpoint)
if not endpoint.startswith("https"):
endpoint = API_BASE + endpoint
resp = requests.request(method,
endpoint,
headers={'Authorization': f'Bearer {API_KEY}'},
**kwargs)
resp = requests.request(
method,
endpoint,
headers={"Authorization": f"Bearer {API_KEY}"},
**kwargs,
)
resp.raise_for_status()
if resp_json:
return resp.json()
@@ -42,97 +49,64 @@ def api(method, endpoint: str, resp_json=True, **kwargs) -> Any:
return resp
@dataclasses.dataclass
class Key:
name: str
id: str
secret_key: Optional[str] = None
def get_bucket_id(bucket_name: str) -> str:
resp: dict = api(
"GET", "/v2/GetBucketInfo", params={"globalAlias": bucket_name}
)
return resp["id"]
@dataclasses.dataclass
class Bucket:
id: str
def keys() -> list[Key]:
data: list[dict] = api('GET', '/v1/key?list')
return [Key(name=k['name'], id=k['id']) for k in data]
def delete_key(key: Key):
api('DELETE', '/v1/key', resp_json=False, params={'id': key.id})
def create_key(name: str) -> Key:
resp: dict = api('POST', '/v1/key', json={'name': name})
return Key(name=resp['name'],
id=resp['accessKeyId'],
secret_key=resp['secretAccessKey'])
AccessType = Literal['read'] | Literal['write'] | Literal['owner']
def get_bucket(bucket_name: str) -> Bucket:
resp: dict = api('GET', '/v1/bucket', params={'globalAlias': bucket_name})
return Bucket(resp['id'])
def grant(bucket: Bucket, access_types: list[AccessType], key: Key):
access_types_dict = {k: True for k in access_types}
api('POST',
'/v1/bucket/allow',
json={
'bucketId': bucket.id,
'accessKeyId': key.id,
'permissions': access_types_dict,
})
KEY_RE = re.compile(r'^.*ephemeral-(\d{14})$')
DATEFMT = '%Y%m%d%H%M%S'
def expired_keys(older_than: datetime.datetime) -> list[Key]:
ret = []
for key in keys():
if m := KEY_RE.match(key.name):
date = datetime.datetime.strptime(m.group(1), DATEFMT)
date = date.astimezone(datetime.UTC)
print(date)
if date < older_than:
ret.append(key)
return ret
DATEFMT = "%Y%m%d%H%M%S"
def do_new(args):
buckets = [get_bucket(b) for b in args.buckets]
for b in args.buckets:
if not BUCKET_REGEX.match(b):
print(f"Bucket {b} not in allowed buckeds '{BUCKET_REGEX_STR}'")
exit(1)
bucket_ids = [get_bucket_id(b) for b in args.buckets]
def optional(s: str, whether) -> list[str]:
if whether:
return [s]
else:
return []
key_name = args.name + "-" if args.name else ""
expiration = datetime.datetime.now(tz=datetime.UTC) + datetime.timedelta(
seconds=args.age_secs
)
key_name += "ephemeral-" + expiration.strftime(DATEFMT)
access_types: list[AccessType] = optional('read', args.read) + optional(
'write', args.write) + optional('owner', args.owner) # type: ignore
key_resp: dict = api(
"POST",
"/v2/CreateKey",
json={
"name": key_name,
"expiration": expiration.isoformat(),
"neverExpires": False,
},
)
key_name = args.name + '-' if args.name else ''
key_name += "ephemeral-" + (
datetime.datetime.now(tz=datetime.UTC) +
datetime.timedelta(seconds=args.age_secs)).strftime(DATEFMT)
for b in bucket_ids:
api(
"POST",
"/v2/AllowBucketKey",
json={
"accessKeyId": key_resp["accessKeyId"],
"bucketId": b,
"permissions": {
"read": args.read,
"write": args.write,
"owner": args.owner,
},
},
)
k = create_key(key_name)
for b in buckets:
grant(b, access_types, k)
print(json.dumps(dataclasses.asdict(k), indent=2))
def do_clean(args):
older_than = datetime.datetime.now(tz=datetime.UTC)
for key in expired_keys(older_than):
delete_key(key)
print(
json.dumps(
{
"name": key_resp["name"],
"id": key_resp["accessKeyId"],
"secret_key": key_resp["secretAccessKey"],
},
indent=2,
)
)
def main():
@@ -148,28 +122,27 @@ def main():
new = sps.add_parser("new", help="Make an ephemeral key")
new.add_argument("--name", help="Name prefix for the key")
new.add_argument("--read",
action="store_true",
help="Grant read access to buckets")
new.add_argument("--write",
action="store_true",
help="Grant write access to buckets")
new.add_argument("--owner",
action="store_true",
help="Grant owner access to buckets")
new.add_argument("--age-secs",
type=int,
required=True,
help="Maximum key lifetime in seconds")
new.add_argument("buckets", nargs='*', help="Buckets to grant access to")
new.add_argument(
"--read", action="store_true", help="Grant read access to buckets"
)
new.add_argument(
"--write", action="store_true", help="Grant write access to buckets"
)
new.add_argument(
"--owner", action="store_true", help="Grant owner access to buckets"
)
new.add_argument(
"--age-secs",
type=int,
required=True,
help="Maximum key lifetime in seconds",
)
new.add_argument("buckets", nargs="*", help="Buckets to grant access to")
new.set_defaults(cmd=do_new)
clean = sps.add_parser("clean", help="Clean up old keys")
clean.set_defaults(cmd=do_clean)
args = ap.parse_args()
args.cmd(args)
if __name__ == '__main__':
if __name__ == "__main__":
main()
+1 -1
View File
@@ -14,7 +14,7 @@ def get_ephemeral_key(
env.docs_bucket.removeprefix('s3://'),
]
if env.s3_ssh_host is not None:
command = ['ssh', '-l', 'root', env.s3_ssh_host, *command]
command = ['ssh', f'{env.s3_ssh_user}@{env.s3_ssh_host}', *command]
output = subprocess.check_output(command)
d = json.loads(output.decode())
return environment.S3Credentials(name=d['name'],
-36
View File
@@ -1,36 +0,0 @@
# Early initialization of LLVM source-based coverage.
#
# Needs to run before defining any targets.
add_project_arguments(
'-fprofile-instr-generate',
'-fcoverage-mapping',
# TODO: -mllvm -runtime-counter-relocation may fix problems with tests with
# nix run/fmt/etc that execvp, bypassing atexit. need to confirm that's real.
language : 'cpp',
)
# N.B. This is a link argument because it needs to link the LLVM profiling runtime, I believe.
add_project_link_arguments(
'-fprofile-instr-generate',
language: 'cpp',
)
add_project_arguments(
'-Cinstrument-coverage',
language : 'rust',
)
coverage_profraw_dir = meson.project_build_root() / 'profraw'
run_command('mkdir', '-p', coverage_profraw_dir, check : true)
coverage_test_env = {
# TODO: may need %c, but that may only work if you set runtime counter relocation, need to find that out
#
# See: https://clang.llvm.org/docs/SourceBasedCodeCoverage.html#running-the-instrumented-program
# %20m -> use 20 raw profiles and merge at runtime, so that our large number of
# invocations of lix in the test suite don't create unreasonable numbers of
# files. This, I think, also limits the test concurrency.
'LLVM_PROFILE_FILE': coverage_profraw_dir / '%20m.profraw'
}
llvm_profdata = find_program('llvm-profdata', required : true)
llvm_cov = find_program('llvm-cov', required : true)
-135
View File
@@ -1,135 +0,0 @@
"""
Generates a report of code coverage using llvm's line-based coverage tool.
This merges together/indexes all the profraw files: https://clang.llvm.org/docs/SourceBasedCodeCoverage.html#creating-coverage-reports
Then it generates reports.
TODO(review): should this maybe be two separate things? idk!
"""
import sys
from pathlib import Path
from dataclasses import dataclass
import glob
import tempfile
import subprocess
import shlex
import logging
log = logging.getLogger(__name__)
log.setLevel(logging.INFO)
fmt = logging.Formatter(
"{asctime} {levelname} {name}: {message}", datefmt="%b %d %H:%M:%S", style="{"
)
hand = logging.StreamHandler()
hand.setFormatter(fmt)
log.addHandler(hand)
def run(args, *rest, check: bool = True, **kwargs):
# stringify all path values
args = [str(a) for a in args]
logging.info("Run: %s", shlex.join(args))
return subprocess.run(args, *rest, check=check, **kwargs)
@dataclass
class LLVMCovArgs:
llvm_cov: Path
objects: list[str]
source_root: Path
profdata: Path
def to_opts(self) -> list[str]:
args = [self.objects[0]]
for obj in self.objects:
args.extend(["-object", obj])
args.extend([f"-compilation-dir={self.source_root}", f"-instr-profile={self.profdata}"])
return args
def show_html(self, out_dir: Path, *args):
run(
[
self.llvm_cov,
"show",
*self.to_opts(),
"-format=html",
f"-output-dir={out_dir}",
# by default, it doesn't show coverage of particular
# instantiations of template functions, but let's turn it on for
# fun!
"-show-instantiation-summary",
*args,
]
)
def export_lcov(self, out_file: Path, *args):
with out_file.open("w") as h:
run([self.llvm_cov, "export", *self.to_opts(), "-format=lcov"], stdout=h)
def main() -> int:
import argparse
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--profraw-dir", type=Path, help="Directory of the .profraw files to ingest"
)
parser.add_argument(
"--out-dir", type=Path, help="Output directory for reports and intermediates (indexes)"
)
parser.add_argument(
"--source-root", type=Path, help="Source root, given to llvm-cov as -compilation-dir"
)
parser.add_argument("--llvm-cov", type=Path, help="llvm-cov executable")
parser.add_argument("--llvm-profdata", type=Path, help="llvm-profdata executable")
parser.add_argument("objects", nargs="+")
args = parser.parse_args()
profraw_dir: Path = args.profraw_dir
out_dir: Path = args.out_dir
source_root: Path = args.source_root
llvm_profdata: Path = args.llvm_profdata
llvm_cov: Path = args.llvm_cov
objects: list[str] = args.objects
profraw_dir.mkdir(parents=True, exist_ok=True)
out_dir.mkdir(parents=True, exist_ok=True)
all_profraws = sorted(glob.glob(str(profraw_dir / "**/*.profraw"), recursive=True))
with tempfile.TemporaryDirectory() as tmp:
tmp = Path(tmp)
inputs_file = tmp / "inputs"
inputs_file.write_text("\n".join(all_profraws) + "\n")
profdata = out_dir / "merged.profdata"
run(
[
llvm_profdata,
"merge",
# ostensibly faster or smaller according to LLVM docs
"-sparse",
# TODO: '--failure-mode=warn' ?
"-o",
profdata,
f"--input-files={inputs_file}",
]
)
llvm_cov_args = LLVMCovArgs(
llvm_cov=llvm_cov, objects=objects, source_root=source_root, profdata=profdata
)
llvm_cov_args.show_html(out_dir=out_dir)
llvm_cov_args.export_lcov(out_file=out_dir / "coverage.lcov")
return 0
if __name__ == "__main__":
sys.exit(main())
-12
View File
@@ -1,12 +0,0 @@
# LLVM source-based coverage for Lix
run_target('coverage-report',
command : [
python, meson.project_source_root() / 'tests/coverage/coverage-report.py',
'--profraw-dir', coverage_profraw_dir,
'--out-dir', meson.project_build_root() / 'meson-logs/coverage',
'--source-root', meson.project_source_root(),
'--llvm-profdata', llvm_profdata.full_path(),
'--llvm-cov', llvm_cov.full_path(),
coverage_objects,
],
)
+2 -1
View File
@@ -57,6 +57,7 @@ functional_tests_scripts = [
'fetchurl.sh',
'fetchPath.sh',
'fetchTree-file.sh',
'simple.sh',
'referrers.sh',
'substitute-with-invalid-ca.sh',
'signing.sh',
@@ -154,7 +155,7 @@ foreach script : functional_tests_scripts
suite : 'installcheck',
env : {
'MESON_BUILD_ROOT': meson.project_build_root(),
} + coverage_test_env,
},
# some tests take 15+ seconds even on an otherwise idle machine, on a loaded machine
# this can easily drive them to failure. give them more time, 5min rather than 30sec
timeout : 300,
+3 -3
View File
@@ -25,13 +25,13 @@ static void maybeRequireMeowForDlopen() {
meow();
}
static Value prim_anotherNull(EvalState & state, Value ** args)
static void prim_anotherNull (EvalState & state, Value ** args, Value & v)
{
assert(entryCalled);
if (mySettings.settingSet)
return Value::VNULL;
v.mkNull();
else
return {NewValueAs::boolean, false};
v = {NewValueAs::boolean, false};
}
extern "C" void nix_plugin_entry()
@@ -40,7 +40,7 @@
:bt, :backtrace Show trace stack
:c, :continue Go until end of program, exception or builtins.break
:s, :step Go one step
:st, :show-trace [integer index] Show current trace. If an integer is provided, this switches to that stack beforehand. If the integer has an explicit + or - sign, it is treated as relative to the current stack index.
:st, :show-trace [integer index] Show current trace. If an integer is provided, this switches to that stack beforehand.
Flakes commands
@@ -14,56 +14,26 @@ This test ensures that continues don't skip opportunities to enter the debugger.
0: error: breakpoint reached
$TEST_DATA/regression_9917.nix:3:5
2| a = builtins.trace "before inner break" (
3| builtins.break { msg = "hello"; }
| ^
4| );
2| a = builtins.trace "before inner break" (
3| builtins.break { msg = "hello"; }
| ^
4| );
1: while calling a function
$TEST_DATA/regression_9917.nix:3:5
2| a = builtins.trace "before inner break" (
3| builtins.break { msg = "hello"; }
| ^
4| );
2| a = builtins.trace "before inner break" (
3| builtins.break { msg = "hello"; }
| ^
4| );
2: while calling a function
$TEST_DATA/regression_9917.nix:2:7
1| let
2| a = builtins.trace "before inner break" (
| ^
3| builtins.break { msg = "hello"; }
3: while calling a function
$TEST_DATA/regression_9917.nix:6:5
5| b = builtins.trace "before outer break" (
6| builtins.break a
| ^
7| );
4: while calling a function
$TEST_DATA/regression_9917.nix:5:7
4| );
5| b = builtins.trace "before outer break" (
| ^
6| builtins.break a
5: while evaluating a 'let' expression
$TEST_DATA/regression_9917.nix:1:1
1| let
| ^
2| a = builtins.trace "before inner break" (
6: while evaluating the file '$TEST_DATA/regression_9917.nix':
$TEST_DATA/regression_9917.nix:1:1
1| let
| ^
2| a = builtins.trace "before inner break" (
1| let
2| a = builtins.trace "before inner break" (
| ^
3| builtins.break { msg = "hello"; }
nix-repl> :c
@@ -87,22 +87,18 @@ If we :st past the frame in the backtrace with the meow in it, the meow should n
nix-repl> :quit
error:
… while evaluating the file '$TEST_DATA/stack_vars.nix':
… while evaluating b
at $TEST_DATA/stack_vars.nix:9:3:
8| in
9| b
| ^
10|
… while calling the 'trace' builtin
at $TEST_DATA/stack_vars.nix:5:7:
4| );
5| b = builtins.trace "before outer break" (
at $TEST_DATA/stack_vars.nix:2:7:
1| let
2| a = builtins.trace "before inner break" (
| ^
6| let meow = 2; in builtins.break a
3| let meow' = 3; in builtins.break { msg = "hello"; }
(stack trace truncated; use '--show-trace' to show the full trace)
… while calling the 'break' builtin
at $TEST_DATA/stack_vars.nix:3:23:
2| a = builtins.trace "before inner break" (
3| let meow' = 3; in builtins.break { msg = "hello"; }
| ^
4| );
error: breakpoint reached
@@ -1,73 +0,0 @@
@args --debugger
nix-repl> throw "(forever?????????)"
error: (forever?????????)
argument-less :st works fine
nix-repl> :st
0: error: (forever?????????)
«string»:1:1
1| throw "(forever?????????)"
| ^
Env level 0
static:
Env level 1
abort baseNameOf break builtins derivation derivationStrict dirOf false fetchGit fetchMercurial fetchTarball fetchTree fromTOML import isNull map null placeholder removeAttrs scopedImport throw toString true
a non-numeric strings produces an error
nix-repl> :st chat
error: argument 'chat' is not a valid integer
nix-repl> :st bedroom community
error: argument 'bedroom community' is not a valid integer
...even when they start with a digit
nix-repl> :st 6up
error: argument '6up' is not a valid integer
...or when they're floats
nix-repl> :st 4.50
error: argument '4.50' is not a valid integer
an integer outside the range produces an error
nix-repl> :st 23571113171923
error: argument '23571113171923' is not a valid integer
argument-less :st is still at the same index after errors
nix-repl> :st 1
1: while calling a function
«string»:1:1
1| throw "(forever?????????)"
| ^
Env level 0
static:
Env level 1
abort baseNameOf break builtins derivation derivationStrict dirOf false fetchGit fetchMercurial fetchTarball fetchTree fromTOML import isNull map null placeholder removeAttrs scopedImport throw toString true
nix-repl> :st foo
error: argument 'foo' is not a valid integer
nix-repl> :st
1: while calling a function
«string»:1:1
1| throw "(forever?????????)"
| ^
Env level 0
static:
Env level 1
abort baseNameOf break builtins derivation derivationStrict dirOf false fetchGit fetchMercurial fetchTarball fetchTree fromTOML import isNull map null placeholder removeAttrs scopedImport throw toString true
quit
nix-repl> :quit
error: (forever?????????)
@@ -1,110 +0,0 @@
@args --debugger
nix-repl> let f = _: throw "x_x"; x = f 5; in x
error: x_x
frames from 0 up to 4 work fine
nix-repl> :st 0
0: error: x_x
«string»:1:12
1| let f = _: throw "x_x"; x = f 5; in x
| ^
Env level 0
static: _
Env level 1
static: f x
Env level 2
static:
Env level 3
abort baseNameOf break builtins derivation derivationStrict dirOf false fetchGit fetchMercurial fetchTarball fetchTree fromTOML import isNull map null placeholder removeAttrs scopedImport throw toString true
nix-repl> :st 4
4: while evaluating a 'let' expression
«string»:1:1
1| let f = _: throw "x_x"; x = f 5; in x
| ^
Env level 0
static: f x
Env level 1
static:
Env level 2
abort baseNameOf break builtins derivation derivationStrict dirOf false fetchGit fetchMercurial fetchTarball fetchTree fromTOML import isNull map null placeholder removeAttrs scopedImport throw toString true
absolute frames out of bounds print an error
nix-repl> :st 5
error: stack index must be between 0 and 4, but was 5
argument-less :st is still at the same after absolute oob
nix-repl> :st
4: while evaluating a 'let' expression
«string»:1:1
1| let f = _: throw "x_x"; x = f 5; in x
| ^
Env level 0
static: f x
Env level 1
static:
Env level 2
abort baseNameOf break builtins derivation derivationStrict dirOf false fetchGit fetchMercurial fetchTarball fetchTree fromTOML import isNull map null placeholder removeAttrs scopedImport throw toString true
positive relative frames oob clamp to upper bound and print a warning
nix-repl> :st +5
4: while evaluating a 'let' expression
«string»:1:1
1| let f = _: throw "x_x"; x = f 5; in x
| ^
Env level 0
static: f x
Env level 1
static:
Env level 2
abort baseNameOf break builtins derivation derivationStrict dirOf false fetchGit fetchMercurial fetchTarball fetchTree fromTOML import isNull map null placeholder removeAttrs scopedImport throw toString true
warning: stopped at stack frame 4, cannot go any higher
negative relative frames oob clamp to lower bound and print a warning
nix-repl> :st -5
0: error: x_x
«string»:1:12
1| let f = _: throw "x_x"; x = f 5; in x
| ^
Env level 0
static: _
Env level 1
static: f x
Env level 2
static:
Env level 3
abort baseNameOf break builtins derivation derivationStrict dirOf false fetchGit fetchMercurial fetchTarball fetchTree fromTOML import isNull map null placeholder removeAttrs scopedImport throw toString true
warning: stopped at stack frame 0, cannot go any deeper
quit
nix-repl> :quit
error: x_x
@@ -1,64 +0,0 @@
@args --debugger
nix-repl> let f = _: throw "x_x"; x = f 5; in x
error: x_x
absolute indices still work:
nix-repl> :st 1
1: while calling a function
«string»:1:12
1| let f = _: throw "x_x"; x = f 5; in x
| ^
Env level 0
static: _
Env level 1
static: f x
Env level 2
static:
Env level 3
abort baseNameOf break builtins derivation derivationStrict dirOf false fetchGit fetchMercurial fetchTarball fetchTree fromTOML import isNull map null placeholder removeAttrs scopedImport throw toString true
index with + goes up the stack relative to current (1 in this case):
nix-repl> :st +3
4: while evaluating a 'let' expression
«string»:1:1
1| let f = _: throw "x_x"; x = f 5; in x
| ^
Env level 0
static: f x
Env level 1
static:
Env level 2
abort baseNameOf break builtins derivation derivationStrict dirOf false fetchGit fetchMercurial fetchTarball fetchTree fromTOML import isNull map null placeholder removeAttrs scopedImport throw toString true
index with - goes down and is also relative to current (4):
nix-repl> :st -1
3: while calling a function
«string»:1:29
1| let f = _: throw "x_x"; x = f 5; in x
| ^
Env level 0
static: f x
Env level 1
static:
Env level 2
abort baseNameOf break builtins derivation derivationStrict dirOf false fetchGit fetchMercurial fetchTarball fetchTree fromTOML import isNull map null placeholder removeAttrs scopedImport throw toString true
quit
nix-repl> :quit
error: x_x
@@ -36,6 +36,6 @@ test(
env : {
'_NIX_TEST_UNIT_DATA': meson.current_build_dir() / 'data',
'MESON_BUILD_ROOT': meson.project_build_root(),
} + coverage_test_env,
},
suite : 'installcheck',
)
@@ -197,8 +197,5 @@ REPL_TEST(idempotent);
REPL_TEST(debug_frames);
REPL_TEST(debug_ignore_try);
REPL_TEST(debug_ignore_try_defaults);
REPL_TEST(stacktrace_invalid_arg);
REPL_TEST(stacktrace_oob);
REPL_TEST(stacktrace_relative);
}; // namespace nix
+33
View File
@@ -0,0 +1,33 @@
source common.sh
drvPath=$(nix-instantiate simple.nix)
test "$(nix-store -q --binding system "$drvPath")" = "$system"
echo "derivation is $drvPath"
outPath=$(nix-store -rvv "$drvPath")
echo "output path is $outPath"
(! [ -w $outPath ])
text=$(cat "$outPath"/hello)
if test "$text" != "Hello World!"; then exit 1; fi
# Directed delete: $outPath is not reachable from a root, so it should
# be deleteable.
nix-store --delete $outPath
(! [ -e $outPath/hello ])
outPath="$(NIX_REMOTE=local?store=/foo\&real=$TEST_ROOT/real-store nix-instantiate --readonly-mode hash-check.nix)"
if test "$outPath" != "/foo/lfy1s6ca46rm5r6w4gg9hc0axiakjcnm-dependencies.drv"; then
echo "hashDerivationModulo appears broken, got $outPath"
exit 1
fi
outPath="$(NIX_REMOTE=local?store=/foo\&real=$TEST_ROOT/real-store nix-instantiate --readonly-mode big-derivation-attr.nix)"
if test "$outPath" != "/foo/xxiwa5zlaajv6xdjynf9yym9g319d6mn-big-derivation-attr.drv"; then
echo "big-derivation-attr.nix hash appears broken, got $outPath. Memory corruption in large drv attr?"
exit 1
fi
+3 -3
View File
@@ -1,7 +1,7 @@
import pytest
import json
from testlib.fixtures.nix import Nix, NixDaemon
from testlib.fixtures.nix import Nix
@pytest.mark.parametrize(
@@ -13,9 +13,9 @@ from testlib.fixtures.nix import Nix, NixDaemon
(["*"], ["--force-untrusted"], False),
],
)
def test_trust(nix: Nix, daemon: NixDaemon, trusted: list[str], flags: list[str], expected: bool):
def test_trust(nix: Nix, trusted: list[str], flags: list[str], expected: bool):
nix.settings.add_xp_feature("nix-command", "daemon-trust-override")
with daemon(nix, flags, settings={"trusted-users": trusted}) as inner:
with nix.daemon(flags, settings={"trusted-users": trusted}) as inner:
trusted = json.loads(inner.nix(["store", "ping", "--json"]).run().ok().stdout)
assert trusted["trusted"] == expected
+3 -6
View File
@@ -23,15 +23,12 @@ ERR_CASES: list[ShouldError] = [
"1",
"""error: the expression selected by the selection path '1' should be a list but is a set: { }""",
),
ShouldError("{}", ".", """error: empty attribute name in selection path '.'"""),
ShouldError(
"{}",
".",
"""error: Leading dot in attribute selection path '.' is not allowed! If the attribute name is an empty string, use '\"\".foo.bar'""",
'{ x."" = 2; }', 'x.""', """error: empty attribute name in selection path 'x.""'"""
),
ShouldError(
"{}",
"bla..blub",
"""error: consecutive dots not allowed in selection path 'bla..blub', use 'foo."".bar' to denote an empty attribute name""",
'{ x."".y = 2; }', 'x."".y', """error: empty attribute name in selection path 'x."".y'"""
),
ShouldError(
"[]", "1", """error: list index 1 in selection path '1' is out of range for list [ ]"""
-110
View File
@@ -1,110 +0,0 @@
from typing import Any
import pytest
from testlib.fixtures.nix import Nix
def do_evaluate(nix: Nix, args: list[str], expect_success: bool = True) -> dict[str, Any] | str:
res = (
nix.nix_instantiate(
[
"--eval",
"--json",
"-E",
"{ arg1, arg2 ? null }: { inherit arg1 arg2; }",
"--strict",
*args,
]
)
.run()
.expect(0 if expect_success else 1)
)
if expect_success:
return res.json()
return res.stderr_s
def test_trivial(nix: Nix):
res = do_evaluate(nix, args=["--arg", "arg1", "[ 1 2 3 ]", "--arg", "arg2", "1"])
assert res["arg1"] == [1, 2, 3]
assert res["arg2"] == 1
def test_recursive(nix: Nix):
res = do_evaluate(
nix,
args=["--arg", "arg1.foo.bar", "1", "--arg", "arg1.foo.baz", "2", "--arg", "arg1.bar", "3"],
)
assert res["arg1"] == {"foo": {"bar": 1, "baz": 2}, "bar": 3}
assert res["arg2"] is None
@pytest.mark.parametrize(
("attribute_path", "expected"),
[("arg1", 2), ("arg1.foo", {"foo": 2}), ("arg1.foo.bar", {"foo": {"bar": 2}})],
)
def test_override(nix: Nix, attribute_path: str, expected: Any):
res = do_evaluate(nix, args=["--arg", attribute_path, "1", "--arg", attribute_path, "2"])
assert res["arg1"] == expected
def test_quoting(nix: Nix):
res = do_evaluate(
nix,
args=[
"--arg",
"arg1.foo.bar",
"1",
"--arg",
'arg1."foo bar baz".baz',
"2",
"--arg",
"arg1.bar",
"2",
],
)
assert res["arg1"] == {"foo": {"bar": 1}, "bar": 2, "foo bar baz": {"baz": 2}}
assert res["arg2"] is None
def test_quoting_error(nix: Nix):
res = do_evaluate(nix, ["--arg", 'arg1."foo bar.baz', "1"], expect_success=False)
assert "error: missing closing quote in selection path 'arg1.\"foo bar.baz'" in res
def test_trailing_dot(nix: Nix):
# This is what `parseAttrPath` from `libutil` does and is consistent with the selection path
# passed to e.g. `nix-build -A`.
res = do_evaluate(nix, args=["--arg", "arg1.bar.", "[ 1 2 3 ]"], expect_success=False)
assert (
"error: Trailing dot on the right-hand side of path expr 'arg1.bar.' is not allowed!" in res
)
@pytest.mark.parametrize(
"args",
[
["--arg", "arg1.foo", "1", "--arg", "arg1.foo.bar", "2"],
["--arg", "arg1.foo.bar", "2", "--arg", "arg1.foo", "1"],
],
)
def test_conflict(nix: Nix, args: list[str]):
res = do_evaluate(nix, args, expect_success=False)
assert (
"error: Cannot set arg1.foo.bar via --arg/--argstr when it's the path-extension of another auto-argument!"
in res
)
@pytest.mark.parametrize("selection", ["foo..bar", "foo.bar.."])
def test_no_empty_items(nix: Nix, selection: str):
res = do_evaluate(nix, ["--arg", selection, "1"], expect_success=False)
assert f"error: consecutive dots not allowed in selection path '{selection}'" in res
-56
View File
@@ -27,59 +27,3 @@ def test_debugger_output(nix: Nix):
assert "error: oh snap" in res.stderr_plain
assert re.findall(r"with: .*a", res.stdout_plain)
assert re.findall(r"static: .*x", res.stdout_plain)
def test_transparent_break(nix: Nix):
"""
Make sure that adding a call to builtins.break doesn't
change the result of an expression
"""
expr = dedent("""
let
inherit (builtins)
attrNames
break
elem
functionArgs
head
isAttrs
isPath
isFunction
map
mapAttrs
removeAttrs
toJSON
typeOf;
in
builtins.all (b: b) [
((attrNames { a = 5; }) == (attrNames (break { a = 5; })))
((elem 5 [1 5]) == (elem 5 (break [1 5])))
((elem (2+3) [1 (2+3)]) == (elem (2+3) (break [1 (2+3)])))
((functionArgs ({ a }: 5)) == (functionArgs (break ({ a }: 5))))
((head [1 2]) == (head (break [1 2])))
((isAttrs { a = 5; }) == (isAttrs (break { a = 5; })))
((isPath ./.) == (isPath (break ./.)))
((isPath ./${".meow"}) == (isPath (break ./${".meow"})))
((isFunction (x: x)) == (isFunction (break (x: x))))
((map (x: x) [1 5]) == (map (x: x) (break [1 5])))
((mapAttrs (n: v: v) { a = 5; }) == (mapAttrs (n: v: v) (break { a = 5; })))
((removeAttrs { a = 5; b = 6; } ["a"]) == (removeAttrs (break { a = 5; b = 6; }) ["a"]))
((removeAttrs { ab = 5; } [("a"+"b")]) == (removeAttrs { ab = 5; } [(break ("a"+"b"))]))
((toJSON { a = 5; }) == (toJSON (break { a = 5; })))
((toJSON { a = [(1+2)]; }) == (toJSON { a = break [(1+2)]; }))
((typeOf { a = 5; }) == (typeOf (break { a = 5; })))
((typeOf (1+2)) == (typeOf (break (1+2))))
]
""")
res_no_dbg = nix.nix(["eval", "--expr", expr], flake=True).run().expect(0)
assert "true" in res_no_dbg.stdout_plain
res_with_dbg = (
nix.nix(["eval", "--debugger", "--expr", expr], flake=True)
.with_stdin(b":c\n" * 50)
.run()
.expect(0)
)
assert "true" in res_with_dbg.stdout_plain
@@ -1,9 +1,14 @@
error:
… while calling the 'seq' builtin
at /pwd/in.nix:1:16:
1| let n = -1; in builtins.seq n (builtins.flakeRefToString {
| ^
2| type = "github";
… while calling the 'flakeRefToString' builtin
at /pwd/in.nix:4:1:
3| in
4| builtins.flakeRefToString {
| ^
5| type = "github";
at /pwd/in.nix:1:32:
1| let n = -1; in builtins.seq n (builtins.flakeRefToString {
| ^
2| type = "github";
error: negative value given for flake ref attr repo: -1
@@ -1,10 +1,7 @@
let
n = -1;
in
builtins.flakeRefToString {
let n = -1; in builtins.seq n (builtins.flakeRefToString {
type = "github";
owner = "NixOS";
repo = n;
ref = "23.05";
dir = "lib";
}
})
@@ -1 +0,0 @@
{ key = "three"; }
@@ -1,8 +0,0 @@
# Duplicate JSON keys should always drop all but the latest value
builtins.fromJSON ''
{
"key": "one",
"key": "two",
"key": "three"
}
''
@@ -1,3 +1,3 @@
error: The prefix 'nix' is reserved for internal use by Lix in the Nix search path, its usage is deprecated and will be forbidden in the future.
Use --extra-deprecated-features nix-path-shadow to silence this error.
This is due to adding 'nix=nix-shadow' in the Nix search path, either through the environment variable 'NIX_PATH' or by passing the flag -I to the nix invocation.
warning: The prefix 'nix' is reserved for internal use by Lix in the Nix search path, its usage is deprecated and will be forbidden in the future.
Use --extra-deprecated-features nix-path-shadow to silence this warning.
This is due to adding 'nix=nix-shadow' in the Nix search path, either through the environment variable 'NIX_PATH' or by passing the flag -I to the nix invocation.
@@ -1,3 +1,3 @@
error: Shadowing '<nix/...>' by configuring the nix-path is deprecated and will be forbidden in the future.
Use --extra-deprecated-features nix-path-shadow to silence this error.
This is due to adding 'nix-shadow' to the nix-path without a prefix, either by passing the flag '-I nix-shadow' to the nix invocation or by adding this path to the environment variable 'NIX_PATH'.
warning: Shadowing '<nix/...>' by configuring the nix-path is deprecated and will be forbidden in the future.
Use --extra-deprecated-features nix-path-shadow to silence this warning.
This is due to adding 'nix-shadow' to the nix-path without a prefix, either by passing the flag '-I nix-shadow' to the nix invocation or by adding this path to the environment variable 'NIX_PATH'.
@@ -1,7 +1,7 @@
from collections.abc import Callable
from pathlib import Path
from lang.test_lang import test_eval_fail as nix_eval_fail, test_eval_okay as nix_eval_okay
from lang.test_lang import test_eval_okay as nix_eval
from testlib.fixtures.file_helper import AssetSymlink, CopyFile, CopyTree, with_files
from testlib.fixtures.nix import Nix
from testlib.fixtures.snapshot import Snapshot
@@ -21,7 +21,7 @@ from testlib.fixtures.snapshot import Snapshot
)
def test_search_path(files: Path, nix: Nix, snapshot: Callable[[str], Snapshot]):
nix.env.set_env("NIX_PATH", "dir3:dir4")
nix_eval_okay(
nix_eval(
files,
nix,
[
@@ -43,26 +43,12 @@ def test_search_path(files: Path, nix: Nix, snapshot: Callable[[str], Snapshot])
"nix-shadow": CopyTree("nix-shadow"),
"in.nix": CopyFile("in-fetchurl.nix"),
"out.exp": AssetSymlink("eval-okay-prefixed.out.exp"),
}
)
def test_prefixed_search_path_deprecated(
files: Path, nix: Nix, snapshot: Callable[[str], Snapshot]
):
nix.env.set_env("NIX_PATH", "nix=nix-shadow")
nix.settings.add_dp_feature("nix-path-shadow")
nix_eval_okay(files, nix, [], snapshot)
@with_files(
{
"nix-shadow": CopyTree("nix-shadow"),
"in.nix": CopyFile("in-fetchurl.nix"),
"err.exp": AssetSymlink("eval-okay-prefixed.err.exp"),
}
)
def test_prefixed_search_path(files: Path, nix: Nix, snapshot: Callable[[str], Snapshot]):
nix.env.set_env("NIX_PATH", "nix=nix-shadow")
nix_eval_fail(files, nix, [], snapshot)
nix_eval(files, nix, [], snapshot)
@with_files(
@@ -70,24 +56,11 @@ def test_prefixed_search_path(files: Path, nix: Nix, snapshot: Callable[[str], S
"nix-shadow": CopyTree("nix-shadow"),
"in.nix": CopyFile("in-fetchurl.nix"),
"out.exp": AssetSymlink("eval-okay-prefixless.out.exp"),
}
)
def test_prefixless_search_path_deprecated(
files: Path, nix: Nix, snapshot: Callable[[str], Snapshot]
):
nix.settings.add_dp_feature("nix-path-shadow")
nix_eval_okay(files, nix, ["-I", "nix-shadow"], snapshot)
@with_files(
{
"nix-shadow": CopyTree("nix-shadow"),
"in.nix": CopyFile("in-fetchurl.nix"),
"err.exp": AssetSymlink("eval-okay-prefixless.err.exp"),
}
)
def test_prefixless_search_path(files: Path, nix: Nix, snapshot: Callable[[str], Snapshot]):
nix_eval_fail(files, nix, ["-I", "nix-shadow"], snapshot)
nix_eval(files, nix, ["-I", "nix-shadow"], snapshot)
@with_files(
@@ -98,4 +71,4 @@ def test_prefixless_search_path(files: Path, nix: Nix, snapshot: Callable[[str],
}
)
def test_empty_search_path(files: Path, nix: Nix, snapshot: Callable[[str], Snapshot]):
nix_eval_okay(files, nix, [], snapshot)
nix_eval(files, nix, [], snapshot)
@@ -1,23 +0,0 @@
error:
… while calling the 'throw' builtin
at /pwd/in.nix:6:1:
5| in
6| throw set.inner
| ^
7|
… while evaluating the attribute 'inner'
at /pwd/in.nix:3:5:
2| set = {
3| inner = throw "nested throw";
| ^
4| };
… caused by explicit throw
at /pwd/in.nix:3:13:
2| set = {
3| inner = throw "nested throw";
| ^
4| };
error: nested throw
@@ -1,6 +0,0 @@
let
set = {
inner = throw "nested throw";
};
in
throw set.inner
-4
View File
@@ -23,10 +23,6 @@ if build_test_env != ''
endif
functional2_env.set('system', host_system)
foreach name, val : coverage_test_env
functional2_env.set(name, val)
endforeach
test(
'functional2',
bash,
@@ -1,7 +1,7 @@
from pathlib import Path
from testlib.fixtures.file_helper import with_files
from testlib.fixtures.nix import Nix, NixDaemon
from testlib.fixtures.nix import Nix
from testlib.utils import get_global_asset
@@ -40,7 +40,7 @@ class TestOptimizeStore:
def test_optimise_store(self, nix: Nix):
self._test_optimise_store(nix)
def test_optimise_store_daemon(self, nix: Nix, daemon: NixDaemon):
def test_optimise_store_daemon(self, nix: Nix):
nix.settings.auto_optimise_store = True
with daemon(nix, [], {"trusted-users": "*"}) as inner:
with nix.daemon([], {"trusted-users": "*"}) as inner:
self._test_optimise_store(inner)
-72
View File
@@ -1,72 +0,0 @@
from pathlib import Path
import pytest
from testlib.fixtures.file_helper import with_files, CopyFile, File
from testlib.fixtures.nix import Nix
from testlib.utils import get_global_asset_pack
from testlib.environ import environ
_files = get_global_asset_pack("simple-drv") | {
"hash-check.nix": CopyFile("assets/test_simple/hash-check.nix"),
"big-derivation-attr.nix": CopyFile("assets/test_simple/big-derivation-attr.nix"),
"dummy": File("Hello World\n"),
}
@pytest.fixture
def drv(nix: Nix) -> str:
res = nix.nix_instantiate(["simple.nix"]).run().ok()
return res.stdout_plain
@with_files(_files)
def test_store_system(nix: Nix, drv: str):
res = nix.nix_store(["-q", "--binding", "system", drv]).run().ok()
assert res.stdout_plain == environ.get("system")
@with_files(_files)
def test_out_path(nix: Nix, drv: str):
res = nix.nix_store(["-rvv", drv]).run().ok()
out_path = Path(res.stdout_plain)
assert out_path.exists()
text_path = out_path / "hello"
assert text_path.read_text() == "Hello World!\n"
# Directed delete: $outPath is not reachable from a root, so it should
# be deleteable.
nix.nix_store(["--delete", str(out_path)]).run().ok()
assert not text_path.exists()
res = (
nix.nix(
[
"eval",
"--store",
f"local?store=/foo&real={nix.env.dirs.real_store_dir}",
"--read-only",
"-f",
"hash-check.nix",
],
flake=True,
)
.run()
.ok()
)
assert (
res.stdout_plain == "«derivation /foo/lfy1s6ca46rm5r6w4gg9hc0axiakjcnm-dependencies.drv»"
), "hashDerivationModulo appears broken"
nix.env.set_env("NIX_REMOTE", f"local?store=/foo&real={nix.env.dirs.real_store_dir}")
nix.settings.store = None
res = nix.nix_instantiate(["--readonly-mode", "hash-check.nix"]).run().ok()
assert res.stdout_plain == "/foo/lfy1s6ca46rm5r6w4gg9hc0axiakjcnm-dependencies.drv", (
"hashDerivationModulo appears broken"
)
res = nix.nix_instantiate(["--readonly-mode", "big-derivation-attr.nix"]).run().ok()
assert res.stdout_plain == "/foo/xxiwa5zlaajv6xdjynf9yym9g319d6mn-big-derivation-attr.drv", (
"big-derivation-attr.nix hash appears broken. Memory corruption in large drv attr?"
)
+2 -6
View File
@@ -221,12 +221,8 @@ class ManagedEnv:
if platform.system() == "Darwin":
# Darwin / Apple behaves differently and requires _NIX_TEST_NO_SANDBOX to be set for whatever reason
self._env |= {"_NIX_TEST_NO_SANDBOX": "1"}
# Copy coreutils from the global path to maintain availability of commands that are not part of
# XCode Developer Tools and provided by busybox on Linux, which does not build on Darwin
for p in global_path.split(":"):
if Path(p).exists() and "coreutils" in p:
self.path.append(p)
break
# copy global path to maintain features usually provided by busybox
[self.path.append(p) for p in global_path.split(":") if Path(p).exists()]
def set_env(self, name: str, value: str):
if name in self.dirs.get_env_keys():
+56 -73
View File
@@ -4,7 +4,7 @@ import dataclasses
import sys
from functools import partialmethod
from pathlib import Path
from typing import Any, Literal, get_args
from typing import Any
from collections.abc import Callable, Generator
import shutil
import subprocess
@@ -20,9 +20,9 @@ from testlib.utils import is_value_of_type
type _NixSettingValue = str | int | list[str] | bool | None
def _serialise_config(value: _NixSettingValue) -> str:
def _serialise(value: Any) -> str:
if is_value_of_type(value, list[str]):
return " ".join(_serialise_config(e) for e in value)
return " ".join(_serialise(e) for e in value)
if is_value_of_type(value, bool):
return "true" if value else "false"
if is_value_of_type(value, str | int):
@@ -105,7 +105,7 @@ class NixSettings:
self["extra-sandbox-paths"] += env.path.to_sandbox_paths()
def field_may(name: str, value: Any, serializer: Callable[[Any], str] = _serialise_config):
def field_may(name: str, value: Any, serializer: Callable[[Any], str] = _serialise):
nonlocal config
if value is not None:
config += f"{name} = {serializer(value)}\n"
@@ -158,6 +158,7 @@ class Nix:
def nix_cmd(self, argv: list[str], flake: bool = False, cwd: Path | None = None) -> Command:
"""
Constructs a NixCommand with the appropriate settings.
:param build: if the executed command wants to build stuff. This is required due to darwin shenanigans. "auto" will try to autodetect, override using `True` or `False`. Has no effect on linux.
"""
# Create a copy of settings to not have a writing side effect
settings = self.settings.clone()
@@ -172,6 +173,57 @@ class Nix:
) -> Command:
return self.nix_cmd([nix_exe, *cmd], flake=flake, cwd=cwd)
@contextlib.contextmanager
def daemon(
self,
args: list[str] | None = None,
settings: dict[str, _NixSettingValue] | None = None,
**kwargs,
) -> "Nix":
daemon = copy.deepcopy(self)
daemon.logger = self.logger.getChild("daemon")
daemon.settings["allowed-users"] = ["*"]
daemon.settings["trusted-users"] = []
daemon.settings.store = f"local?root={self.env.dirs.test_root}"
daemon.settings.update(settings)
sockets_dir = Path(daemon.env.dirs.nix_state_dir) / "daemon-socket"
sockets = [sockets_dir / "socket"]
for p in sockets:
p.unlink(missing_ok=True)
proc = daemon.nix(args or [], nix_exe="nix-daemon", **kwargs).start()
def log_daemon_result(result: CommandResult | None, level: int):
if result:
daemon.logger.log(level, "daemon exited with code %i", result.rc)
daemon.logger.log(level, "stdout: %s", result.stdout_s)
daemon.logger.log(level, "stderr: %s", result.stderr_s)
else:
daemon.logger.error("daemon exited unexpectedly")
# wait for daemon to come up. this may take a while under load.
# we only test the *last* socket in the list because that's the
# last one the daemon creates, once it's there the daemon is up
while not sockets[-1].exists():
if status := proc.wait(0.01):
log_daemon_result(status, logging.ERROR)
raise RuntimeError("daemon exited during startup")
inner = copy.deepcopy(self)
inner.settings.store = f"unix://{sockets[-1]}" # missing multi socket support
try:
timeout, level = 1, logging.ERROR
yield inner
# 5 seconds should be enough to wait for a *graceful* exit.
timeout, level = 5, logging.DEBUG
finally:
result = proc.terminate(timeout)
if not result:
result = proc.kill()
log_daemon_result(result, level)
# Mark each of these as correct as they are not ClassVars, but we also don't want to turn off RUF045
nix_build = partialmethod(nix, nix_exe="nix-build") # noqa: RUF045
nix_shell = partialmethod(nix, nix_exe="nix-shell") # noqa: RUF045
@@ -290,75 +342,6 @@ def nix(tmp_path: Path, env: ManagedEnv, logger: logging.Logger) -> Generator[Ni
cmd.run().ok()
type NixDaemon = Callable[..., contextlib.AbstractAsyncContextManager[Nix]]
type NixDaemonProtocol = Literal["legacy-combined"]
daemon_protocols: list[NixDaemonProtocol] = get_args(NixDaemonProtocol.__value__)
# paramterize every daemon tests to run using all supported nix protocols
@pytest.fixture(params=daemon_protocols)
def daemon(request: pytest.FixtureRequest) -> NixDaemon:
default_protocol = request.param
@contextlib.contextmanager
def wrapper(
nix: Nix,
args: list[str] | None = None,
settings: dict[str, _NixSettingValue] | None = None,
protocol: NixDaemonProtocol | None = None,
**kwargs,
) -> contextlib.AbstractAsyncContextManager[Nix]:
protocol = protocol or default_protocol
daemon = copy.deepcopy(nix)
daemon.logger = nix.logger.getChild("daemon")
daemon.settings["allowed-users"] = ["*"]
daemon.settings["trusted-users"] = []
daemon.settings.store = f"local?root={nix.env.dirs.test_root}"
daemon.settings.update(settings)
sockets_dir = Path(daemon.env.dirs.nix_state_dir) / "daemon-socket"
sockets = [sockets_dir / "socket"]
for p in sockets:
p.unlink(missing_ok=True)
proc = daemon.nix(args or [], nix_exe="nix-daemon", **kwargs).start()
def log_daemon_result(result: CommandResult | None, level: int):
if result:
daemon.logger.log(level, "daemon exited with code %i", result.rc)
daemon.logger.log(level, "stdout: %s", result.stdout_s)
daemon.logger.log(level, "stderr: %s", result.stderr_s)
else:
daemon.logger.error("daemon exited unexpectedly")
# wait for daemon to come up. this may take a while under load.
# we only test the *last* socket in the list because that's the
# last one the daemon creates, once it's there the daemon is up
while not sockets[-1].exists():
if status := proc.wait(0.01):
log_daemon_result(status, logging.ERROR)
raise RuntimeError("daemon exited during startup")
inner = copy.deepcopy(nix)
inner.settings.store = f"unix://{sockets[-1]}" # missing multi socket support
try:
timeout, level = 1, logging.ERROR
yield inner
# 5 seconds should be enough to wait for a *graceful* exit.
timeout, level = 5, logging.DEBUG
finally:
result = proc.terminate(timeout)
if not result:
result = proc.kill()
log_daemon_result(result, level)
return wrapper
@pytest.fixture
def enable_diverted_store(nix: Nix):
"""
-8
View File
@@ -30,14 +30,6 @@ def test_list_type_nested_single_invalid():
assert not is_value_of_type([[1], [2, 3], ["a"]], list[list[int]])
def test_none_type():
assert is_value_of_type(None, None)
def test_none_type_union():
assert is_value_of_type(None, int | None)
def test_weird_type_valid():
assert is_value_of_type(42, Literal[42])
-2
View File
@@ -116,8 +116,6 @@ def is_value_of_type(value: Any, expected_type: type[Any] | UnionType) -> bool:
return True
match origin:
case None:
if expected_type is None:
return origin is None
return isinstance(value, expected_type)
case types.UnionType:
return any(is_value_of_type(value, t) for t in get_args(expected_type))
+3 -47
View File
@@ -38,56 +38,12 @@ std::pair<Value, PosIdx> AttrPathEval::testFindAlongAttrPath(std::string expr, s
// n.b. I do not know why we throw for empty attrs but they are apparently
// disallowed.
TEST_F(AttrPathEval, emptyAttrsThrowsWithoutQuotes)
TEST_F(AttrPathEval, emptyAttrsThrows)
{
std::string expr = "{a.\"\".b = 2;}";
ASSERT_NO_THROW(testFindAlongAttrPath(expr, "a"));
ASSERT_NO_THROW(testFindAlongAttrPath(expr, "a.\"\".b"));
ASSERT_THROW(testFindAlongAttrPath(expr, "a..b"), Error);
ASSERT_NO_THROW(testFindAlongAttrPath(expr, "a.\"\""));
ASSERT_THROW(testFindAlongAttrPath(expr, "a.\"\".b"), Error);
ASSERT_THROW(testFindAlongAttrPath(expr, "a.\"\""), Error);
}
TEST(attr_path_eval, quotes)
{
auto p1 = parseAttrPath("foo.\"foo bar\".baz");
ASSERT_EQ(3, p1.size());
ASSERT_EQ("foo", p1[0]);
ASSERT_EQ("foo bar", p1[1]);
ASSERT_EQ("baz", p1[2]);
auto p2 = parseAttrPath("foo.\"foo bar\"");
ASSERT_EQ(2, p2.size());
ASSERT_EQ("foo", p2[0]);
ASSERT_EQ("foo bar", p2[1]);
auto p3 = parseAttrPath("\"foo bar\"");
ASSERT_EQ(1, p3.size());
ASSERT_EQ("foo bar", p3[0]);
}
TEST(attr_path_eval, quotes_empty)
{
auto p1 = parseAttrPath("foo.\"\".bar");
ASSERT_EQ(3, p1.size());
ASSERT_EQ("foo", p1[0]);
ASSERT_EQ("", p1[1]);
ASSERT_EQ("bar", p1[2]);
auto p2 = parseAttrPath("foo.\"\"");
ASSERT_EQ(2, p2.size());
ASSERT_EQ("foo", p2[0]);
ASSERT_EQ("", p2[1]);
auto p3 = parseAttrPath("\"\"");
ASSERT_EQ(1, p3.size());
ASSERT_EQ("", p3[0]);
}
TEST(attr_path_eval, quotes_syntax)
{
ASSERT_THROW(parseAttrPath("foo.\"bar"), ParseError);
// escaped quotes (\") are not supported
ASSERT_THROW(parseAttrPath("foo.\"bar\\\"\""), ParseError);
}
}
+4 -2
View File
@@ -27,7 +27,8 @@ RC_GTEST_FIXTURE_PROP(
prop_opaque_path_round_trip,
(const SingleDerivedPath::Opaque & o))
{
Value v = evaluator.paths.mkStorePathString(o.path);
Value v;
evaluator.paths.mkStorePathString(o.path, v);
auto d = state.coerceToSingleDerivedPath(noPos, v, "");
RC_ASSERT(SingleDerivedPath { o } == d);
}
@@ -40,7 +41,8 @@ RC_GTEST_FIXTURE_PROP(
prop_derived_path_built_out_path_round_trip,
(const SingleDerivedPath::Built & b, const StorePath & outPath))
{
Value v = state.mkOutputString(b, outPath);
Value v;
state.mkOutputString(v, b, outPath);
auto [d, _] = state.coerceToSingleDerivedPathUnchecked(noPos, v, "");
RC_ASSERT(SingleDerivedPath { b } == d);
}
+2 -1
View File
@@ -15,7 +15,8 @@ namespace nix {
};
TEST_F(JSONValueTest, null) {
Value v = Value::VNULL;
Value v;
v.mkNull();
ASSERT_EQ(getJSONValue(v), "null");
}
+2 -1
View File
@@ -152,7 +152,8 @@ TEST_F(TypeValuePrintingTests, vExternal)
}
} myExternal;
Value vExternal = {NewValueAs::external, myExternal};
Value vExternal;
vExternal.mkExternal(&myExternal);
test(vExternal, "an external value from MyExternal");
}
+9 -5
View File
@@ -30,7 +30,8 @@ TEST_F(ValuePrintingTests, tInt)
TEST_F(ValuePrintingTests, tBool)
{
Value vBool = {NewValueAs::boolean, true};
Value vBool;
vBool = {NewValueAs::boolean, true};
test(vBool, "true");
}
@@ -48,7 +49,8 @@ TEST_F(ValuePrintingTests, tPath)
TEST_F(ValuePrintingTests, tNull)
{
Value vNull = Value::VNULL;
Value vNull;
vNull.mkNull();
test(vNull, "null");
}
@@ -94,7 +96,7 @@ TEST_F(ValuePrintingTests, vThunk)
TEST_F(ValuePrintingTests, vApp)
{
EvalMemory mem;
Value vFn = Value::VNULL;
Value vFn{NewValueAs::null};
Value vApp{NewValueAs::app, mem, vFn, vFn};
test(vApp, "«thunk»");
@@ -162,7 +164,8 @@ TEST_F(ValuePrintingTests, vExternal)
return str;
}
} myExternal;
Value vExternal = {NewValueAs::external, myExternal};
Value vExternal;
vExternal.mkExternal(&myExternal);
test(vExternal, "testing-external!");
}
@@ -367,7 +370,8 @@ TEST_F(ValuePrintingTests, ansiColorsPath)
TEST_F(ValuePrintingTests, ansiColorsNull)
{
Value v = Value::VNULL;
Value v;
v.mkNull();
test(v,
ANSI_CYAN "null" ANSI_NORMAL,

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