Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
609bc41e6f | ||
|
|
89999119dc | ||
|
|
71043329f1 | ||
|
|
ecaf293c4f | ||
|
|
09cb1fbca9 | ||
|
|
23620d0b7a | ||
|
|
351dbdfdca | ||
|
|
128159a717 | ||
|
|
33f713fa5d | ||
|
|
f92aaa9f59 | ||
|
|
e70bbff278 | ||
|
|
d2f8263fb8 | ||
|
|
15421962e6 | ||
|
|
a10fd676f6 | ||
|
|
bc506e4f67 | ||
|
|
f156a27cf4 | ||
|
|
7166c00efc |
@@ -57,10 +57,6 @@ blitz:
|
|||||||
display_name: Julian Stecklina
|
display_name: Julian Stecklina
|
||||||
github: blitz
|
github: blitz
|
||||||
|
|
||||||
blokyk:
|
|
||||||
display_name: blokyk
|
|
||||||
github: blokyk
|
|
||||||
|
|
||||||
cole-h:
|
cole-h:
|
||||||
display_name: Cole Helbling
|
display_name: Cole Helbling
|
||||||
github: cole-h
|
github: cole-h
|
||||||
@@ -77,6 +73,9 @@ detroyejr:
|
|||||||
display_name: Jonathan De Troye
|
display_name: Jonathan De Troye
|
||||||
github: detroyejr
|
github: detroyejr
|
||||||
|
|
||||||
|
edef:
|
||||||
|
github: edef1c
|
||||||
|
|
||||||
edolstra:
|
edolstra:
|
||||||
display_name: Eelco Dolstra
|
display_name: Eelco Dolstra
|
||||||
github: edolstra
|
github: edolstra
|
||||||
@@ -253,13 +252,12 @@ rootile:
|
|||||||
display_name: rootile (Rutile)
|
display_name: rootile (Rutile)
|
||||||
forgejo: rootile
|
forgejo: rootile
|
||||||
|
|
||||||
|
sandydoo:
|
||||||
|
github: sandydoo
|
||||||
|
|
||||||
seppel3210:
|
seppel3210:
|
||||||
github: Seppel3210
|
github: Seppel3210
|
||||||
|
|
||||||
sterni:
|
|
||||||
forgejo: sterni
|
|
||||||
github: sternenseemann
|
|
||||||
|
|
||||||
stevalkr:
|
stevalkr:
|
||||||
github: stevalkr
|
github: stevalkr
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
|
||||||
@@ -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.
|
|
||||||
@@ -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.
|
|
||||||
@@ -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.
|
|
||||||
@@ -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.
|
|
||||||
@@ -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`.
|
|
||||||
@@ -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.
|
|
||||||
@@ -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\"`.
|
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.)
|
(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*
|
- <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.
|
This option is like `--arg`, only the value is not a Nix expression but a string.
|
||||||
|
|||||||
@@ -51,64 +51,28 @@ $ nix-shell -A native-clangStdenvPackages
|
|||||||
|
|
||||||
### Building from the development shell
|
### Building from the development shell
|
||||||
|
|
||||||
We have a [justfile](just.systems) for extra convenient building.
|
Run a clean build and test with `just clean setup build install test`.
|
||||||
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`.
|
|
||||||
|
|
||||||
You can also run the unit tests and integration tests separately:
|
You can also run the unit tests and integration tests separately:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ just setup
|
$ just setup build test-unit
|
||||||
$ just test-unit
|
$ just install test-integration
|
||||||
$ just 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:
|
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.
|
Note that only targets which don't accept extra arguments can be used when
|
||||||
`just clean setup` is equivalent to `just clean && just setup`, but `just build test` runs the `build` target with the argument `test`.
|
running multiple targets at once; `just setup build` is fine, but `just
|
||||||
This means that if you want to, for example, build with lower parallelism, and then test, you will have to do something like this:
|
setup-custom build` is an error. The `test` target is usually the last one to
|
||||||
|
run, so it always accepts extra arguments.
|
||||||
```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
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also build Lix manually:
|
You can also build Lix manually:
|
||||||
|
|
||||||
@@ -444,18 +408,6 @@ You can build it yourself:
|
|||||||
# xdg-open ./result/coverage/index.html
|
# 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)).
|
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}
|
## Add a release note {#release-notes}
|
||||||
|
|||||||
@@ -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
|
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.
|
`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
|
- 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
|
program that is executed to perform the build. It can be either a
|
||||||
derivation or a source (a local file reference, e.g.,
|
derivation or a source (a local file reference, e.g.,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
FIXME(Lix): This chapter is quite outdated with respect to recommended practices in 2024 and needs updating.
|
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.
|
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>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,53 @@
|
|||||||
# Lix 2.95 "Kakigōri" (2026-03-13)
|
# 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)
|
# Lix 2.95.0 (2026-03-13)
|
||||||
|
|||||||
@@ -185,6 +185,9 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Forward from the previous stage as we don’t want it to pick the lowdown override
|
||||||
|
nixUnstable = prev.nixUnstable;
|
||||||
|
|
||||||
check-headers = final.buildPackages.callPackage ./maintainers/check-headers.nix { };
|
check-headers = final.buildPackages.callPackage ./maintainers/check-headers.nix { };
|
||||||
check-syscalls = final.buildPackages.callPackage ./maintainers/check-syscalls.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
|
# We don't change the default version in order to not change the hash
|
||||||
# of Nix/Lix from upstream Nixpkgs.
|
# of Nix/Lix from upstream Nixpkgs.
|
||||||
lowdown_3_0 =
|
lowdown_3_0 =
|
||||||
if (lib.versions.major prev.lowdown.version == "3") then
|
assert lib.versionOlder prev.lowdown.version "3.0.0";
|
||||||
prev.lowdown
|
prev.lowdown.overrideAttrs (
|
||||||
else
|
finalAttrs: _prevAttrs: {
|
||||||
prev.lowdown.overrideAttrs (
|
version = "3.0.0";
|
||||||
finalAttrs: _prevAttrs: {
|
|
||||||
version = "3.0.0";
|
|
||||||
|
|
||||||
src = final.fetchurl {
|
src = final.fetchurl {
|
||||||
url = "https://kristaps.bsd.lv/lowdown/snapshots/lowdown-${finalAttrs.version}.tar.gz";
|
url = "https://kristaps.bsd.lv/lowdown/snapshots/lowdown-${finalAttrs.version}.tar.gz";
|
||||||
sha512 = "94e97234d598382c3c3dc27f9bfdb3a3a2fcf7dbb6a8df3c85ee09f27f792449034a41d49d9cfd3d8450d2de01b8562c20c3d120e65c81af4d7d6c9454119e93";
|
sha512 = "94e97234d598382c3c3dc27f9bfdb3a3a2fcf7dbb6a8df3c85ee09f27f792449034a41d49d9cfd3d8450d2de01b8562c20c3d120e65c81af4d7d6c9454119e93";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
capnproto = prev.capnproto.overrideAttrs (old: {
|
capnproto = prev.capnproto.overrideAttrs (old: {
|
||||||
patches =
|
patches =
|
||||||
@@ -286,57 +287,33 @@
|
|||||||
overlays.default = overlayFor (p: p.clangStdenv);
|
overlays.default = overlayFor (p: p.clangStdenv);
|
||||||
|
|
||||||
hydraJobs = {
|
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.
|
# Binary package for various platforms.
|
||||||
build = forAllSystems (system: self.packages.${system}.nix);
|
build = forAllSystems (system: self.packages.${system}.nix);
|
||||||
|
|
||||||
# Ensure support for lowdown < 3.0 doesn't regress for NixOS 25.11
|
# 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:
|
system:
|
||||||
assert lib.versionOlder nixpkgsFor.${system}.native.lowdown.version "3.0.0";
|
|
||||||
self.packages.${system}.nix.override {
|
self.packages.${system}.nix.override {
|
||||||
lowdown = nixpkgsFor.${system}.native.lowdown;
|
lowdown = nixpkgsFor.${system}.native.lowdown;
|
||||||
lowdown-unsandboxed = nixpkgsFor.${system}.native.lowdown-unsandboxed;
|
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: {
|
devShell = forAllSystems (system: {
|
||||||
default = self.devShells.${system}.default;
|
default = self.devShells.${system}.default;
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
# https://just.systems/man/en/
|
# 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 all available targets
|
||||||
list:
|
list:
|
||||||
@@ -12,32 +6,40 @@ list:
|
|||||||
|
|
||||||
# Clean build artifacts
|
# Clean build artifacts
|
||||||
clean:
|
clean:
|
||||||
rm -rf {{ builddir }}
|
rm -rf build
|
||||||
|
|
||||||
# Prepare meson for building.
|
# Prepare meson for building with extra options
|
||||||
setup *OPTIONS:
|
setup-custom *OPTIONS:
|
||||||
meson setup {{ builddir }} --reconfigure --prefix="{{outdir}}" $mesonFlags {{ OPTIONS }}
|
meson setup build --prefix="$PWD/outputs/out" $mesonFlags {{ OPTIONS }}
|
||||||
|
|
||||||
|
# Prepare meson for building
|
||||||
|
setup: (setup-custom)
|
||||||
|
|
||||||
# Build lix with extra options
|
# Build lix with extra options
|
||||||
build *OPTIONS:
|
build-custom *OPTIONS:
|
||||||
meson compile -C {{ builddir }} {{ OPTIONS }}
|
meson compile -C build {{ OPTIONS }}
|
||||||
|
|
||||||
|
# Build lix
|
||||||
|
build: (build-custom)
|
||||||
|
|
||||||
alias compile := build
|
alias compile := build
|
||||||
|
|
||||||
# `meson install` will automatically build anything that needs to be built to install it.
|
# Install lix for local development with extra options
|
||||||
[doc("Install Lix for local development")]
|
install-custom *OPTIONS: (build-custom OPTIONS)
|
||||||
install *OPTIONS:
|
meson install -C build
|
||||||
meson install --quiet -C {{ builddir }} {{ OPTIONS }}
|
|
||||||
|
|
||||||
# Run all tests tests (installs first).
|
# Install lix for local development
|
||||||
test *OPTIONS: (install)
|
install: (install-custom)
|
||||||
meson test -C {{ builddir }} --print-errorlogs --max-lines 10000 {{ OPTIONS }}
|
|
||||||
|
# Run tests (usually requires `install`) with extra options
|
||||||
|
test *OPTIONS:
|
||||||
|
meson test -C build --print-errorlogs --max-lines 10000 {{ OPTIONS }}
|
||||||
|
|
||||||
# Run unit tests only
|
# Run unit tests only
|
||||||
test-unit *OPTIONS: (test "--suite" "check")
|
test-unit *OPTIONS: (test "--suite" "check")
|
||||||
|
|
||||||
# Run integration tests only
|
# 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
|
# 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:
|
test-functional2 *OPTIONS:
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
|||||||
auto autoArgsWithInNixShell = autoArgs;
|
auto autoArgsWithInNixShell = autoArgs;
|
||||||
if (runEnv) {
|
if (runEnv) {
|
||||||
auto newArgs = evaluator->buildBindings(autoArgsWithInNixShell->size() + 1);
|
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);
|
for (auto & i : *autoArgs) newArgs.insert(i);
|
||||||
autoArgsWithInNixShell = newArgs.finish();
|
autoArgsWithInNixShell = newArgs.finish();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,7 +154,8 @@ static void getAllExprs(Evaluator & state,
|
|||||||
Value vArg = {NewValueAs::string, path2.canonical().abs()};
|
Value vArg = {NewValueAs::string, path2.canonical().abs()};
|
||||||
if (seen.size() == maxAttrs)
|
if (seen.size() == maxAttrs)
|
||||||
throw Error("too many Nix expressions in directory '%1%'", path);
|
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)
|
else if (st.type == InputAccessor::tDirectory)
|
||||||
/* `path2' is a directory (with no default.nix in it);
|
/* `path2' is a directory (with no default.nix in it);
|
||||||
@@ -179,7 +180,7 @@ static Value loadSourceExpr(EvalState & state, const SourcePath & path_)
|
|||||||
directory). */
|
directory). */
|
||||||
else if (st.type == InputAccessor::tDirectory) {
|
else if (st.type == InputAccessor::tDirectory) {
|
||||||
auto attrs = state.ctx.buildBindings(maxAttrs);
|
auto attrs = state.ctx.buildBindings(maxAttrs);
|
||||||
attrs.insert("_combineChannels", Value::EMPTY_LIST);
|
attrs.alloc("_combineChannels") = Value::EMPTY_LIST;
|
||||||
StringSet seen;
|
StringSet seen;
|
||||||
getAllExprs(state.ctx, path, seen, attrs);
|
getAllExprs(state.ctx, path, seen, attrs);
|
||||||
return {NewValueAs::attrs, attrs};
|
return {NewValueAs::attrs, attrs};
|
||||||
|
|||||||
@@ -42,7 +42,11 @@ void processExpr(EvalState & state, const Strings & attrPaths,
|
|||||||
|
|
||||||
NixStringContext context;
|
NixStringContext context;
|
||||||
if (evalOnly) {
|
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)
|
if (output == okRaw)
|
||||||
std::cout << *state.coerceToString(noPos, vRes, context, "while generating the nix-instantiate output", StringCoercionMode::Strict);
|
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
|
// We intentionally don't output a newline here. The default PS1 for Bash in NixOS starts with a newline
|
||||||
|
|||||||
+17
-18
@@ -46,31 +46,30 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
|
|||||||
|
|
||||||
auto attrs = state.ctx.buildBindings(7 + outputs.size());
|
auto attrs = state.ctx.buildBindings(7 + outputs.size());
|
||||||
|
|
||||||
attrs.insert(state.ctx.symbols.sym_type, {NewValueAs::string, "derivation"});
|
attrs.alloc(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_name) = {NewValueAs::string, i.queryName(state)};
|
||||||
auto system = i.querySystem(state);
|
auto system = i.querySystem(state);
|
||||||
if (!system.empty())
|
if (!system.empty())
|
||||||
attrs.insert(state.ctx.symbols.sym_system, {NewValueAs::string, system});
|
attrs.alloc(state.ctx.symbols.sym_system) = {NewValueAs::string, system};
|
||||||
attrs.insert(
|
attrs.alloc(state.ctx.symbols.sym_outPath) = {
|
||||||
state.ctx.symbols.sym_outPath,
|
NewValueAs::string, state.ctx.store->printStorePath(i.queryOutPath(state))
|
||||||
{NewValueAs::string, state.ctx.store->printStorePath(i.queryOutPath(state))}
|
};
|
||||||
);
|
|
||||||
if (drvPath)
|
if (drvPath)
|
||||||
attrs.insert(
|
attrs.alloc(state.ctx.symbols.sym_drvPath) = {
|
||||||
state.ctx.symbols.sym_drvPath, {NewValueAs::string, state.ctx.store->printStorePath(*drvPath)}
|
NewValueAs::string, state.ctx.store->printStorePath(*drvPath)
|
||||||
);
|
};
|
||||||
|
|
||||||
// Copy each output meant for installation.
|
// Copy each output meant for installation.
|
||||||
|
auto & vOutputs = attrs.alloc(state.ctx.symbols.sym_outputs);
|
||||||
auto outputsList = state.ctx.mem.newList(outputs.size());
|
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)) {
|
for (const auto & [m, j] : enumerate(outputs)) {
|
||||||
outputsList->elems[m] = {NewValueAs::string, j.first};
|
outputsList->elems[m] = {NewValueAs::string, j.first};
|
||||||
auto outputAttrs = state.ctx.buildBindings(2);
|
auto outputAttrs = state.ctx.buildBindings(2);
|
||||||
outputAttrs.insert(
|
outputAttrs.alloc(state.ctx.symbols.sym_outPath) = {
|
||||||
state.ctx.symbols.sym_outPath,
|
NewValueAs::string, state.ctx.store->printStorePath(*j.second)
|
||||||
{NewValueAs::string, state.ctx.store->printStorePath(*j.second)}
|
};
|
||||||
);
|
attrs.alloc(j.first) = {NewValueAs::attrs, outputAttrs};
|
||||||
attrs.insert(j.first, {NewValueAs::attrs, outputAttrs});
|
|
||||||
|
|
||||||
/* This is only necessary when installing store paths, e.g.,
|
/* This is only necessary when installing store paths, e.g.,
|
||||||
`nix-env -i /nix/store/abcd...-foo'. */
|
`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);
|
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};
|
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
|
/* Construct a Nix expression that calls the user environment
|
||||||
builder with the manifest as argument. */
|
builder with the manifest as argument. */
|
||||||
auto attrs = state.ctx.buildBindings(3);
|
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);
|
attrs.insert(state.ctx.symbols.create("derivations"), vManifest);
|
||||||
Value args = {NewValueAs::attrs, attrs};
|
Value args = {NewValueAs::attrs, attrs};
|
||||||
|
|
||||||
|
|||||||
@@ -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/libexpr/eval-settings.hh"
|
||||||
#include "lix/libcmd/common-eval-args.hh"
|
#include "lix/libcmd/common-eval-args.hh"
|
||||||
#include "lix/libmain/shared.hh"
|
#include "lix/libmain/shared.hh"
|
||||||
@@ -13,10 +10,31 @@
|
|||||||
#include "lix/libcmd/command.hh"
|
#include "lix/libcmd/command.hh"
|
||||||
#include "lix/libutil/async.hh"
|
#include "lix/libutil/async.hh"
|
||||||
#include "lix/libutil/error.hh"
|
#include "lix/libutil/error.hh"
|
||||||
#include <deque>
|
#include "lix/libutil/regex.hh"
|
||||||
|
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
namespace nix {
|
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()
|
MixEvalArgs::MixEvalArgs()
|
||||||
{
|
{
|
||||||
addFlag(
|
addFlag(
|
||||||
@@ -24,7 +42,10 @@ MixEvalArgs::MixEvalArgs()
|
|||||||
.description = "Pass the value *expr* as the argument *name* to Nix functions.",
|
.description = "Pass the value *expr* as the argument *name* to Nix functions.",
|
||||||
.category = category,
|
.category = category,
|
||||||
.labels = {"name", "expr"},
|
.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({
|
addFlag({
|
||||||
@@ -32,7 +53,10 @@ MixEvalArgs::MixEvalArgs()
|
|||||||
.description = "Pass the string *string* as the argument *name* to Nix functions.",
|
.description = "Pass the string *string* as the argument *name* to Nix functions.",
|
||||||
.category = category,
|
.category = category,
|
||||||
.labels = {"name", "string"},
|
.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({
|
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)
|
Bindings * MixEvalArgs::getAutoArgs(Evaluator & state)
|
||||||
{
|
{
|
||||||
AutoArgsContainer aac;
|
auto res = state.buildBindings(autoArgs.size());
|
||||||
for (auto & [name, value] : autoArgs) {
|
for (auto & i : autoArgs) {
|
||||||
Value v = std::visit(
|
Value v;
|
||||||
overloaded{
|
if (i.second[0] == 'E')
|
||||||
[&](StringArgument & str) -> Value { return {NewValueAs::string, (std::string_view) str.value}; },
|
v = state.evalLazily(state.parseExprFromString(i.second.substr(1), CanonPath::fromCwd()));
|
||||||
[&](ExprArgument & e) -> Value {
|
else
|
||||||
return state.evalLazily(state.parseExprFromString(e.expr, CanonPath::fromCwd()));
|
v = {NewValueAs::string, ((std::string_view) i.second).substr(1)};
|
||||||
}
|
res.insert(state.symbols.create(i.first), v);
|
||||||
},
|
|
||||||
value
|
|
||||||
);
|
|
||||||
|
|
||||||
addAutoArgRecursive(aac, state, parseAttrPath(name, false), v, name);
|
|
||||||
}
|
}
|
||||||
|
return res.finish();
|
||||||
return aac.toBindings(state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kj::Promise<Result<EvalPaths::PathResult<SourcePath, ThrownError>>>
|
kj::Promise<Result<EvalPaths::PathResult<SourcePath, ThrownError>>>
|
||||||
|
|||||||
@@ -14,15 +14,6 @@ class EvalState;
|
|||||||
class Bindings;
|
class Bindings;
|
||||||
struct SourcePath;
|
struct SourcePath;
|
||||||
|
|
||||||
struct StringArgument
|
|
||||||
{
|
|
||||||
std::string value;
|
|
||||||
};
|
|
||||||
struct ExprArgument
|
|
||||||
{
|
|
||||||
std::string expr;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MixEvalArgs : virtual Args, virtual MixRepair
|
struct MixEvalArgs : virtual Args, virtual MixRepair
|
||||||
{
|
{
|
||||||
static constexpr auto category = "Common evaluation options";
|
static constexpr auto category = "Common evaluation options";
|
||||||
@@ -36,7 +27,7 @@ struct MixEvalArgs : virtual Args, virtual MixRepair
|
|||||||
std::optional<std::string> evalStoreUrl;
|
std::optional<std::string> evalStoreUrl;
|
||||||
|
|
||||||
private:
|
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
|
/** @brief Resolve an argument that is generally a file, but could be something that
|
||||||
|
|||||||
+11
-11
@@ -446,18 +446,18 @@ Installables SourceExprCommand::parseInstallables(
|
|||||||
throw UsageError("'--file' and '--expr' are exclusive");
|
throw UsageError("'--file' and '--expr' are exclusive");
|
||||||
|
|
||||||
auto evaluator = getEvaluator();
|
auto evaluator = getEvaluator();
|
||||||
|
Value vFile;
|
||||||
|
|
||||||
Value vFile = [&](NeverAsync = {}) {
|
if (file == "-") {
|
||||||
if (file == "-") {
|
auto & e = evaluator->parseStdin();
|
||||||
auto & e = evaluator->parseStdin();
|
vFile = state.eval(e);
|
||||||
return state.eval(e);
|
}
|
||||||
} else if (file) {
|
else if (file)
|
||||||
return state.evalFile(state.aio.blockOn(lookupFileArg(*evaluator, *file)).unwrap());
|
vFile = state.evalFile(state.aio.blockOn(lookupFileArg(*evaluator, *file)).unwrap());
|
||||||
} else {
|
else {
|
||||||
auto & e = evaluator->parseExprFromString(*expr, CanonPath::fromCwd());
|
auto & e = evaluator->parseExprFromString(*expr, CanonPath::fromCwd());
|
||||||
return state.eval(e);
|
vFile = state.eval(e);
|
||||||
}
|
}
|
||||||
}();
|
|
||||||
|
|
||||||
for (auto & s : ss) {
|
for (auto & s : ss) {
|
||||||
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(s);
|
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(s);
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ std::string renderMarkdownToTerminal(std::string_view markdown, StandardOutputSt
|
|||||||
|
|
||||||
struct lowdown_opts opts{
|
struct lowdown_opts opts{
|
||||||
.type = LOWDOWN_TERM,
|
.type = LOWDOWN_TERM,
|
||||||
|
#ifdef LOWDOWN_SEPARATE_TERM_OPTS
|
||||||
.term =
|
.term =
|
||||||
{
|
{
|
||||||
.cols = lowdown_cols,
|
.cols = lowdown_cols,
|
||||||
@@ -64,7 +65,14 @@ std::string renderMarkdownToTerminal(std::string_view markdown, StandardOutputSt
|
|||||||
.vmargin = 0,
|
.vmargin = 0,
|
||||||
.centre = 0,
|
.centre = 0,
|
||||||
},
|
},
|
||||||
|
// maxdepth needs to be part of the ifdefs to match declaration order
|
||||||
.maxdepth = 20,
|
.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,
|
.feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES,
|
||||||
#ifdef LOWDOWN_CONSOLIDATED_OFLAGS
|
#ifdef LOWDOWN_CONSOLIDATED_OFLAGS
|
||||||
.oflags = LOWDOWN_NOLINK,
|
.oflags = LOWDOWN_NOLINK,
|
||||||
|
|||||||
+22
-103
@@ -1,13 +1,10 @@
|
|||||||
#include <algorithm>
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <editline.h>
|
#include <editline.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <optional>
|
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
#include "libutil/logging.hh"
|
|
||||||
#include "lix/libexpr/value.hh"
|
#include "lix/libexpr/value.hh"
|
||||||
#include "lix/libutil/box_ptr.hh"
|
#include "lix/libutil/box_ptr.hh"
|
||||||
#include "lix/libcmd/repl-interacter.hh"
|
#include "lix/libcmd/repl-interacter.hh"
|
||||||
@@ -714,111 +711,29 @@ void NixRepl::initDebugBuiltinCommands()
|
|||||||
|
|
||||||
addCommand(
|
addCommand(
|
||||||
"show-trace",
|
"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) {
|
[](NixRepl & repl, const std::string & arg) {
|
||||||
auto setTrace = [&](size_t traceIdx, const DebugTrace * trace) {
|
try {
|
||||||
repl.debugTraceIndex = traceIdx;
|
repl.debugTraceIndex = stoi(arg);
|
||||||
std::cout << "\n" << ANSI_BLUE << traceIdx << ANSI_NORMAL << ": ";
|
} catch (...) {
|
||||||
showDebugTrace(std::cout, repl.evaluator.positions, *trace);
|
}
|
||||||
std::cout << std::endl;
|
|
||||||
printEnvBindings(repl.state, trace->expr, trace->env);
|
|
||||||
repl.loadDebugTraceEnv(*trace);
|
|
||||||
};
|
|
||||||
|
|
||||||
// tries to find a trace at a given index.
|
auto traces = repl.evaluator.debug->traces();
|
||||||
// - if it is found, it returns the requested trace, along with its
|
for (const auto & [idx, i] : enumerate(traces)) {
|
||||||
// index, which will be *the same* as requested
|
if (idx == repl.debugTraceIndex) {
|
||||||
// - otherwise, it returns the last (=outermost) trace, along with
|
std::cout << "\n" << ANSI_BLUE << idx << ANSI_NORMAL << ": ";
|
||||||
// its index, which will be *different* than the one requested
|
showDebugTrace(std::cout, repl.evaluator.positions, *i);
|
||||||
auto tryFindTrace = [&](size_t traceIdx) -> std::pair<size_t, const DebugTrace *> {
|
std::cout << std::endl;
|
||||||
size_t lastIndex = 0;
|
printEnvBindings(repl.state, i->expr, i->env);
|
||||||
const DebugTrace * lastTrace;
|
repl.loadDebugTraceEnv(*i);
|
||||||
auto traces = repl.evaluator.debug->traces();
|
break;
|
||||||
for (const auto & [idx, i] : enumerate(traces)) {
|
|
||||||
lastTrace = i;
|
|
||||||
lastIndex = idx;
|
|
||||||
if (idx == traceIdx) {
|
|
||||||
return std::pair(idx, i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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"},
|
{.aliases = {"st"},
|
||||||
.debugModeOnly = true,
|
.debugModeOnly = true,
|
||||||
.help = "Show current trace. If an integer is provided, this switches to that stack "
|
.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"
|
"beforehand.",
|
||||||
"relative to the current stack index.",
|
|
||||||
.section = "Debug mode",
|
.section = "Debug mode",
|
||||||
.positionalArgsSpecifiers = {{.placeholderText = "integer index", .optional = true}}}
|
.positionalArgsSpecifiers = {{.placeholderText = "integer index", .optional = true}}}
|
||||||
);
|
);
|
||||||
@@ -1402,10 +1317,12 @@ void NixRepl::loadFlake(const std::string & flakeRefS)
|
|||||||
.kind = ReplLoadKind::Flake,
|
.kind = ReplLoadKind::Flake,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Value v;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
loaded.remove(loadable);
|
loaded.remove(loadable);
|
||||||
loaded.push_back(loadable);
|
loaded.push_back(loadable);
|
||||||
Value v = flake::callFlake(
|
v = flake::callFlake(
|
||||||
state,
|
state,
|
||||||
flake::lockFlake(
|
flake::lockFlake(
|
||||||
state,
|
state,
|
||||||
@@ -1533,6 +1450,7 @@ Value NixRepl::getReplOverlaysEvalFunction()
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto evalReplInitFilesPath = CanonPath::root + "repl-overlays.nix";
|
auto evalReplInitFilesPath = CanonPath::root + "repl-overlays.nix";
|
||||||
|
*replOverlaysEvalFunction = Value{};
|
||||||
auto code =
|
auto code =
|
||||||
#include "repl-overlays.nix.gen.hh"
|
#include "repl-overlays.nix.gen.hh"
|
||||||
;
|
;
|
||||||
@@ -1542,15 +1460,16 @@ Value NixRepl::getReplOverlaysEvalFunction()
|
|||||||
evaluator.builtins.staticEnv
|
evaluator.builtins.staticEnv
|
||||||
);
|
);
|
||||||
|
|
||||||
*replOverlaysEvalFunction = state.eval(expr);
|
**replOverlaysEvalFunction = state.eval(expr);
|
||||||
|
|
||||||
return **replOverlaysEvalFunction;
|
return **replOverlaysEvalFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
Value NixRepl::replOverlays()
|
Value NixRepl::replOverlays()
|
||||||
{
|
{
|
||||||
|
Value replInits;
|
||||||
auto replInitStorage = evaluator.mem.newList(evalSettings.replOverlays.get().size());
|
auto replInitStorage = evaluator.mem.newList(evalSettings.replOverlays.get().size());
|
||||||
Value replInits = {NewValueAs::list, replInitStorage};
|
replInits = {NewValueAs::list, replInitStorage};
|
||||||
|
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
for (auto path : evalSettings.replOverlays.get()) {
|
for (auto path : evalSettings.replOverlays.get()) {
|
||||||
|
|||||||
+13
-30
@@ -7,7 +7,8 @@
|
|||||||
|
|
||||||
namespace nix {
|
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::vector<std::string> res;
|
||||||
std::string cur;
|
std::string cur;
|
||||||
@@ -15,22 +16,6 @@ std::vector<std::string> parseAttrPath(std::string_view const s, bool allowRhsTr
|
|||||||
auto i = s.begin();
|
auto i = s.begin();
|
||||||
while (i != s.end()) {
|
while (i != s.end()) {
|
||||||
if (*i == '.') {
|
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);
|
res.push_back(cur);
|
||||||
haveData = false;
|
haveData = false;
|
||||||
cur.clear();
|
cur.clear();
|
||||||
@@ -51,11 +36,7 @@ std::vector<std::string> parseAttrPath(std::string_view const s, bool allowRhsTr
|
|||||||
}
|
}
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
if (haveData) {
|
if (haveData) res.push_back(cur);
|
||||||
res.push_back(cur);
|
|
||||||
} else if (!allowRhsTrailingDot) {
|
|
||||||
throw ParseError("Trailing dot on the right-hand side of path expr '%1%' is not allowed!", s);
|
|
||||||
};
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +90,9 @@ findAlongAttrPath(EvalState & state, const std::string & attrPath, Bindings & au
|
|||||||
according to what is specified in the attrPath. */
|
according to what is specified in the attrPath. */
|
||||||
|
|
||||||
if (!attrIndex) {
|
if (!attrIndex) {
|
||||||
|
if (attr.empty())
|
||||||
|
throw Error("empty attribute name in selection path '%1%'", attrPath);
|
||||||
|
|
||||||
if (v.type() != nAttrs) {
|
if (v.type() != nAttrs) {
|
||||||
auto pathPart =
|
auto pathPart =
|
||||||
std::vector<std::string>(tokens.begin(), tokens.begin() + attrPathIdx);
|
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)
|
std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what)
|
||||||
{
|
{
|
||||||
Value v2 = [&]() {
|
Value v2;
|
||||||
try {
|
try {
|
||||||
auto dummyArgs = state.ctx.mem.allocBindings(0);
|
auto dummyArgs = state.ctx.mem.allocBindings(0);
|
||||||
return findAlongAttrPath(state, "meta.position", *dummyArgs, v).first;
|
v2 = findAlongAttrPath(state, "meta.position", *dummyArgs, v).first;
|
||||||
} catch (Error &) {
|
} catch (Error &) {
|
||||||
throw NoPositionInfo("package '%s' has no source location information", what);
|
throw NoPositionInfo("package '%s' has no source location information", what);
|
||||||
}
|
}
|
||||||
}();
|
|
||||||
|
|
||||||
// FIXME: is it possible to extract the Pos object instead of doing this
|
// FIXME: is it possible to extract the Pos object instead of doing this
|
||||||
// toString + parsing?
|
// toString + parsing?
|
||||||
|
|||||||
@@ -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.
|
* 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.
|
* 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.
|
* Converts an attr path from a list of strings into a string once more.
|
||||||
|
|||||||
+11
-2
@@ -23,11 +23,20 @@ Bindings * EvalMemory::allocBindings(size_t capacity)
|
|||||||
return new (allocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) Bindings();
|
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()
|
void Bindings::sort()
|
||||||
{
|
{
|
||||||
if (size_) std::sort(begin(), end());
|
if (size_) std::sort(begin(), end());
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ struct Attr
|
|||||||
PosIdx pos;
|
PosIdx pos;
|
||||||
mutable Value value;
|
mutable Value value;
|
||||||
Attr(Symbol name, Value value, PosIdx pos = noPos) : name(name), pos(pos), 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
|
bool operator < (const Attr & a) const
|
||||||
{
|
{
|
||||||
return name < a.name;
|
return name < a.name;
|
||||||
@@ -73,9 +72,8 @@ public:
|
|||||||
|
|
||||||
const Attr * get(Symbol name)
|
const Attr * get(Symbol name)
|
||||||
{
|
{
|
||||||
iterator i = std::lower_bound(begin(), end(), name, [](const Attr & value, const Symbol & compare) {
|
Attr key(name, {});
|
||||||
return value.name < compare;
|
iterator i = std::lower_bound(begin(), end(), key);
|
||||||
});
|
|
||||||
if (i != end() && i->name == name) return &*i;
|
if (i != end() && i->name == name) return &*i;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -142,8 +140,6 @@ public:
|
|||||||
insert(Attr(name, value, pos));
|
insert(Attr(name, value, pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
void insert(std::string_view name, Value value, PosIdx pos = noPos);
|
|
||||||
|
|
||||||
void insert(const Attr & attr)
|
void insert(const Attr & attr)
|
||||||
{
|
{
|
||||||
push_back(attr);
|
push_back(attr);
|
||||||
@@ -155,6 +151,10 @@ public:
|
|||||||
bindings->push_back(attr);
|
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")]]
|
[[nodiscard("must use created bindings")]]
|
||||||
Bindings * finish()
|
Bindings * finish()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ present in *args*. All are optional except `path`:
|
|||||||
|
|
||||||
- name\
|
- name\
|
||||||
The name of the path when added to the store. This can used to
|
The name of the path when added to the store. This can used to
|
||||||
reference paths that have
|
reference paths that have nix-illegal characters in their names,
|
||||||
[nix-illegal characters in their names](./derivations.md),
|
|
||||||
like `@`.
|
like `@`.
|
||||||
|
|
||||||
- filter\
|
- filter\
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: scopedImport
|
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]
|
args: [scope, path]
|
||||||
renameInGlobalScope: false
|
renameInGlobalScope: false
|
||||||
---
|
---
|
||||||
|
|||||||
+65
-49
@@ -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.
|
* http://github.com/NixOS/nix/issues/7012. Any accesses to the output attrset will thus infrec.
|
||||||
*/
|
*/
|
||||||
Value vBackup = v;
|
Value vBackup = v;
|
||||||
Symbol nameSym;
|
Value nameVal;
|
||||||
{
|
{
|
||||||
KJ_DEFER(v = vBackup);
|
KJ_DEFER(v = vBackup);
|
||||||
v = Value{NewValueAs::blackhole};
|
v = Value{NewValueAs::blackhole};
|
||||||
Value nameVal = i.nameExpr->eval(state, *dynamicEnv);
|
nameVal = i.nameExpr->eval(state, *dynamicEnv);
|
||||||
state.forceValue(nameVal, i.pos);
|
state.forceValue(nameVal, i.pos);
|
||||||
if (nameVal.type() == nNull) {
|
if (nameVal.type() == nNull) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
state.forceStringNoCtx(nameVal, i.pos, "while evaluating the name of a dynamic attribute");
|
state.forceStringNoCtx(nameVal, i.pos, "while evaluating the name of a dynamic attribute");
|
||||||
nameSym = state.ctx.symbols.create(nameVal.str());
|
|
||||||
}
|
}
|
||||||
|
auto nameSym = state.ctx.symbols.create(nameVal.str());
|
||||||
auto j = v.attrs()->get(nameSym);
|
auto j = v.attrs()->get(nameSym);
|
||||||
if (j) {
|
if (j) {
|
||||||
state.ctx.errors
|
state.ctx.errors
|
||||||
@@ -187,29 +187,7 @@ Value ExprSet::eval(EvalState & state, Env & env)
|
|||||||
.debugThrow();
|
.debugThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
// clang-format off
|
i.valueExpr->setName(nameSym);
|
||||||
/* 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
|
|
||||||
|
|
||||||
/* Keep sorted order so find can catch duplicates */
|
/* Keep sorted order so find can catch duplicates */
|
||||||
v.attrs()->push_back(Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), i.pos));
|
v.attrs()->push_back(Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), i.pos));
|
||||||
v.attrs()->sort(); // FIXME: inefficient
|
v.attrs()->sort(); // FIXME: inefficient
|
||||||
@@ -351,7 +329,45 @@ Value ExprOpUpdate::eval(EvalState & state, Env & env)
|
|||||||
Value v2 = e2->eval(state, env);
|
Value v2 = e2->eval(state, env);
|
||||||
state.checkAttrs(v2, env, *e2);
|
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)
|
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.
|
// List of returned strings. References to these Values must NOT be persisted.
|
||||||
SmallTemporaryValueVector<conservativeStackReservation> values;
|
SmallTemporaryValueVector<conservativeStackReservation> values(es.size());
|
||||||
values.reserve(es.size());
|
Value * vTmpP = values.data();
|
||||||
|
|
||||||
for (auto & [i_pos, i] : es) {
|
for (auto & [i_pos, i] : es) {
|
||||||
values.push_back(i->eval(state, env));
|
Value & vTmp = *vTmpP++;
|
||||||
Value & vTmp = values.back();
|
vTmp = i->eval(state, env);
|
||||||
|
|
||||||
/* If the first element is a path, then the result will also
|
/* 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,
|
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)
|
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)
|
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.
|
// Position for the current selector in this select chain.
|
||||||
PosIdx posCurrentSyntax;
|
PosIdx posCurrentSyntax;
|
||||||
|
|
||||||
Value baseSelectee = [&]() {
|
Value baseSelectee;
|
||||||
try {
|
try {
|
||||||
// Evaluate the original thing we're selecting on.
|
// Evaluate the original thing we're selecting on.
|
||||||
return e->eval(state, env);
|
baseSelectee = e->eval(state, env);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
e.addTrace(state.ctx.positions[getPos()], HintFmt(
|
e.addTrace(state.ctx.positions[getPos()], HintFmt(
|
||||||
"while evaluating an expression to select '%s' on it",
|
"while evaluating an expression to select '%s' on it",
|
||||||
showAttrPath(state.ctx.symbols, attrPath)
|
showAttrPath(state.ctx.symbols, attrPath)
|
||||||
));
|
));
|
||||||
// clang-format on
|
// clang-format on
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// With the original selectee evaluated, we'll walk the selection path starting
|
// 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
|
// 5: under 10
|
||||||
// This excluded attrset lambdas (`{...}:`). Contributions of mixed lambdas appears insignificant at ~150
|
// This excluded attrset lambdas (`{...}:`). Contributions of mixed lambdas appears insignificant at ~150
|
||||||
// total.
|
// total.
|
||||||
SmallValueVector<4> vArgs;
|
SmallValueVector<4> vArgs(args.size());
|
||||||
vArgs.reserve(args.size());
|
|
||||||
for (size_t i = 0; i < args.size(); ++i) {
|
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);
|
return state.callFunction(vFun, vArgs, pos);
|
||||||
|
|||||||
+60
-93
@@ -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 \
|
#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
|
#endif
|
||||||
|
|
||||||
if (!featureSettings.isEnabled(DeprecatedFeature::NixPathShadow)) {
|
if (!featureSettings.isEnabled(DeprecatedFeature::NixPathShadow)) {
|
||||||
for (auto & [prefix, path] : searchPath_.elements) {
|
for (auto & [prefix, path] : searchPath_.elements) {
|
||||||
// Match on the 'nix' prefix
|
// Match on the 'nix' prefix
|
||||||
if (prefix.s == "nix") {
|
if (prefix.s == "nix") {
|
||||||
throw EvalError(HintFmt(
|
logWarning(
|
||||||
"The prefix '%s' is reserved for internal use by Lix in the Nix search "
|
{.msg = HintFmt(
|
||||||
"path, its usage is deprecated and will be forbidden in the future.\n"
|
"The prefix '%s' is reserved for internal use by Lix in the Nix search "
|
||||||
"Use %s to silence this error.\n"
|
"path, its usage is deprecated and will be forbidden in the future.\n"
|
||||||
"This is due to adding '%s=%s' in the Nix search path, either through the "
|
"Use %s to silence this warning.\n"
|
||||||
"environment variable '%s' or by passing the flag %s to the nix "
|
"This is due to adding '%s=%s' in the Nix search path, either through the "
|
||||||
"invocation.",
|
"environment variable '%s' or by passing the flag %s to the nix "
|
||||||
"nix",
|
"invocation.",
|
||||||
"--extra-deprecated-features nix-path-shadow",
|
"nix",
|
||||||
prefix.s,
|
"--extra-deprecated-features nix-path-shadow",
|
||||||
path.s,
|
prefix.s,
|
||||||
"NIX_PATH",
|
path.s,
|
||||||
"-I"
|
"NIX_PATH",
|
||||||
));
|
"-I"
|
||||||
|
)}
|
||||||
|
);
|
||||||
} else
|
} else
|
||||||
// Match prefixless paths that contain a `nix` directory
|
// Match prefixless paths that contain a `nix` directory
|
||||||
if (auto res =
|
if (auto res =
|
||||||
@@ -332,19 +333,21 @@ EvalPaths::EvalPaths(
|
|||||||
});
|
});
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
throw EvalError(HintFmt(
|
logWarning(
|
||||||
"Shadowing '%s' by configuring the nix-path is deprecated and "
|
{.msg = HintFmt(
|
||||||
"will be forbidden in the future.\n"
|
"Shadowing '%s' by configuring the nix-path is deprecated and "
|
||||||
"Use %s to silence this error.\n"
|
"will be forbidden in the future.\n"
|
||||||
"This is due to adding '%s' to the nix-path without a prefix, "
|
"Use %s to silence this warning.\n"
|
||||||
"either by passing the flag '-I %s' to the nix invocation or by "
|
"This is due to adding '%s' to the nix-path without a prefix, "
|
||||||
"adding this path to the environment variable '%s'.",
|
"either by passing the flag '-I %s' to the nix invocation or by "
|
||||||
"<nix/...>",
|
"adding this path to the environment variable '%s'.",
|
||||||
"--extra-deprecated-features nix-path-shadow",
|
"<nix/...>",
|
||||||
path.s,
|
"--extra-deprecated-features nix-path-shadow",
|
||||||
path.s,
|
path.s,
|
||||||
"NIX_PATH"
|
path.s,
|
||||||
));
|
"NIX_PATH"
|
||||||
|
)}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -434,11 +437,11 @@ void EvalPaths::allowPath(const StorePath & storePath)
|
|||||||
allowPath(store->toRealPath(storePath));
|
allowPath(store->toRealPath(storePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
Value EvalPaths::allowAndSetStorePathString(const StorePath & storePath)
|
void EvalPaths::allowAndSetStorePathString(const StorePath & storePath, Value & v)
|
||||||
{
|
{
|
||||||
allowPath(storePath);
|
allowPath(storePath);
|
||||||
|
|
||||||
return mkStorePathString(storePath);
|
mkStorePathString(storePath, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckedSourcePath EvalPaths::checkSourcePath(const SourcePath & path_)
|
CheckedSourcePath EvalPaths::checkSourcePath(const SourcePath & path_)
|
||||||
@@ -733,13 +736,13 @@ void mapStaticEnvBindings(const SymbolTable & st, const StaticEnv & se, const En
|
|||||||
// add 'with' bindings.
|
// add 'with' bindings.
|
||||||
Bindings::iterator j = env.values[0].attrs()->begin();
|
Bindings::iterator j = env.values[0].attrs()->begin();
|
||||||
while (j != env.values[0].attrs()->end()) {
|
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;
|
++j;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// iterate through staticenv bindings and add them.
|
// iterate through staticenv bindings and add them.
|
||||||
for (auto & i : se.vars)
|
for (auto & i : se.vars)
|
||||||
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};
|
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);
|
auto origin = ctx.positions.originOf(p);
|
||||||
if (auto path = std::get_if<CheckedSourcePath>(&origin)) {
|
if (auto path = std::get_if<CheckedSourcePath>(&origin)) {
|
||||||
auto attrs = ctx.buildBindings(3);
|
auto attrs = ctx.buildBindings(3);
|
||||||
attrs.insert(ctx.symbols.sym_file, {NewValueAs::string, path->to_string()});
|
attrs.alloc(ctx.symbols.sym_file) = {NewValueAs::string, path->to_string()};
|
||||||
auto [line, col] = makePositionThunks(*this, p);
|
makePositionThunks(*this, p, attrs.alloc(ctx.symbols.sym_line), attrs.alloc(ctx.symbols.sym_column));
|
||||||
attrs.insert(ctx.symbols.sym_line, line);
|
v = {NewValueAs::attrs, attrs};
|
||||||
attrs.insert(ctx.symbols.sym_column, col);
|
|
||||||
return {NewValueAs::attrs, attrs};
|
|
||||||
} else
|
} else
|
||||||
return Value::VNULL;
|
v.mkNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
Value EvalPaths::mkStorePathString(const StorePath & p)
|
|
||||||
|
void EvalPaths::mkStorePathString(const StorePath & p, Value & v)
|
||||||
{
|
{
|
||||||
return {
|
v = {
|
||||||
NewValueAs::string,
|
NewValueAs::string,
|
||||||
store->printStorePath(p),
|
store->printStorePath(p),
|
||||||
NixStringContext{
|
NixStringContext{
|
||||||
@@ -883,9 +885,13 @@ std::string EvalState::mkOutputStringRaw(
|
|||||||
return ctx.store->printStorePath(staticOutputPath);
|
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++) {
|
for (unsigned i = 0; i < argsLeft; i++) {
|
||||||
pargs[i] = &args[i];
|
pargs[i] = &args[i];
|
||||||
}
|
}
|
||||||
vCur = fn->fun(*this, pargs.data());
|
fn->fun(*this, pargs.data(), vCur);
|
||||||
} catch (ThrownError & e) {
|
} catch (ThrownError & e) {
|
||||||
// Distinguish between an error that simply happened while "throw"
|
// Distinguish between an error that simply happened while "throw"
|
||||||
// was being evaluated and an explicit thrown error.
|
// 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");
|
e.addTrace(ctx.positions[pos], "caused by explicit %s", "throw");
|
||||||
} else {
|
} else {
|
||||||
e.addTrace(ctx.positions[pos], "while calling the '%s' builtin", fn->name);
|
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.
|
// 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)
|
// 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.
|
// 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) {
|
} catch (Error & e) {
|
||||||
e.addTrace(ctx.positions[pos], "while calling the '%1%' builtin", fn->name);
|
e.addTrace(ctx.positions[pos], "while calling the '%1%' builtin", fn->name);
|
||||||
throw;
|
throw;
|
||||||
@@ -1358,49 +1364,9 @@ https://docs.lix.systems/manual/lix/stable/language/constructs.html#functions)",
|
|||||||
return callFunction(fun, vAttrs, pos);
|
return callFunction(fun, vAttrs, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
Value EvalState::updateAttrs(const Value & v1, const Value & v2)
|
void EvalState::concatLists(
|
||||||
{
|
Value & v, std::span<Value> lists, const PosIdx pos, std::string_view errorCtx
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
ctx.stats.nrListConcats++;
|
ctx.stats.nrListConcats++;
|
||||||
|
|
||||||
@@ -1416,10 +1382,12 @@ Value EvalState::concatLists(std::span<Value> lists, const PosIdx pos, std::stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (nonEmpty && len == nonEmpty->listSize()) {
|
if (nonEmpty && len == nonEmpty->listSize()) {
|
||||||
return *nonEmpty;
|
v = *nonEmpty;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto list = ctx.mem.newList(len);
|
auto list = ctx.mem.newList(len);
|
||||||
|
v = {NewValueAs::list, list};
|
||||||
auto out = list->elems;
|
auto out = list->elems;
|
||||||
for (size_t n = 0, pos = 0; n < lists.size(); ++n) {
|
for (size_t n = 0, pos = 0; n < lists.size(); ++n) {
|
||||||
auto l = lists[n].listSize();
|
auto l = lists[n].listSize();
|
||||||
@@ -1428,7 +1396,6 @@ Value EvalState::concatLists(std::span<Value> lists, const PosIdx pos, std::stri
|
|||||||
}
|
}
|
||||||
pos += l;
|
pos += l;
|
||||||
}
|
}
|
||||||
return {NewValueAs::list, list};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// always force this to be separate, otherwise forceValue may inline it and take
|
// always force this to be separate, otherwise forceValue may inline it and take
|
||||||
|
|||||||
+11
-6
@@ -370,7 +370,7 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Allow access to a store path and return it as a string.
|
* 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
|
* 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
|
* The string is the printed store path with a context containing a
|
||||||
* single `NixStringContextElem::Opaque` element of that store path.
|
* single `NixStringContextElem::Opaque` element of that store path.
|
||||||
*/
|
*/
|
||||||
Value mkStorePathString(const StorePath & storePath);
|
void mkStorePathString(const StorePath & storePath, Value & v);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct EvalStatistics
|
struct EvalStatistics
|
||||||
@@ -800,7 +800,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
Value autoCallFunction(Bindings & args, Value & fun, PosIdx pos);
|
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`.
|
* Create a string representing a `SingleDerivedPath::Built`.
|
||||||
@@ -809,13 +809,18 @@ public:
|
|||||||
* single `NixStringContextElem::Built` element of the drv path and
|
* single `NixStringContextElem::Built` element of the drv path and
|
||||||
* output name.
|
* output name.
|
||||||
*
|
*
|
||||||
|
* @param value Value we are settings
|
||||||
|
*
|
||||||
* @param b the drv whose output we are making a string for, and the
|
* @param b the drv whose output we are making a string for, and the
|
||||||
* output
|
* output
|
||||||
*
|
*
|
||||||
* @param staticOutputPath Output path for that string.
|
* @param staticOutputPath Output path for that string.
|
||||||
* Will be printed to form 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`.
|
* Create a string representing a `SingleDerivedPath`.
|
||||||
@@ -826,8 +831,8 @@ public:
|
|||||||
const SingleDerivedPath & p,
|
const SingleDerivedPath & p,
|
||||||
Value & v);
|
Value & v);
|
||||||
|
|
||||||
Value updateAttrs(const Value & v1, const Value & v2);
|
void
|
||||||
Value concatLists(std::span<Value> lists, const PosIdx pos, std::string_view errorCtx);
|
concatLists(Value & v, std::span<Value> lists, const PosIdx pos, std::string_view errorCtx);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
|||||||
@@ -8,24 +8,25 @@ namespace nix {
|
|||||||
class EvalState;
|
class EvalState;
|
||||||
struct Value;
|
struct Value;
|
||||||
|
|
||||||
Value prim_addDrvOutputDependencies(EvalState & state, Value ** args);
|
void prim_addDrvOutputDependencies(EvalState & state, Value * * args, Value & v);
|
||||||
Value prim_fetchTree(EvalState & state, Value ** args);
|
void prim_fetchTree(EvalState & state, Value * * args, Value & v);
|
||||||
Value prim_fetchGit(EvalState & state, Value ** args);
|
void prim_fetchGit(EvalState & state, Value * * args, Value & v);
|
||||||
Value prim_fetchMercurial(EvalState & state, Value ** args);
|
void prim_fetchMercurial(EvalState & state, Value ** args, Value & v);
|
||||||
Value prim_fetchTarball(EvalState & state, Value ** args);
|
void prim_fetchTarball(EvalState & state, Value * * args, Value & v);
|
||||||
Value prim_fetchurl(EvalState & state, Value ** args);
|
void prim_fetchurl(EvalState & state, Value * * args, Value & v);
|
||||||
Value prim_fromTOML(EvalState & state, Value ** args);
|
void prim_fromTOML(EvalState & state, Value * * args, Value & v);
|
||||||
Value prim_appendContext(EvalState & state, Value ** args);
|
void prim_appendContext(EvalState & state, Value ** args, Value & v);
|
||||||
Value prim_getContext(EvalState & state, Value ** args);
|
void prim_getContext(EvalState & state, Value * * args, Value & v);
|
||||||
Value prim_hasContext(EvalState & state, Value ** args);
|
void prim_hasContext(EvalState & state, Value * * args, Value & v);
|
||||||
Value prim_unsafeDiscardOutputDependency(EvalState & state, Value ** args);
|
void prim_unsafeDiscardOutputDependency(EvalState & state, Value * * args, Value & v);
|
||||||
Value prim_unsafeDiscardStringContext(EvalState & state, Value ** args);
|
void prim_unsafeDiscardStringContext(EvalState & state, Value ** args, Value & v);
|
||||||
|
|
||||||
namespace flake {
|
namespace flake {
|
||||||
|
|
||||||
Value prim_flakeRefToString(EvalState & state, Value ** args);
|
void prim_flakeRefToString(EvalState & state, Value * * args, Value & v);
|
||||||
Value prim_getFlake(EvalState & state, Value ** args);
|
void prim_getFlake(EvalState & state, Value * * args, Value & v);
|
||||||
Value prim_parseFlakeRef(EvalState & state, Value ** args);
|
void prim_parseFlakeRef(EvalState & state, Value * * args, Value & v);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-16
@@ -942,7 +942,10 @@ LockedFlake lockFlake(
|
|||||||
|
|
||||||
Value callFlake(EvalState & state, const LockedFlake & lockedFlake)
|
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(
|
Value vRootSrc = emitTreeAttrs(
|
||||||
state.ctx,
|
state.ctx,
|
||||||
@@ -952,13 +955,14 @@ Value callFlake(EvalState & state, const LockedFlake & lockedFlake)
|
|||||||
lockedFlake.flake.forceDirty
|
lockedFlake.flake.forceDirty
|
||||||
);
|
);
|
||||||
|
|
||||||
Value vRootSubdir = {NewValueAs::string, lockedFlake.flake.lockedRef.subdir};
|
vRootSubdir = {NewValueAs::string, lockedFlake.flake.lockedRef.subdir};
|
||||||
|
|
||||||
if (!state.ctx.caches.vCallFlake) {
|
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"
|
#include "call-flake.nix.gen.hh"
|
||||||
, CanonPath::root
|
, CanonPath::root
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Value vTmp1 = state.callFunction(*state.ctx.caches.vCallFlake, vLocks, noPos);
|
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);
|
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"));
|
std::string flakeRefS(state.forceStringNoCtx(*args[0], noPos, "while evaluating the argument passed to builtins.getFlake"));
|
||||||
auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
|
auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
|
||||||
if (evalSettings.pureEval && !flakeRef.input.isLocked())
|
if (evalSettings.pureEval && !flakeRef.input.isLocked())
|
||||||
throw Error("cannot call 'getFlake' on unlocked flake reference '%s' (use --impure to override)", flakeRefS);
|
throw Error("cannot call 'getFlake' on unlocked flake reference '%s' (use --impure to override)", flakeRefS);
|
||||||
|
|
||||||
return callFlake(
|
v = callFlake(
|
||||||
state,
|
state,
|
||||||
lockFlake(
|
lockFlake(
|
||||||
state,
|
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,
|
std::string flakeRefS(state.forceStringNoCtx(*args[0], noPos,
|
||||||
"while evaluating the argument passed to builtins.parseFlakeRef"));
|
"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());
|
auto binds = state.ctx.buildBindings(attrs.size());
|
||||||
for (const auto & [key, value] : attrs) {
|
for (const auto & [key, value] : attrs) {
|
||||||
auto s = state.ctx.symbols.create(key);
|
auto s = state.ctx.symbols.create(key);
|
||||||
Value vv = std::visit(
|
auto & vv = binds.alloc(s);
|
||||||
|
std::visit(
|
||||||
overloaded{
|
overloaded{
|
||||||
[](const std::string & value) -> Value { return {NewValueAs::string, value}; },
|
[&vv](const std::string & value) { vv = {NewValueAs::string, value}; },
|
||||||
[](const uint64_t & value) -> Value { return {NewValueAs::integer, NixInt::Inner(value)}; },
|
[&vv](const uint64_t & value) { vv = {NewValueAs::integer, NixInt::Inner(value)}; },
|
||||||
[](const Explicit<bool> & value) -> Value { return {NewValueAs::boolean, value.t}; }
|
[&vv](const Explicit<bool> & value) { vv = {NewValueAs::boolean, value.t}; }
|
||||||
},
|
},
|
||||||
value
|
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,
|
state.forceAttrs(*args[0], noPos,
|
||||||
"while evaluating the argument passed to builtins.flakeRefToString");
|
"while evaluating the argument passed to builtins.flakeRefToString");
|
||||||
fetchers::Attrs attrs;
|
fetchers::Attrs attrs;
|
||||||
for (const auto & attr : *args[0]->attrs()) {
|
for (const auto & attr : *args[0]->attrs()) {
|
||||||
state.forceValue(attr.value, noPos);
|
|
||||||
auto t = attr.value.type();
|
auto t = attr.value.type();
|
||||||
if (t == nInt) {
|
if (t == nInt) {
|
||||||
auto intValue = attr.value.integer().value;
|
auto intValue = attr.value.integer().value;
|
||||||
@@ -1042,7 +1051,7 @@ Value prim_flakeRefToString(EvalState & state, Value ** args)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto flakeRef = FlakeRef::fromAttrs(attrs);
|
auto flakeRef = FlakeRef::fromAttrs(attrs);
|
||||||
return {NewValueAs::string, flakeRef.to_string()};
|
v = {NewValueAs::string, flakeRef.to_string()};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,39 +14,24 @@ class JSONSax : nlohmann::json_sax<JSON> {
|
|||||||
class JSONState {
|
class JSONState {
|
||||||
protected:
|
protected:
|
||||||
std::unique_ptr<JSONState> parent;
|
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;
|
RootValue v;
|
||||||
public:
|
public:
|
||||||
std::unique_ptr<JSONState> resolve(EvalState &) override
|
virtual std::unique_ptr<JSONState> resolve(EvalState &)
|
||||||
{
|
{
|
||||||
assert(false && "tried to close toplevel json parser state");
|
assert(false && "tried to close toplevel json parser state");
|
||||||
}
|
}
|
||||||
TopLevelJSONState() = default;
|
explicit JSONState(std::unique_ptr<JSONState> && p) : parent(std::move(p)) {}
|
||||||
TopLevelJSONState(TopLevelJSONState & p) = delete;
|
JSONState() = default;
|
||||||
Value & finalValue() override
|
JSONState(JSONState & p) = delete;
|
||||||
|
Value & value()
|
||||||
{
|
{
|
||||||
assert(v && "tried to read nonexistent final value from json parser");
|
if (!v) {
|
||||||
|
v = allocRootValue({});
|
||||||
|
}
|
||||||
return *v;
|
return *v;
|
||||||
}
|
}
|
||||||
void addValue(Value v) override
|
virtual ~JSONState() {}
|
||||||
{
|
virtual void add() {}
|
||||||
assert(!this->v && "duplicate value in toplevel JSON scope");
|
|
||||||
this->v = allocRootValue(v);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class JSONObjectState : public JSONState {
|
class JSONObjectState : public JSONState {
|
||||||
@@ -58,12 +43,13 @@ class JSONSax : nlohmann::json_sax<JSON> {
|
|||||||
auto attrs2 = state.ctx.buildBindings(attrs.size());
|
auto attrs2 = state.ctx.buildBindings(attrs.size());
|
||||||
for (auto & i : attrs)
|
for (auto & i : attrs)
|
||||||
attrs2.insert(i.first, i.second);
|
attrs2.insert(i.first, i.second);
|
||||||
parent->addValue({NewValueAs::attrs, attrs2.alreadySorted()});
|
parent->value() = {NewValueAs::attrs, attrs2.alreadySorted()};
|
||||||
return std::move(parent);
|
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:
|
public:
|
||||||
void key(string_t & name, EvalState & state)
|
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
|
std::unique_ptr<JSONState> resolve(EvalState & state) override
|
||||||
{
|
{
|
||||||
auto list = state.ctx.mem.newList(values.size());
|
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) {
|
for (size_t n = 0; n < values.size(); ++n) {
|
||||||
list->elems[n] = values[n];
|
list->elems[n] = values[n];
|
||||||
}
|
}
|
||||||
return std::move(parent);
|
return std::move(parent);
|
||||||
}
|
}
|
||||||
void addValue(Value v) override
|
void add() override
|
||||||
{
|
{
|
||||||
values.push_back(v);
|
values.push_back(*v);
|
||||||
|
v = nullptr;
|
||||||
}
|
}
|
||||||
public:
|
public:
|
||||||
JSONListState(std::unique_ptr<JSONState> && p, std::size_t reserve) : JSONState(std::move(p))
|
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;
|
std::unique_ptr<JSONState> rs;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
JSONSax(EvalState & state) : state(state), rs(new TopLevelJSONState()) {};
|
JSONSax(EvalState & state) : state(state), rs(new JSONState()) {};
|
||||||
|
|
||||||
Value result()
|
Value result()
|
||||||
{
|
{
|
||||||
return rs->finalValue();
|
return rs->value();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool null() override
|
bool null() override
|
||||||
{
|
{
|
||||||
rs->addValue(Value::VNULL);
|
rs->value().mkNull();
|
||||||
|
rs->add();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool boolean(bool val) override
|
bool boolean(bool val) override
|
||||||
{
|
{
|
||||||
rs->addValue({NewValueAs::boolean, val});
|
rs->value() = {NewValueAs::boolean, val};
|
||||||
|
rs->add();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool number_integer(number_integer_t val) override
|
bool number_integer(number_integer_t val) override
|
||||||
{
|
{
|
||||||
rs->addValue({NewValueAs::integer, val});
|
rs->value() = {NewValueAs::integer, val};
|
||||||
|
rs->add();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,19 +121,22 @@ public:
|
|||||||
return number_float(static_cast<number_float_t>(val_), "");
|
return number_float(static_cast<number_float_t>(val_), "");
|
||||||
}
|
}
|
||||||
NixInt::Inner val = val_;
|
NixInt::Inner val = val_;
|
||||||
rs->addValue({NewValueAs::integer, val});
|
rs->value() = {NewValueAs::integer, val};
|
||||||
|
rs->add();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool number_float(number_float_t val, const string_t & s) override
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool string(string_t & val) override
|
bool string(string_t & val) override
|
||||||
{
|
{
|
||||||
rs->addValue({NewValueAs::string, val});
|
rs->value() = {NewValueAs::string, val};
|
||||||
|
rs->add();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +163,7 @@ public:
|
|||||||
|
|
||||||
bool end_object() override {
|
bool end_object() override {
|
||||||
rs = rs->resolve(state);
|
rs = rs->resolve(state);
|
||||||
|
rs->add();
|
||||||
return true;
|
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);
|
JSONSax parser(state);
|
||||||
bool res = JSON::sax_parse(s_, &parser);
|
bool res = JSON::sax_parse(s_, &parser);
|
||||||
if (!res)
|
if (!res)
|
||||||
throw JSONParseError("Invalid JSON Value");
|
throw JSONParseError("Invalid JSON Value");
|
||||||
return parser.result();
|
v = parser.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,5 +12,6 @@ struct Value;
|
|||||||
|
|
||||||
MakeError(JSONParseError, Error);
|
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
@@ -130,10 +130,6 @@ public:
|
|||||||
virtual void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) = 0;
|
virtual void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) = 0;
|
||||||
virtual Value eval(EvalState & state, Env & env);
|
virtual Value eval(EvalState & state, Env & env);
|
||||||
virtual Value maybeThunk(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);
|
virtual void setName(Symbol name);
|
||||||
PosIdx getPos() const { return pos; }
|
PosIdx getPos() const { return pos; }
|
||||||
|
|
||||||
@@ -179,7 +175,7 @@ struct ExprLiteral : Expr
|
|||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
Value v;
|
Value v;
|
||||||
ExprLiteral(const PosIdx pos, Value v) : Expr(pos), v(v) {};
|
ExprLiteral(const PosIdx pos) : Expr(pos) {};
|
||||||
public:
|
public:
|
||||||
Value maybeThunk(EvalState & state, Env & env) override;
|
Value maybeThunk(EvalState & state, Env & env) override;
|
||||||
JSON toJSON(const SymbolTable & symbols) const 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); }
|
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)
|
Value::Int i;
|
||||||
: tuple({{Value::Acb::tInt}, n})
|
ExprInt(const PosIdx pos, NixInt n) : ExprLiteral(pos), i{{Value::Acb::tInt}, n}
|
||||||
, ExprLiteral(
|
|
||||||
pos,
|
|
||||||
Value::isTaggableInteger(n) ? Value{NewValueAs::integer, n} : Value(std::get<Value::Int>(*this))
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
|
v = Value::isTaggableInteger(n) ? Value{NewValueAs::integer, n} : Value(i);
|
||||||
}
|
}
|
||||||
ExprInt(const PosIdx pos, NixInt::Inner n) : ExprInt(pos, NixInt(n)) {}
|
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)
|
ExprFloat(const PosIdx pos, NewValueAs::floating_t, double f)
|
||||||
: tuple({{Value::Acb::tFloat}, f})
|
: ExprLiteral(pos)
|
||||||
, ExprLiteral(pos, Value(std::get<Value::Float>(*this)))
|
, f{{Value::Acb::tFloat}, f}
|
||||||
{
|
{
|
||||||
|
v = Value(this->f);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ExprStringBase
|
struct ExprString : ExprLiteral
|
||||||
{
|
{
|
||||||
std::unique_ptr<Value::Str, Value::Str::Deleter> contents;
|
std::unique_ptr<Value::Str, Value::Str::Deleter> contents;
|
||||||
Value::String strcb;
|
Value::String strcb{.content = contents.get(), .context = nullptr};
|
||||||
protected:
|
ExprString(const PosIdx pos, std::string s) : ExprLiteral(pos), contents(Value::Str::copy(s))
|
||||||
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})
|
|
||||||
{
|
{
|
||||||
|
v = {NewValueAs::string, &strcb};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string_view str() const
|
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)
|
std::unique_ptr<Value::Str, Value::Str::Deleter> contents;
|
||||||
: ExprStringBase(s, Value::String::path)
|
Value::String strcb{.content = contents.get(), .context = Value::String::path};
|
||||||
, ExprLiteral(pos, Value{NewValueAs::path, &strcb})
|
ExprPath(const PosIdx pos, std::string s) : ExprLiteral(pos), contents(Value::Str::copy(s))
|
||||||
{
|
{
|
||||||
v = Value{NewValueAs::path, &strcb};
|
v = Value{NewValueAs::path, &strcb};
|
||||||
}
|
}
|
||||||
|
|||||||
+334
-348
File diff suppressed because it is too large
Load Diff
@@ -48,12 +48,13 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Load a ValueInitializer from a DSO and return whatever it initializes
|
* 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
|
* 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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,21 +7,22 @@
|
|||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
Value prim_unsafeDiscardStringContext(EvalState & state, Value ** args)
|
void prim_unsafeDiscardStringContext(EvalState & state, Value ** args, Value & v)
|
||||||
{
|
{
|
||||||
NixStringContext context;
|
NixStringContext context;
|
||||||
auto s = state.coerceToString(noPos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardStringContext");
|
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;
|
NixStringContext context;
|
||||||
state.forceString(*args[0], context, noPos, "while evaluating the argument passed to builtins.hasContext");
|
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;
|
NixStringContext context;
|
||||||
auto s = state.coerceToString(noPos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardOutputDependency");
|
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;
|
NixStringContext context;
|
||||||
auto s = state.coerceToString(noPos, *args[0], context, "while evaluating the argument passed to builtins.addDrvOutputDependencies");
|
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) }),
|
}, 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
|
Note that for a given path any combination of the above attributes
|
||||||
may be present.
|
may be present.
|
||||||
*/
|
*/
|
||||||
Value prim_getContext(EvalState & state, Value ** args)
|
void prim_getContext(EvalState & state, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
struct ContextInfo {
|
struct ContextInfo {
|
||||||
bool path = false;
|
bool path = false;
|
||||||
@@ -134,19 +136,20 @@ Value prim_getContext(EvalState & state, Value ** args)
|
|||||||
for (const auto & info : contextInfos) {
|
for (const auto & info : contextInfos) {
|
||||||
auto infoAttrs = state.ctx.buildBindings(3);
|
auto infoAttrs = state.ctx.buildBindings(3);
|
||||||
if (info.second.path)
|
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)
|
if (info.second.allOutputs)
|
||||||
infoAttrs.insert(sAllOutputs, {NewValueAs::boolean, true});
|
infoAttrs.alloc(sAllOutputs) = {NewValueAs::boolean, true};
|
||||||
if (!info.second.outputs.empty()) {
|
if (!info.second.outputs.empty()) {
|
||||||
|
auto & outputsVal = infoAttrs.alloc(state.ctx.symbols.sym_outputs);
|
||||||
auto content = state.ctx.mem.newList(info.second.outputs.size());
|
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))
|
for (const auto & [i, output] : enumerate(info.second.outputs))
|
||||||
content->elems[i] = {NewValueAs::string, output};
|
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
|
See the commentary above unsafeGetContext for details of the
|
||||||
context representation.
|
context representation.
|
||||||
*/
|
*/
|
||||||
Value prim_appendContext(EvalState & state, Value ** args)
|
void prim_appendContext(EvalState & state, Value ** args, Value & v)
|
||||||
{
|
{
|
||||||
NixStringContext context;
|
NixStringContext context;
|
||||||
auto orig = state.forceString(*args[0], context, noPos, "while evaluating the first argument passed to builtins.appendContext");
|
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};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
Value prim_fetchMercurial(EvalState & state, Value ** args)
|
void prim_fetchMercurial(EvalState & state, Value ** args, Value & v)
|
||||||
{
|
{
|
||||||
std::string url;
|
std::string url;
|
||||||
std::optional<Hash> rev;
|
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 [tree, input2] = state.aio.blockOn(input.fetch(state.ctx.store));
|
||||||
|
|
||||||
auto attrs2 = state.ctx.buildBindings(8);
|
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())
|
if (input2.getRef())
|
||||||
attrs2.insert("branch", {NewValueAs::string, *input2.getRef()});
|
attrs2.alloc("branch") = {NewValueAs::string, *input2.getRef()};
|
||||||
// Backward compatibility: set 'rev' to
|
// Backward compatibility: set 'rev' to
|
||||||
// 0000000000000000000000000000000000000000 for a dirty tree.
|
// 0000000000000000000000000000000000000000 for a dirty tree.
|
||||||
auto rev2 = input2.getRev().value_or(Hash(HashType::SHA1));
|
auto rev2 = input2.getRev().value_or(Hash(HashType::SHA1));
|
||||||
attrs2.insert("rev", {NewValueAs::string, rev2.gitRev()});
|
attrs2.alloc("rev") = {NewValueAs::string, rev2.gitRev()};
|
||||||
attrs2.insert("shortRev", {NewValueAs::string, rev2.gitRev().substr(0, 12)});
|
attrs2.alloc("shortRev") = {NewValueAs::string, rev2.gitRev().substr(0, 12)};
|
||||||
if (auto revCount = input2.getRevCount())
|
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);
|
state.ctx.paths.allowPath(tree.storePath);
|
||||||
return {NewValueAs::attrs, attrs2};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,51 +26,49 @@ Value emitTreeAttrs(
|
|||||||
|
|
||||||
auto attrs = state.buildBindings(10);
|
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.
|
// FIXME: support arbitrary input attributes.
|
||||||
|
|
||||||
auto narHash = input.getNarHash();
|
auto narHash = input.getNarHash();
|
||||||
assert(narHash);
|
assert(narHash);
|
||||||
attrs.insert("narHash", {NewValueAs::string, narHash->to_string()});
|
attrs.alloc("narHash") = {NewValueAs::string, narHash->to_string()};
|
||||||
|
|
||||||
if (input.getType() == "git")
|
if (input.getType() == "git")
|
||||||
attrs.insert(
|
attrs.alloc("submodules") = {
|
||||||
"submodules",
|
NewValueAs::boolean, fetchers::maybeGetBoolAttr(input.attrs, "submodules").value_or(false)
|
||||||
{NewValueAs::boolean, fetchers::maybeGetBoolAttr(input.attrs, "submodules").value_or(false)}
|
};
|
||||||
);
|
|
||||||
|
|
||||||
if (!forceDirty) {
|
if (!forceDirty) {
|
||||||
|
|
||||||
if (auto rev = input.getRev()) {
|
if (auto rev = input.getRev()) {
|
||||||
attrs.insert("rev", {NewValueAs::string, rev->gitRev()});
|
attrs.alloc("rev") = {NewValueAs::string, rev->gitRev()};
|
||||||
attrs.insert("shortRev", {NewValueAs::string, rev->gitShortRev()});
|
attrs.alloc("shortRev") = {NewValueAs::string, rev->gitShortRev()};
|
||||||
} else if (emptyRevFallback) {
|
} else if (emptyRevFallback) {
|
||||||
// Backwards compat for `builtins.fetchGit`: dirty repos return an empty sha1 as rev
|
// Backwards compat for `builtins.fetchGit`: dirty repos return an empty sha1 as rev
|
||||||
auto emptyHash = Hash(HashType::SHA1);
|
auto emptyHash = Hash(HashType::SHA1);
|
||||||
attrs.insert("rev", {NewValueAs::string, emptyHash.gitRev()});
|
attrs.alloc("rev") = {NewValueAs::string, emptyHash.gitRev()};
|
||||||
attrs.insert("shortRev", {NewValueAs::string, emptyHash.gitShortRev()});
|
attrs.alloc("shortRev") = {NewValueAs::string, emptyHash.gitShortRev()};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto revCount = input.getRevCount())
|
if (auto revCount = input.getRevCount())
|
||||||
attrs.insert("revCount", {NewValueAs::integer, NixInt::Inner(*revCount)});
|
attrs.alloc("revCount") = {NewValueAs::integer, NixInt::Inner(*revCount)};
|
||||||
else if (emptyRevFallback)
|
else if (emptyRevFallback)
|
||||||
attrs.insert("revCount", {NewValueAs::integer, 0});
|
attrs.alloc("revCount") = {NewValueAs::integer, 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto dirtyRev = fetchers::maybeGetStrAttr(input.attrs, "dirtyRev")) {
|
if (auto dirtyRev = fetchers::maybeGetStrAttr(input.attrs, "dirtyRev")) {
|
||||||
attrs.insert("dirtyRev", {NewValueAs::string, *dirtyRev});
|
attrs.alloc("dirtyRev") = {NewValueAs::string, *dirtyRev};
|
||||||
attrs.insert(
|
attrs.alloc("dirtyShortRev") = {
|
||||||
"dirtyShortRev", {NewValueAs::string, *fetchers::maybeGetStrAttr(input.attrs, "dirtyShortRev")}
|
NewValueAs::string, *fetchers::maybeGetStrAttr(input.attrs, "dirtyShortRev")
|
||||||
);
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto lastModified = input.getLastModified()) {
|
if (auto lastModified = input.getLastModified()) {
|
||||||
attrs.insert("lastModified", {NewValueAs::integer, *lastModified});
|
attrs.alloc("lastModified") = {NewValueAs::integer, *lastModified};
|
||||||
attrs.insert(
|
attrs.alloc("lastModifiedDate") = {
|
||||||
"lastModifiedDate",
|
NewValueAs::string, fmt("%s", std::put_time(std::gmtime(&*lastModified), "%Y%m%d%H%M%S"))
|
||||||
{NewValueAs::string, fmt("%s", std::put_time(std::gmtime(&*lastModified), "%Y%m%d%H%M%S"))}
|
};
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {NewValueAs::attrs, attrs};
|
return {NewValueAs::attrs, attrs};
|
||||||
@@ -108,14 +106,14 @@ struct FetchTreeParams {
|
|||||||
bool allowNameArgument = false;
|
bool allowNameArgument = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
static Value fetchTree(
|
static void fetchTree(
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
const PosIdx pos,
|
const PosIdx pos,
|
||||||
Value ** args,
|
Value * * args,
|
||||||
|
Value & v,
|
||||||
std::optional<std::string> type,
|
std::optional<std::string> type,
|
||||||
const FetchTreeParams & params = FetchTreeParams{}
|
const FetchTreeParams & params = FetchTreeParams{}
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
fetchers::Input input;
|
fetchers::Input input;
|
||||||
NixStringContext context;
|
NixStringContext context;
|
||||||
|
|
||||||
@@ -225,17 +223,16 @@ static Value fetchTree(
|
|||||||
|
|
||||||
state.ctx.paths.allowPath(tree.storePath);
|
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(
|
static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v,
|
||||||
EvalState & state, const PosIdx pos, Value ** args, const std::string & who, bool unpack, std::string name
|
const std::string & who, bool unpack, std::string name)
|
||||||
)
|
|
||||||
{
|
{
|
||||||
std::optional<std::string> url;
|
std::optional<std::string> url;
|
||||||
std::optional<Hash> expectedHash;
|
std::optional<Hash> expectedHash;
|
||||||
@@ -300,7 +297,8 @@ static Value fetch(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (state.aio.blockOn(state.ctx.store->isValidPath(expectedPath))) {
|
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(
|
fetchTree(state, noPos, args, v, "git", FetchTreeParams { .emptyRevFallback = true, .allowNameArgument = true });
|
||||||
state, noPos, args, "git", FetchTreeParams{.emptyRevFallback = true, .allowNameArgument = true}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
Value prim_fromTOML(EvalState & state, Value ** args)
|
void prim_fromTOML(EvalState & state, Value ** args, Value & val)
|
||||||
{
|
{
|
||||||
auto toml = state.forceStringNoCtx(
|
auto toml = state.forceStringNoCtx(
|
||||||
*args[0], noPos, "while evaluating the argument passed to builtins.fromTOML"
|
*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});
|
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()) {
|
switch (t.type()) {
|
||||||
case toml::value_t::table: {
|
case toml::value_t::table: {
|
||||||
auto table = toml::get<toml::table>(t);
|
auto table = toml::get<toml::table>(t);
|
||||||
auto attrs = state.ctx.buildBindings(table.size());
|
auto attrs = state.ctx.buildBindings(table.size());
|
||||||
|
|
||||||
for (auto & elem : table) {
|
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: {
|
case toml::value_t::array: {
|
||||||
auto array = toml::get<std::vector<toml::value>>(t);
|
auto array = toml::get<std::vector<toml::value>>(t);
|
||||||
|
|
||||||
size_t size = array.size();
|
size_t size = array.size();
|
||||||
auto list = state.ctx.mem.newList(size);
|
auto list = state.ctx.mem.newList(size);
|
||||||
|
v = {NewValueAs::list, list};
|
||||||
for (size_t i = 0; i < size; ++i) {
|
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:
|
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:
|
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:
|
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:
|
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::local_datetime:
|
||||||
case toml::value_t::offset_datetime:
|
case toml::value_t::offset_datetime:
|
||||||
case toml::value_t::local_date:
|
case toml::value_t::local_date:
|
||||||
case toml::value_t::local_time:
|
case toml::value_t::local_time:
|
||||||
// NOLINTNEXTLINE(lix-foreign-exceptions)
|
// NOLINTNEXTLINE(lix-foreign-exceptions)
|
||||||
throw std::runtime_error("Dates and times are not supported");
|
throw std::runtime_error("Dates and times are not supported");
|
||||||
|
break;
|
||||||
case toml::value_t::empty:
|
case toml::value_t::empty:
|
||||||
return Value::VNULL;
|
v.mkNull();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return visit(
|
visit(
|
||||||
|
val,
|
||||||
toml::parse(
|
toml::parse(
|
||||||
tomlStream,
|
tomlStream,
|
||||||
"fromTOML", /* the "filename" */
|
"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
|
} catch (std::exception & e) { // NOLINT(lix-foreign-exceptions) // TODO: toml::syntax_error
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ Each file is called with three arguments:
|
|||||||
except that it's available in
|
except that it's available in
|
||||||
[`pure-eval`](@docroot@/command-ref/conf-file.html#conf-pure-eval)
|
[`pure-eval`](@docroot@/command-ref/conf-file.html#conf-pure-eval)
|
||||||
mode).
|
mode).
|
||||||
2. The final top-level bindings produced by calling all
|
2. The top-level bindings produced by the previous `repl-overlays`
|
||||||
`repl-overlays`.
|
|
||||||
3. The top-level bindings produced by the previous `repl-overlays`
|
|
||||||
value (or the default top-level bindings).
|
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
|
For example, the following file would alias `pkgs` to
|
||||||
`legacyPackages.${info.currentSystem}` (if that attribute is defined):
|
`legacyPackages.${info.currentSystem}` (if that attribute is defined):
|
||||||
|
|||||||
@@ -12,10 +12,8 @@ namespace nix
|
|||||||
|
|
||||||
static const Value::List emptyListData{.size = 0};
|
static const Value::List emptyListData{.size = 0};
|
||||||
Value Value::EMPTY_LIST{Value::list_t{}, &emptyListData};
|
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}};
|
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(Value::String) >= Value::TAG_ALIGN);
|
||||||
static_assert(alignof(Bindings) >= Value::TAG_ALIGN);
|
static_assert(alignof(Bindings) >= Value::TAG_ALIGN);
|
||||||
|
|||||||
+11
-3
@@ -32,7 +32,7 @@ struct Value;
|
|||||||
/**
|
/**
|
||||||
* Function that implements a primop.
|
* 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
|
* Info about a primitive operation, and its implementation
|
||||||
@@ -356,8 +356,6 @@ public:
|
|||||||
* Empty list constant.
|
* Empty list constant.
|
||||||
*/
|
*/
|
||||||
static Value EMPTY_LIST;
|
static Value EMPTY_LIST;
|
||||||
static Value EMPTY_SET;
|
|
||||||
static Value VNULL;
|
|
||||||
|
|
||||||
struct String;
|
struct String;
|
||||||
struct Acb;
|
struct Acb;
|
||||||
@@ -766,6 +764,16 @@ public:
|
|||||||
*/
|
*/
|
||||||
inline ValueType type(bool invalidIsThunk = false) const;
|
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
|
bool isList() const
|
||||||
{
|
{
|
||||||
return internalType() == tList;
|
return internalType() == tList;
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ struct Parser
|
|||||||
buffer.clear(); \
|
buffer.clear(); \
|
||||||
std::move(str); \
|
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) \
|
#define READ_PADDING(size) \
|
||||||
do { \
|
do { \
|
||||||
if ((size) % 8) { \
|
if ((size) % 8) { \
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ChunkedVector(uint32_t reserve = 1)
|
ChunkedVector(uint32_t reserve)
|
||||||
{
|
{
|
||||||
chunks.reserve(reserve);
|
chunks.reserve(reserve);
|
||||||
addChunk();
|
addChunk();
|
||||||
|
|||||||
@@ -39,13 +39,13 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
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() = default;
|
||||||
LinearMap(size_t expectedSize)
|
LinearMap(size_t expectedSize)
|
||||||
{
|
{
|
||||||
reserve(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
|
/* Insert an element at the correct position, shifting later elements back by
|
||||||
* one place. Returns `true` if a previous element with that key was
|
* one place. Returns `true` if a previous element with that key was
|
||||||
@@ -148,13 +148,5 @@ public:
|
|||||||
{
|
{
|
||||||
return cend();
|
return cend();
|
||||||
}
|
}
|
||||||
const_reverse_iterator rbegin() const
|
|
||||||
{
|
|
||||||
return crbegin();
|
|
||||||
}
|
|
||||||
const_reverse_iterator rend() const
|
|
||||||
{
|
|
||||||
return crend();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
} // namespace nix
|
} // namespace nix
|
||||||
|
|||||||
@@ -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
|
into the lexical scope. You can load addition files using the `:l
|
||||||
<filename>` command, or reload all files using `:r`.
|
<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
@@ -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')
|
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
|
# 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')
|
if lowdown.version().version_compare('>= 3.0.0')
|
||||||
@@ -688,11 +693,6 @@ if cxx.get_id() in ['clang', 'gcc']
|
|||||||
language : 'cpp',
|
language : 'cpp',
|
||||||
)
|
)
|
||||||
endif
|
endif
|
||||||
add_project_arguments(
|
|
||||||
# likewise for rust
|
|
||||||
'--remap-path-prefix=../lix=lix',
|
|
||||||
language : 'rust',
|
|
||||||
)
|
|
||||||
|
|
||||||
if is_darwin
|
if is_darwin
|
||||||
fs.copyfile(
|
fs.copyfile(
|
||||||
@@ -718,24 +718,10 @@ capnpc_wrapper = custom_target(
|
|||||||
output : 'capnpc_wrapper',
|
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('lix')
|
||||||
subdir('scripts')
|
subdir('scripts')
|
||||||
subdir('misc')
|
subdir('misc')
|
||||||
|
|
||||||
coverage_objects = [nix, liblix_all]
|
|
||||||
|
|
||||||
if enable_docs
|
if enable_docs
|
||||||
subdir('doc/manual')
|
subdir('doc/manual')
|
||||||
endif
|
endif
|
||||||
@@ -753,9 +739,3 @@ endif
|
|||||||
subdir('meson/clang-tidy')
|
subdir('meson/clang-tidy')
|
||||||
|
|
||||||
subproject('nix-eval-jobs', required : enable_nix_eval_jobs)
|
subproject('nix-eval-jobs', required : enable_nix_eval_jobs)
|
||||||
|
|
||||||
if coverage
|
|
||||||
# targets defined by coverage
|
|
||||||
subdir('tests/coverage')
|
|
||||||
endif
|
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,3 @@ option('disable-fibers', type : 'boolean', value : false,
|
|||||||
option('builtin-dep-closure', type : 'array',
|
option('builtin-dep-closure', type : 'array',
|
||||||
description : 'dependency closure used for builtin builder sandboxes. the install paths are included automatically.',
|
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'
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
libcpuid,
|
libcpuid,
|
||||||
libseccomp,
|
libseccomp,
|
||||||
libsystemtap,
|
libsystemtap,
|
||||||
|
linuxPackages,
|
||||||
lix-clang-tidy ? null,
|
lix-clang-tidy ? null,
|
||||||
llvmPackages,
|
llvmPackages,
|
||||||
lsof,
|
lsof,
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ def setup_creds(env: RelengEnvironment):
|
|||||||
key = keys.get_ephemeral_key(env)
|
key = keys.get_ephemeral_key(env)
|
||||||
$AWS_SECRET_ACCESS_KEY = key.secret_key
|
$AWS_SECRET_ACCESS_KEY = key.secret_key
|
||||||
$AWS_ACCESS_KEY_ID = key.id
|
$AWS_ACCESS_KEY_ID = key.id
|
||||||
$AWS_DEFAULT_REGION = 'garage'
|
$AWS_DEFAULT_REGION = env.s3_region
|
||||||
$AWS_ENDPOINT_URL = env.s3_endpoint
|
$AWS_ENDPOINT_URL = env.s3_endpoint
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+20
-11
@@ -5,11 +5,12 @@ import functools
|
|||||||
import subprocess
|
import subprocess
|
||||||
import dataclasses
|
import dataclasses
|
||||||
|
|
||||||
S3_HOST = 's3.lix.systems'
|
S3_HOST = 's3-admin.afnix.fr'
|
||||||
|
S3_USER = 'lix-releng'
|
||||||
|
|
||||||
DEFAULT_STORE_URI_BITS = {
|
DEFAULT_STORE_URI_BITS = {
|
||||||
'region': 'garage',
|
'region': 'global',
|
||||||
'endpoint': 's3.lix.systems',
|
'endpoint': 's3.afnix.fr',
|
||||||
'want-mass-query': 'true',
|
'want-mass-query': 'true',
|
||||||
'write-nar-listing': 'true',
|
'write-nar-listing': 'true',
|
||||||
'ls-compression': 'zstd',
|
'ls-compression': 'zstd',
|
||||||
@@ -54,7 +55,9 @@ class RelengEnvironment:
|
|||||||
git_repo: Callable[[], str]
|
git_repo: Callable[[], str]
|
||||||
git_repo_is_gerrit: bool
|
git_repo_is_gerrit: bool
|
||||||
s3_endpoint: str
|
s3_endpoint: str
|
||||||
|
s3_region: str
|
||||||
s3_ssh_host: str | None
|
s3_ssh_host: str | None
|
||||||
|
s3_ssh_user: str | None
|
||||||
|
|
||||||
docker_targets: list[DockerTarget]
|
docker_targets: list[DockerTarget]
|
||||||
|
|
||||||
@@ -86,17 +89,19 @@ LOCAL = RelengEnvironment(
|
|||||||
git_repo_is_gerrit=False,
|
git_repo_is_gerrit=False,
|
||||||
docker_targets=[],
|
docker_targets=[],
|
||||||
s3_endpoint = 'http://localhost:3900',
|
s3_endpoint = 'http://localhost:3900',
|
||||||
|
s3_region = 'garage',
|
||||||
s3_ssh_host = None,
|
s3_ssh_host = None,
|
||||||
|
s3_ssh_user = None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
STAGING = RelengEnvironment(
|
STAGING = RelengEnvironment(
|
||||||
name='staging',
|
name='staging',
|
||||||
colour=functools.partial(sgr, GREEN),
|
colour=functools.partial(sgr, GREEN),
|
||||||
docs_bucket='s3://staging-docs',
|
docs_bucket='s3://docs.staging.lix.systems',
|
||||||
cache_bucket='s3://staging-cache',
|
cache_bucket='s3://cache.staging.lix.systems',
|
||||||
cache_store_overlay={'secret-key': 'staging.key'},
|
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=lambda: 'ssh://git@git.lix.systems/lix-project/lix-releng-staging',
|
||||||
git_repo_is_gerrit=False,
|
git_repo_is_gerrit=False,
|
||||||
docker_targets=[
|
docker_targets=[
|
||||||
@@ -106,8 +111,10 @@ STAGING = RelengEnvironment(
|
|||||||
DockerTarget('ghcr.io/lix-project/lix-releng-staging',
|
DockerTarget('ghcr.io/lix-project/lix-releng-staging',
|
||||||
tags=['{version}', '{major}']),
|
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_host = S3_HOST,
|
||||||
|
s3_ssh_user = S3_USER,
|
||||||
)
|
)
|
||||||
|
|
||||||
GERRIT_REMOTE_RE = re.compile(r'^ssh://(\w+@)?gerrit.lix.systems:2022/lix$')
|
GERRIT_REMOTE_RE = re.compile(r'^ssh://(\w+@)?gerrit.lix.systems:2022/lix$')
|
||||||
@@ -127,13 +134,13 @@ def guess_gerrit_remote():
|
|||||||
PROD = RelengEnvironment(
|
PROD = RelengEnvironment(
|
||||||
name='production',
|
name='production',
|
||||||
colour=functools.partial(sgr, RED),
|
colour=functools.partial(sgr, RED),
|
||||||
docs_bucket='s3://docs',
|
docs_bucket='s3://docs.lix.systems',
|
||||||
cache_bucket='s3://cache',
|
cache_bucket='s3://cache.lix.systems',
|
||||||
# FIXME: we should decrypt this with age into a tempdir in the future, but
|
# 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
|
# the issue is how to deal with the recipients file. For now, we should
|
||||||
# just delete it after doing a release.
|
# just delete it after doing a release.
|
||||||
cache_store_overlay={'secret-key': 'prod.key'},
|
cache_store_overlay={'secret-key': 'prod.key'},
|
||||||
releases_bucket='s3://releases',
|
releases_bucket='s3://releases.lix.systems',
|
||||||
git_repo=guess_gerrit_remote,
|
git_repo=guess_gerrit_remote,
|
||||||
git_repo_is_gerrit=True,
|
git_repo_is_gerrit=True,
|
||||||
docker_targets=[
|
docker_targets=[
|
||||||
@@ -142,8 +149,10 @@ PROD = RelengEnvironment(
|
|||||||
tags=['{version}', '{major}']),
|
tags=['{version}', '{major}']),
|
||||||
DockerTarget('ghcr.io/lix-project/lix', 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_host = S3_HOST,
|
||||||
|
s3_ssh_user = S3_USER,
|
||||||
)
|
)
|
||||||
|
|
||||||
ENVIRONMENTS = {
|
ENVIRONMENTS = {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# SPDX-FileCopyrightText: 2024 Jade Lovelace
|
# SPDX-FileCopyrightText: 2024 Jade Lovelace
|
||||||
|
# SPDX-FileCopyrightText: 2026 Yureka Lilian <yureka@cyberchaos.dev>
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
import datetime
|
import datetime
|
||||||
import dataclasses
|
|
||||||
import re
|
import re
|
||||||
from typing import Any, Literal, Optional
|
from typing import Any
|
||||||
import requests
|
import requests
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
@@ -14,27 +14,34 @@ import logging
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
log.setLevel(logging.INFO)
|
log.setLevel(logging.INFO)
|
||||||
|
|
||||||
fmt = logging.Formatter('{asctime} {levelname} {name}: {message}',
|
fmt = logging.Formatter(
|
||||||
datefmt='%b %d %H:%M:%S',
|
"{asctime} {levelname} {name}: {message}",
|
||||||
style='{')
|
datefmt="%b %d %H:%M:%S",
|
||||||
|
style="{",
|
||||||
|
)
|
||||||
|
|
||||||
if not any(isinstance(h, logging.StreamHandler) for h in log.handlers):
|
if not any(isinstance(h, logging.StreamHandler) for h in log.handlers):
|
||||||
hand = logging.StreamHandler()
|
hand = logging.StreamHandler()
|
||||||
hand.setFormatter(fmt)
|
hand.setFormatter(fmt)
|
||||||
log.addHandler(hand)
|
log.addHandler(hand)
|
||||||
|
|
||||||
API_BASE = os.environ.get('GARAGE_ADMIN_API_BASE', 'http://localhost:3903')
|
API_BASE = os.environ.get("GARAGE_ADMIN_API_BASE", "http://localhost:3903")
|
||||||
API_KEY = os.environ['GARAGE_ADMIN_TOKEN']
|
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:
|
def api(method, endpoint: str, resp_json=True, **kwargs) -> Any:
|
||||||
log.info('http %s %s', method, endpoint)
|
log.info("http %s %s", method, endpoint)
|
||||||
if not endpoint.startswith('https'):
|
if not endpoint.startswith("https"):
|
||||||
endpoint = API_BASE + endpoint
|
endpoint = API_BASE + endpoint
|
||||||
resp = requests.request(method,
|
resp = requests.request(
|
||||||
endpoint,
|
method,
|
||||||
headers={'Authorization': f'Bearer {API_KEY}'},
|
endpoint,
|
||||||
**kwargs)
|
headers={"Authorization": f"Bearer {API_KEY}"},
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
if resp_json:
|
if resp_json:
|
||||||
return resp.json()
|
return resp.json()
|
||||||
@@ -42,97 +49,64 @@ def api(method, endpoint: str, resp_json=True, **kwargs) -> Any:
|
|||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
def get_bucket_id(bucket_name: str) -> str:
|
||||||
class Key:
|
resp: dict = api(
|
||||||
name: str
|
"GET", "/v2/GetBucketInfo", params={"globalAlias": bucket_name}
|
||||||
id: str
|
)
|
||||||
secret_key: Optional[str] = None
|
return resp["id"]
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
DATEFMT = "%Y%m%d%H%M%S"
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def do_new(args):
|
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]:
|
key_name = args.name + "-" if args.name else ""
|
||||||
if whether:
|
expiration = datetime.datetime.now(tz=datetime.UTC) + datetime.timedelta(
|
||||||
return [s]
|
seconds=args.age_secs
|
||||||
else:
|
)
|
||||||
return []
|
key_name += "ephemeral-" + expiration.strftime(DATEFMT)
|
||||||
|
|
||||||
access_types: list[AccessType] = optional('read', args.read) + optional(
|
key_resp: dict = api(
|
||||||
'write', args.write) + optional('owner', args.owner) # type: ignore
|
"POST",
|
||||||
|
"/v2/CreateKey",
|
||||||
|
json={
|
||||||
|
"name": key_name,
|
||||||
|
"expiration": expiration.isoformat(),
|
||||||
|
"neverExpires": False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
key_name = args.name + '-' if args.name else ''
|
for b in bucket_ids:
|
||||||
key_name += "ephemeral-" + (
|
api(
|
||||||
datetime.datetime.now(tz=datetime.UTC) +
|
"POST",
|
||||||
datetime.timedelta(seconds=args.age_secs)).strftime(DATEFMT)
|
"/v2/AllowBucketKey",
|
||||||
|
json={
|
||||||
|
"accessKeyId": key_resp["accessKeyId"],
|
||||||
|
"bucketId": b,
|
||||||
|
"permissions": {
|
||||||
|
"read": args.read,
|
||||||
|
"write": args.write,
|
||||||
|
"owner": args.owner,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
k = create_key(key_name)
|
print(
|
||||||
for b in buckets:
|
json.dumps(
|
||||||
grant(b, access_types, k)
|
{
|
||||||
|
"name": key_resp["name"],
|
||||||
print(json.dumps(dataclasses.asdict(k), indent=2))
|
"id": key_resp["accessKeyId"],
|
||||||
|
"secret_key": key_resp["secretAccessKey"],
|
||||||
|
},
|
||||||
def do_clean(args):
|
indent=2,
|
||||||
older_than = datetime.datetime.now(tz=datetime.UTC)
|
)
|
||||||
for key in expired_keys(older_than):
|
)
|
||||||
delete_key(key)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -148,28 +122,27 @@ def main():
|
|||||||
|
|
||||||
new = sps.add_parser("new", help="Make an ephemeral key")
|
new = sps.add_parser("new", help="Make an ephemeral key")
|
||||||
new.add_argument("--name", help="Name prefix for the key")
|
new.add_argument("--name", help="Name prefix for the key")
|
||||||
new.add_argument("--read",
|
new.add_argument(
|
||||||
action="store_true",
|
"--read", action="store_true", help="Grant read access to buckets"
|
||||||
help="Grant read access to buckets")
|
)
|
||||||
new.add_argument("--write",
|
new.add_argument(
|
||||||
action="store_true",
|
"--write", action="store_true", help="Grant write access to buckets"
|
||||||
help="Grant write access to buckets")
|
)
|
||||||
new.add_argument("--owner",
|
new.add_argument(
|
||||||
action="store_true",
|
"--owner", action="store_true", help="Grant owner access to buckets"
|
||||||
help="Grant owner access to buckets")
|
)
|
||||||
new.add_argument("--age-secs",
|
new.add_argument(
|
||||||
type=int,
|
"--age-secs",
|
||||||
required=True,
|
type=int,
|
||||||
help="Maximum key lifetime in seconds")
|
required=True,
|
||||||
new.add_argument("buckets", nargs='*', help="Buckets to grant access to")
|
help="Maximum key lifetime in seconds",
|
||||||
|
)
|
||||||
|
new.add_argument("buckets", nargs="*", help="Buckets to grant access to")
|
||||||
new.set_defaults(cmd=do_new)
|
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 = ap.parse_args()
|
||||||
args.cmd(args)
|
args.cmd(args)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ def get_ephemeral_key(
|
|||||||
env.docs_bucket.removeprefix('s3://'),
|
env.docs_bucket.removeprefix('s3://'),
|
||||||
]
|
]
|
||||||
if env.s3_ssh_host is not None:
|
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)
|
output = subprocess.check_output(command)
|
||||||
d = json.loads(output.decode())
|
d = json.loads(output.decode())
|
||||||
return environment.S3Credentials(name=d['name'],
|
return environment.S3Credentials(name=d['name'],
|
||||||
|
|||||||
@@ -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)
|
|
||||||
@@ -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())
|
|
||||||
@@ -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,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
@@ -57,6 +57,7 @@ functional_tests_scripts = [
|
|||||||
'fetchurl.sh',
|
'fetchurl.sh',
|
||||||
'fetchPath.sh',
|
'fetchPath.sh',
|
||||||
'fetchTree-file.sh',
|
'fetchTree-file.sh',
|
||||||
|
'simple.sh',
|
||||||
'referrers.sh',
|
'referrers.sh',
|
||||||
'substitute-with-invalid-ca.sh',
|
'substitute-with-invalid-ca.sh',
|
||||||
'signing.sh',
|
'signing.sh',
|
||||||
@@ -154,7 +155,7 @@ foreach script : functional_tests_scripts
|
|||||||
suite : 'installcheck',
|
suite : 'installcheck',
|
||||||
env : {
|
env : {
|
||||||
'MESON_BUILD_ROOT': meson.project_build_root(),
|
'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
|
# 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
|
# this can easily drive them to failure. give them more time, 5min rather than 30sec
|
||||||
timeout : 300,
|
timeout : 300,
|
||||||
|
|||||||
@@ -25,13 +25,13 @@ static void maybeRequireMeowForDlopen() {
|
|||||||
meow();
|
meow();
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value prim_anotherNull(EvalState & state, Value ** args)
|
static void prim_anotherNull (EvalState & state, Value ** args, Value & v)
|
||||||
{
|
{
|
||||||
assert(entryCalled);
|
assert(entryCalled);
|
||||||
if (mySettings.settingSet)
|
if (mySettings.settingSet)
|
||||||
return Value::VNULL;
|
v.mkNull();
|
||||||
else
|
else
|
||||||
return {NewValueAs::boolean, false};
|
v = {NewValueAs::boolean, false};
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void nix_plugin_entry()
|
extern "C" void nix_plugin_entry()
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
:bt, :backtrace Show trace stack
|
:bt, :backtrace Show trace stack
|
||||||
:c, :continue Go until end of program, exception or builtins.break
|
:c, :continue Go until end of program, exception or builtins.break
|
||||||
:s, :step Go one step
|
: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
|
Flakes commands
|
||||||
|
|
||||||
|
|||||||
@@ -14,56 +14,26 @@ This test ensures that continues don't skip opportunities to enter the debugger.
|
|||||||
0: error: breakpoint reached
|
0: error: breakpoint reached
|
||||||
$TEST_DATA/regression_9917.nix:3:5
|
$TEST_DATA/regression_9917.nix:3:5
|
||||||
|
|
||||||
2| a = builtins.trace "before inner break" (
|
2| a = builtins.trace "before inner break" (
|
||||||
3| builtins.break { msg = "hello"; }
|
3| builtins.break { msg = "hello"; }
|
||||||
| ^
|
| ^
|
||||||
4| );
|
4| );
|
||||||
|
|
||||||
1: while calling a function
|
1: while calling a function
|
||||||
$TEST_DATA/regression_9917.nix:3:5
|
$TEST_DATA/regression_9917.nix:3:5
|
||||||
|
|
||||||
2| a = builtins.trace "before inner break" (
|
2| a = builtins.trace "before inner break" (
|
||||||
3| builtins.break { msg = "hello"; }
|
3| builtins.break { msg = "hello"; }
|
||||||
| ^
|
| ^
|
||||||
4| );
|
4| );
|
||||||
|
|
||||||
2: while calling a function
|
2: while calling a function
|
||||||
$TEST_DATA/regression_9917.nix:2:7
|
$TEST_DATA/regression_9917.nix:2:7
|
||||||
|
|
||||||
1| let
|
1| let
|
||||||
2| a = builtins.trace "before inner break" (
|
2| a = builtins.trace "before inner break" (
|
||||||
| ^
|
| ^
|
||||||
3| builtins.break { msg = "hello"; }
|
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" (
|
|
||||||
|
|
||||||
nix-repl> :c
|
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
|
nix-repl> :quit
|
||||||
error:
|
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
|
… while calling the 'trace' builtin
|
||||||
at $TEST_DATA/stack_vars.nix:5:7:
|
at $TEST_DATA/stack_vars.nix:2:7:
|
||||||
4| );
|
1| let
|
||||||
5| b = builtins.trace "before outer break" (
|
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
|
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 : {
|
env : {
|
||||||
'_NIX_TEST_UNIT_DATA': meson.current_build_dir() / 'data',
|
'_NIX_TEST_UNIT_DATA': meson.current_build_dir() / 'data',
|
||||||
'MESON_BUILD_ROOT': meson.project_build_root(),
|
'MESON_BUILD_ROOT': meson.project_build_root(),
|
||||||
} + coverage_test_env,
|
},
|
||||||
suite : 'installcheck',
|
suite : 'installcheck',
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -197,8 +197,5 @@ REPL_TEST(idempotent);
|
|||||||
REPL_TEST(debug_frames);
|
REPL_TEST(debug_frames);
|
||||||
REPL_TEST(debug_ignore_try);
|
REPL_TEST(debug_ignore_try);
|
||||||
REPL_TEST(debug_ignore_try_defaults);
|
REPL_TEST(debug_ignore_try_defaults);
|
||||||
REPL_TEST(stacktrace_invalid_arg);
|
|
||||||
REPL_TEST(stacktrace_oob);
|
|
||||||
REPL_TEST(stacktrace_relative);
|
|
||||||
|
|
||||||
}; // namespace nix
|
}; // namespace nix
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from testlib.fixtures.nix import Nix, NixDaemon
|
from testlib.fixtures.nix import Nix
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@@ -13,9 +13,9 @@ from testlib.fixtures.nix import Nix, NixDaemon
|
|||||||
(["*"], ["--force-untrusted"], False),
|
(["*"], ["--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")
|
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)
|
trusted = json.loads(inner.nix(["store", "ping", "--json"]).run().ok().stdout)
|
||||||
assert trusted["trusted"] == expected
|
assert trusted["trusted"] == expected
|
||||||
|
|||||||
@@ -23,15 +23,12 @@ ERR_CASES: list[ShouldError] = [
|
|||||||
"1",
|
"1",
|
||||||
"""error: the expression selected by the selection path '1' should be a list but is a set: { }""",
|
"""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(
|
ShouldError(
|
||||||
"{}",
|
'{ x."" = 2; }', 'x.""', """error: empty attribute name in selection path 'x.""'"""
|
||||||
".",
|
|
||||||
"""error: Leading dot in attribute selection path '.' is not allowed! If the attribute name is an empty string, use '\"\".foo.bar'""",
|
|
||||||
),
|
),
|
||||||
ShouldError(
|
ShouldError(
|
||||||
"{}",
|
'{ x."".y = 2; }', 'x."".y', """error: empty attribute name in selection path 'x."".y'"""
|
||||||
"bla..blub",
|
|
||||||
"""error: consecutive dots not allowed in selection path 'bla..blub', use 'foo."".bar' to denote an empty attribute name""",
|
|
||||||
),
|
),
|
||||||
ShouldError(
|
ShouldError(
|
||||||
"[]", "1", """error: list index 1 in selection path '1' is out of range for list [ ]"""
|
"[]", "1", """error: list index 1 in selection path '1' is out of range for list [ ]"""
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -27,59 +27,3 @@ def test_debugger_output(nix: Nix):
|
|||||||
assert "error: oh snap" in res.stderr_plain
|
assert "error: oh snap" in res.stderr_plain
|
||||||
assert re.findall(r"with: .*a", res.stdout_plain)
|
assert re.findall(r"with: .*a", res.stdout_plain)
|
||||||
assert re.findall(r"static: .*x", 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
|
|
||||||
|
|||||||
+10
-5
@@ -1,9 +1,14 @@
|
|||||||
error:
|
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
|
… while calling the 'flakeRefToString' builtin
|
||||||
at /pwd/in.nix:4:1:
|
at /pwd/in.nix:1:32:
|
||||||
3| in
|
1| let n = -1; in builtins.seq n (builtins.flakeRefToString {
|
||||||
4| builtins.flakeRefToString {
|
| ^
|
||||||
| ^
|
2| type = "github";
|
||||||
5| type = "github";
|
|
||||||
|
|
||||||
error: negative value given for flake ref attr repo: -1
|
error: negative value given for flake ref attr repo: -1
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
let
|
let n = -1; in builtins.seq n (builtins.flakeRefToString {
|
||||||
n = -1;
|
|
||||||
in
|
|
||||||
builtins.flakeRefToString {
|
|
||||||
type = "github";
|
type = "github";
|
||||||
owner = "NixOS";
|
owner = "NixOS";
|
||||||
repo = n;
|
repo = n;
|
||||||
ref = "23.05";
|
ref = "23.05";
|
||||||
dir = "lib";
|
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.
|
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 error.
|
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.
|
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.
|
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 error.
|
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'.
|
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 collections.abc import Callable
|
||||||
from pathlib import Path
|
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.file_helper import AssetSymlink, CopyFile, CopyTree, with_files
|
||||||
from testlib.fixtures.nix import Nix
|
from testlib.fixtures.nix import Nix
|
||||||
from testlib.fixtures.snapshot import Snapshot
|
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]):
|
def test_search_path(files: Path, nix: Nix, snapshot: Callable[[str], Snapshot]):
|
||||||
nix.env.set_env("NIX_PATH", "dir3:dir4")
|
nix.env.set_env("NIX_PATH", "dir3:dir4")
|
||||||
nix_eval_okay(
|
nix_eval(
|
||||||
files,
|
files,
|
||||||
nix,
|
nix,
|
||||||
[
|
[
|
||||||
@@ -43,26 +43,12 @@ def test_search_path(files: Path, nix: Nix, snapshot: Callable[[str], Snapshot])
|
|||||||
"nix-shadow": CopyTree("nix-shadow"),
|
"nix-shadow": CopyTree("nix-shadow"),
|
||||||
"in.nix": CopyFile("in-fetchurl.nix"),
|
"in.nix": CopyFile("in-fetchurl.nix"),
|
||||||
"out.exp": AssetSymlink("eval-okay-prefixed.out.exp"),
|
"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"),
|
"err.exp": AssetSymlink("eval-okay-prefixed.err.exp"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def test_prefixed_search_path(files: Path, nix: Nix, snapshot: Callable[[str], Snapshot]):
|
def test_prefixed_search_path(files: Path, nix: Nix, snapshot: Callable[[str], Snapshot]):
|
||||||
nix.env.set_env("NIX_PATH", "nix=nix-shadow")
|
nix.env.set_env("NIX_PATH", "nix=nix-shadow")
|
||||||
nix_eval_fail(files, nix, [], snapshot)
|
nix_eval(files, nix, [], snapshot)
|
||||||
|
|
||||||
|
|
||||||
@with_files(
|
@with_files(
|
||||||
@@ -70,24 +56,11 @@ def test_prefixed_search_path(files: Path, nix: Nix, snapshot: Callable[[str], S
|
|||||||
"nix-shadow": CopyTree("nix-shadow"),
|
"nix-shadow": CopyTree("nix-shadow"),
|
||||||
"in.nix": CopyFile("in-fetchurl.nix"),
|
"in.nix": CopyFile("in-fetchurl.nix"),
|
||||||
"out.exp": AssetSymlink("eval-okay-prefixless.out.exp"),
|
"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"),
|
"err.exp": AssetSymlink("eval-okay-prefixless.err.exp"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def test_prefixless_search_path(files: Path, nix: Nix, snapshot: Callable[[str], Snapshot]):
|
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(
|
@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]):
|
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
|
|
||||||
@@ -23,10 +23,6 @@ if build_test_env != ''
|
|||||||
endif
|
endif
|
||||||
functional2_env.set('system', host_system)
|
functional2_env.set('system', host_system)
|
||||||
|
|
||||||
foreach name, val : coverage_test_env
|
|
||||||
functional2_env.set(name, val)
|
|
||||||
endforeach
|
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'functional2',
|
'functional2',
|
||||||
bash,
|
bash,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from testlib.fixtures.file_helper import with_files
|
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
|
from testlib.utils import get_global_asset
|
||||||
|
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ class TestOptimizeStore:
|
|||||||
def test_optimise_store(self, nix: Nix):
|
def test_optimise_store(self, nix: Nix):
|
||||||
self._test_optimise_store(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
|
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)
|
self._test_optimise_store(inner)
|
||||||
|
|||||||
@@ -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?"
|
|
||||||
)
|
|
||||||
@@ -221,12 +221,8 @@ class ManagedEnv:
|
|||||||
if platform.system() == "Darwin":
|
if platform.system() == "Darwin":
|
||||||
# Darwin / Apple behaves differently and requires _NIX_TEST_NO_SANDBOX to be set for whatever reason
|
# Darwin / Apple behaves differently and requires _NIX_TEST_NO_SANDBOX to be set for whatever reason
|
||||||
self._env |= {"_NIX_TEST_NO_SANDBOX": "1"}
|
self._env |= {"_NIX_TEST_NO_SANDBOX": "1"}
|
||||||
# Copy coreutils from the global path to maintain availability of commands that are not part of
|
# copy global path to maintain features usually provided by busybox
|
||||||
# XCode Developer Tools and provided by busybox on Linux, which does not build on Darwin
|
[self.path.append(p) for p in global_path.split(":") if Path(p).exists()]
|
||||||
for p in global_path.split(":"):
|
|
||||||
if Path(p).exists() and "coreutils" in p:
|
|
||||||
self.path.append(p)
|
|
||||||
break
|
|
||||||
|
|
||||||
def set_env(self, name: str, value: str):
|
def set_env(self, name: str, value: str):
|
||||||
if name in self.dirs.get_env_keys():
|
if name in self.dirs.get_env_keys():
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import dataclasses
|
|||||||
import sys
|
import sys
|
||||||
from functools import partialmethod
|
from functools import partialmethod
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Literal, get_args
|
from typing import Any
|
||||||
from collections.abc import Callable, Generator
|
from collections.abc import Callable, Generator
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -20,9 +20,9 @@ from testlib.utils import is_value_of_type
|
|||||||
type _NixSettingValue = str | int | list[str] | bool | None
|
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]):
|
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):
|
if is_value_of_type(value, bool):
|
||||||
return "true" if value else "false"
|
return "true" if value else "false"
|
||||||
if is_value_of_type(value, str | int):
|
if is_value_of_type(value, str | int):
|
||||||
@@ -105,7 +105,7 @@ class NixSettings:
|
|||||||
|
|
||||||
self["extra-sandbox-paths"] += env.path.to_sandbox_paths()
|
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
|
nonlocal config
|
||||||
if value is not None:
|
if value is not None:
|
||||||
config += f"{name} = {serializer(value)}\n"
|
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:
|
def nix_cmd(self, argv: list[str], flake: bool = False, cwd: Path | None = None) -> Command:
|
||||||
"""
|
"""
|
||||||
Constructs a NixCommand with the appropriate settings.
|
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
|
# Create a copy of settings to not have a writing side effect
|
||||||
settings = self.settings.clone()
|
settings = self.settings.clone()
|
||||||
@@ -172,6 +173,57 @@ class Nix:
|
|||||||
) -> Command:
|
) -> Command:
|
||||||
return self.nix_cmd([nix_exe, *cmd], flake=flake, cwd=cwd)
|
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
|
# 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_build = partialmethod(nix, nix_exe="nix-build") # noqa: RUF045
|
||||||
nix_shell = partialmethod(nix, nix_exe="nix-shell") # 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()
|
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
|
@pytest.fixture
|
||||||
def enable_diverted_store(nix: Nix):
|
def enable_diverted_store(nix: Nix):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -30,14 +30,6 @@ def test_list_type_nested_single_invalid():
|
|||||||
assert not is_value_of_type([[1], [2, 3], ["a"]], list[list[int]])
|
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():
|
def test_weird_type_valid():
|
||||||
assert is_value_of_type(42, Literal[42])
|
assert is_value_of_type(42, Literal[42])
|
||||||
|
|
||||||
|
|||||||
@@ -116,8 +116,6 @@ def is_value_of_type(value: Any, expected_type: type[Any] | UnionType) -> bool:
|
|||||||
return True
|
return True
|
||||||
match origin:
|
match origin:
|
||||||
case None:
|
case None:
|
||||||
if expected_type is None:
|
|
||||||
return origin is None
|
|
||||||
return isinstance(value, expected_type)
|
return isinstance(value, expected_type)
|
||||||
case types.UnionType:
|
case types.UnionType:
|
||||||
return any(is_value_of_type(value, t) for t in get_args(expected_type))
|
return any(is_value_of_type(value, t) for t in get_args(expected_type))
|
||||||
|
|||||||
@@ -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
|
// n.b. I do not know why we throw for empty attrs but they are apparently
|
||||||
// disallowed.
|
// disallowed.
|
||||||
TEST_F(AttrPathEval, emptyAttrsThrowsWithoutQuotes)
|
TEST_F(AttrPathEval, emptyAttrsThrows)
|
||||||
{
|
{
|
||||||
std::string expr = "{a.\"\".b = 2;}";
|
std::string expr = "{a.\"\".b = 2;}";
|
||||||
ASSERT_NO_THROW(testFindAlongAttrPath(expr, "a"));
|
ASSERT_NO_THROW(testFindAlongAttrPath(expr, "a"));
|
||||||
ASSERT_NO_THROW(testFindAlongAttrPath(expr, "a.\"\".b"));
|
ASSERT_THROW(testFindAlongAttrPath(expr, "a.\"\".b"), Error);
|
||||||
ASSERT_THROW(testFindAlongAttrPath(expr, "a..b"), Error);
|
ASSERT_THROW(testFindAlongAttrPath(expr, "a.\"\""), Error);
|
||||||
ASSERT_NO_THROW(testFindAlongAttrPath(expr, "a.\"\""));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ RC_GTEST_FIXTURE_PROP(
|
|||||||
prop_opaque_path_round_trip,
|
prop_opaque_path_round_trip,
|
||||||
(const SingleDerivedPath::Opaque & o))
|
(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, "");
|
auto d = state.coerceToSingleDerivedPath(noPos, v, "");
|
||||||
RC_ASSERT(SingleDerivedPath { o } == d);
|
RC_ASSERT(SingleDerivedPath { o } == d);
|
||||||
}
|
}
|
||||||
@@ -40,7 +41,8 @@ RC_GTEST_FIXTURE_PROP(
|
|||||||
prop_derived_path_built_out_path_round_trip,
|
prop_derived_path_built_out_path_round_trip,
|
||||||
(const SingleDerivedPath::Built & b, const StorePath & outPath))
|
(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, "");
|
auto [d, _] = state.coerceToSingleDerivedPathUnchecked(noPos, v, "");
|
||||||
RC_ASSERT(SingleDerivedPath { b } == d);
|
RC_ASSERT(SingleDerivedPath { b } == d);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ namespace nix {
|
|||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(JSONValueTest, null) {
|
TEST_F(JSONValueTest, null) {
|
||||||
Value v = Value::VNULL;
|
Value v;
|
||||||
|
v.mkNull();
|
||||||
ASSERT_EQ(getJSONValue(v), "null");
|
ASSERT_EQ(getJSONValue(v), "null");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -152,7 +152,8 @@ TEST_F(TypeValuePrintingTests, vExternal)
|
|||||||
}
|
}
|
||||||
} myExternal;
|
} myExternal;
|
||||||
|
|
||||||
Value vExternal = {NewValueAs::external, myExternal};
|
Value vExternal;
|
||||||
|
vExternal.mkExternal(&myExternal);
|
||||||
|
|
||||||
test(vExternal, "an external value from MyExternal");
|
test(vExternal, "an external value from MyExternal");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ TEST_F(ValuePrintingTests, tInt)
|
|||||||
|
|
||||||
TEST_F(ValuePrintingTests, tBool)
|
TEST_F(ValuePrintingTests, tBool)
|
||||||
{
|
{
|
||||||
Value vBool = {NewValueAs::boolean, true};
|
Value vBool;
|
||||||
|
vBool = {NewValueAs::boolean, true};
|
||||||
test(vBool, "true");
|
test(vBool, "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +49,8 @@ TEST_F(ValuePrintingTests, tPath)
|
|||||||
|
|
||||||
TEST_F(ValuePrintingTests, tNull)
|
TEST_F(ValuePrintingTests, tNull)
|
||||||
{
|
{
|
||||||
Value vNull = Value::VNULL;
|
Value vNull;
|
||||||
|
vNull.mkNull();
|
||||||
test(vNull, "null");
|
test(vNull, "null");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +96,7 @@ TEST_F(ValuePrintingTests, vThunk)
|
|||||||
TEST_F(ValuePrintingTests, vApp)
|
TEST_F(ValuePrintingTests, vApp)
|
||||||
{
|
{
|
||||||
EvalMemory mem;
|
EvalMemory mem;
|
||||||
Value vFn = Value::VNULL;
|
Value vFn{NewValueAs::null};
|
||||||
Value vApp{NewValueAs::app, mem, vFn, vFn};
|
Value vApp{NewValueAs::app, mem, vFn, vFn};
|
||||||
|
|
||||||
test(vApp, "«thunk»");
|
test(vApp, "«thunk»");
|
||||||
@@ -162,7 +164,8 @@ TEST_F(ValuePrintingTests, vExternal)
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
} myExternal;
|
} myExternal;
|
||||||
Value vExternal = {NewValueAs::external, myExternal};
|
Value vExternal;
|
||||||
|
vExternal.mkExternal(&myExternal);
|
||||||
|
|
||||||
test(vExternal, "testing-external!");
|
test(vExternal, "testing-external!");
|
||||||
}
|
}
|
||||||
@@ -367,7 +370,8 @@ TEST_F(ValuePrintingTests, ansiColorsPath)
|
|||||||
|
|
||||||
TEST_F(ValuePrintingTests, ansiColorsNull)
|
TEST_F(ValuePrintingTests, ansiColorsNull)
|
||||||
{
|
{
|
||||||
Value v = Value::VNULL;
|
Value v;
|
||||||
|
v.mkNull();
|
||||||
|
|
||||||
test(v,
|
test(v,
|
||||||
ANSI_CYAN "null" ANSI_NORMAL,
|
ANSI_CYAN "null" ANSI_NORMAL,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user