Compare commits

...
Author SHA1 Message Date
Raito Bezarius 21317ce965 release: 2.92.2 "Bombe glacée"
Release produced with releng/create_release.xsh

Change-Id: I43476413ebe28aa60618f24205fdda9a3a92902c
2025-06-24 10:50:36 +00:00
Raito Bezarius b43a289c02 release: release notes for 2.92.2
Release created with releng/create_release.xsh

Change-Id: I9bcc8bec834ebf9b2d0ec9df00a585b610152ba8
2025-06-24 10:50:36 +00:00
Raito Bezarius 2138b0f7e9 version: 2.92.1 -> 2.92.2
Fixes CVE-2025-46415, CVE-2025-46416, CVE-2025-52991, CVE-2025-52992,
and CVE-2025-52993.

Change-Id: Ia73b43abd1cf2d0cfa185938c59d3a836a9eb160
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-24 10:50:36 +00:00
Raito Bezarius 2f32368136 libstore/build: automatic clean up of unsuccessfully built scratch outputs
When a build fails, its scratch output paths are not cleaned up.

Until recently, this was deemed not a problem but as part of the effort
to harden the Nix builds and protect these paths against being part of a
staged attack (race conditions, etc.), we automatically cleanup after
failed builds.

Fixes CVE-2025-52992.

Change-Id: I58481b1cc83826298b9d80d37fecf81f117ccb09
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-24 10:50:36 +00:00
eldritch horrorsandRaito Bezarius a713c064af libstore: don't default build-dir to temp-dir
if a build directory is accessible to other users it is possible to
smuggle data in and out of build directories. usually this ins only
a build purity problem, but in combination with other issues it can
be used to break out of a build sandbox. to prevent this we default
to using a subdirectory of nixStateDir (which is more restrictive).

Fixes CVE-2025-52991.

Change-Id: Iacfc9b50534de158618c815f9fb99d7dae1be4d0
2025-06-24 10:50:36 +00:00
cba05329ee libstore: use pasta for FODs if available
This allows using a userspace program, pasta, to handle comms between
the build sandbox, and the outside world; allowing for full isolation
including the network namespace, closing the "fixed-output derivation
talks to the host over an abstract domain socket" hole for good.

Fixes CVE-2025-46416.

Co-Authored-By: Puck Meerburg <puck@puckipedia.com>
Change-Id: Ifd499b7dbb3784600a6e842fede65fc031ff9f15
2025-06-24 10:50:36 +00:00
eldritch horrors f5f2e1537d libutil: add capability support to runProgram2
launching pasta to not run as root will ambient require capabilities.

Change-Id: I1dd2506a1fa3944a9d9062123ef8a74903c597ea
2025-06-24 10:50:36 +00:00
eldritch horrors 0ea8649445 libutil: add generic redirections runProgram2
explicit stderr redirection makes mergeStderrToStdout unnecessary also.

Change-Id: I63de929e6dc53f6c5ceb2d43c2ce288bfc04d872
2025-06-24 10:50:36 +00:00
eldritch horrors d0678a57f9 libutil: make RunningProgram more useful
make it moveable, make it killable, and add a stdout fd accessor.

Change-Id: I2387cbe8ac67b899a322cd6c7d306ef9ea7abcd0
2025-06-24 10:50:36 +00:00
Raito Bezarius 1d4ddb7e3b libutil: ensure that _deletePath does NOT use absolute paths with dirfds
When calling `_deletePath` with a parent file descriptor, `openat` is
made effective by using relative paths to the directory file descriptor.

To avoid the problem, the signature is changed to resist misuse with an
assert in the prologue of the function.

Fixes CVE-2025-46415.

Change-Id: I6b3fc766bad2afe54dc27d47d1df3873e188de96
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-24 10:50:36 +00:00
Raito Bezarius c7867e89f9 libstore: ensure that passAsFile is created in the original temp dir
This ensures that `passAsFile` data is created inside the expected
temporary build directory by `openat()` from the parent directory file
descriptor.

Fixes CVE-2025-52993.

Change-Id: Ie5273446c4a19403088d0389ae8e3f473af8879a
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-24 10:45:29 +00:00
Raito Bezarius deb5150c82 libutil: writeFile variant for file descriptors
`writeFile` lose its `sync` boolean flag to make things simpler.

A new `writeFileAndSync` function is created and all call sites are
converted to it.

Change-Id: Ib871a5283a9c047db1e4fe48a241506e4aab9192
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-23 16:50:44 +02:00
Raito Bezarius 92eb418a59 libstore: chown to builder variant for file descriptors
We use it immediately for the build temporary directory.

Change-Id: I180193c63a2b98721f5fb8e542c4e39c099bb947
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-23 16:50:44 +02:00
Raito Bezarius 7fceee3ce3 libstore: open build directory as a dirfd as well
We now keep around a proper AutoCloseFD around the temporary directory
which we plan to use for openat operations and avoiding the build
directory being swapped out while we are doing something else.

Change-Id: I18d387b0f123ebf2d20c6405cd47ebadc5505f2a
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-23 16:50:44 +02:00
Raito Bezarius 31f976dc19 libutil: guess or invent a path from file descriptors
This is useful for certain error recovery paths (no pun intended) that
does not thread through the original path name.

Change-Id: I2d800740cb4f9912e64c923120d3f977c58ccb7e
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-23 16:50:44 +02:00
jadeandLix Systems Gerrit d8db15010d Merge changes I577bdc15,I1c5f7a5c,I75f56c80 into release-2.92
* changes:
  version: 2.92.1
  release-notes: note the flake.lock fix
  libexpr/flake: user friendly error if parsing flake.lock fails
2025-03-17 22:26:40 +00:00
Jade Lovelace 253be7d2ba version: 2.92.1
Change-Id: I577bdc1549e80c0b7621624e2d390153581b3bf2
2025-03-17 12:11:24 -07:00
Jade Lovelace a34e583305 releng: workaround awscli breaking garage by changing checksum algo
CHERRY-PICK: required to release

We use https://garagehq.deuxfleurs.fr for https://docs.lix.systems,
https://releases.lix.systems, https://cache.lix.systems. It's generally
great, but AWS doesn't, erm, care, about other implementations and broke
their client library.

We already ran into
https://git.deuxfleurs.fr/Deuxfleurs/garage/issues/824, which was
mitigated by a garage upgrade to create a new error. These bugs were
what got us:
- https://github.com/boto/boto3/issues/4392
- https://github.com/aws/aws-cli/issues/9214

Error:
upload failed: release/manual/.nojekyll to s3://docs/manual/lix/nightly/.nojekyll An error occurred (InvalidRequest) when calling the PutObject operation: Bad request: invalid checksum algorithm

The missing checksum algorithm is CRC32NVME, with a bug filed here:
https://git.deuxfleurs.fr/Deuxfleurs/garage/issues/963

Change-Id: Ib78a89034bf0f2a6773fc505a347b2aadb775e93
(cherry picked from commit 0f4c5b3b8a)
2025-03-17 19:10:24 +00:00
Rebecca Turnerandjade a8fb008106 releng: use aws s3 sync --delete when uploading manual
CHERRY-PICK: prereq to fixing the aws flags for garage shenanigans

For the user manual, we don't delete things that are missing when doing
aws s3 sync. This doesn't seem wise. If you delete a page in the manual,
it will stay public and visible.

This adds `--delete` to the `aws s3 sync` commands to fix this issue.

Closes #396

Change-Id: I6d7fb97bcdab96c0115d6c66fea0310125207df4
(cherry picked from commit 16df34b295)
2025-03-17 19:09:10 +00:00
Jade Lovelace 32cbb66a69 release-notes: note the flake.lock fix
Change-Id: I1c5f7a5cba12da1bfb2896ffc04ec89862cb346d
2025-03-17 12:06:39 -07:00
giliceandjade 9631e9a30f libexpr/flake: user friendly error if parsing flake.lock fails
CHERRY-PICK: repeated bug reports

Fix: https://git.lix.systems/lix-project/lix/issues/559
Change-Id: I75f56c801d7f16a2e1ef8e702f16f1ef91f7b01f
(cherry picked from commit 68373f8664)
2025-03-17 19:01:23 +00:00
jadeandLix Systems Gerrit 98772c4a3b Merge "Revert "libstore: don't use curl decompression support"" into release-2.92 2025-03-17 18:17:41 +00:00
jadeandLix Systems Gerrit a7fd5c3867 Merge "libexpr: fix checkSourcePath purity regression" into release-2.92 2025-03-17 17:58:29 +00:00
jadeandLix Systems Gerrit 6f3a7bbeb0 Revert "libstore: don't use curl decompression support"
This reverts commit 0d1f794178.

Reason for revert: pennae says it makes the bug more visible, for some unknown reason. See https://git.lix.systems/lix-project/lix/issues/662

Change-Id: I91853cb6645f188a260c4c71cfff4de7bea99feb
2025-03-17 17:56:20 +00:00
eldritch horrorsandjade 0d1f794178 libstore: don't use curl decompression support
it's broken with http2 and transfer flow control. cf fj#662

BACKPORT: due to regression
Change-Id: Iaf6312bfcefa18d168faef47f57481199dd30b8d
2025-03-09 08:39:10 +00:00
Alois Wohlschlager ef5689dc1b libexpr: fix checkSourcePath purity regression
Starting with commit 0dbfa7b26e access would also
be allowed to ancestors of allowed paths. This is (ironically) a significant
purity regression, since several users of the purity checks will themselves
assume that arbitrary descent is allowed. For example, `builtins.readDir` and
`builtins.path` could now refer to the filesystem root, breaking purity
entirely in the latter case by allowing to read arbitrary files. Restore the
previous behaviour of only allowing access to explicitly allowed paths.

Change-Id: Ie64180733ab735da9873255e1ccbf95ba7c9161c
(cherry picked from commit 9d99a7c2cf)
2025-03-04 18:15:15 +01:00
Lunaphiedandjade db55ca9a2e hotfix for releng scripts, fixes #440
Change-Id: I33b29fbf3920a2d4fb53b58db477dff5bf7a1af1
2025-02-21 01:17:44 +00:00
eldritch horrorsandJade Lovelace 4cd618272a libstore: never return from lockFile without a lock
signals could cause lockFile to return without having locked the file.
the garbage collector didn't check for this, and then hilarity ensued.

Change-Id: If86d33595e8bf5510d2b032139342261dc6e07c9
(cherry picked from commit d186064c3d)
2025-02-09 12:54:48 -08:00
eldritch horrorsandJade Lovelace 73f0213500 libstore: split tryLockFile lockFile
lockFile is currently interruptible by signals like SIGCHLD. which
happen a lot in daemons. now imagine that daemon enabled automatic
garbage collection. observe that the local store does not actually
check whether its lock operations have succeeded ... get the idea?

Change-Id: Ibfd7ee786c4fee3add72d4456a7e95e73e09c73e
(cherry picked from commit 79f9c39e36)
2025-02-09 12:54:48 -08:00
eldritch horrorsandJade Lovelace 9c1db3cd8b libstore: extract unlockFile from lockFile
propagating the "unlock" lock type through this high-level api is
nonsense. it doesn't make sense to treat locking and unlocking as
similar operations; unlocking *must* not not interruptible by our
checkInterrupt machinery or it will just leave locks lying around
for a potentially very long time. unlock operations should not be
taking long enough to *want* them interrupted anyway. even on nfs
this makes very little sense because nfs waits *uninterruptibly*.

Change-Id: I10d605c8fe6c651bee64466eee1f8e20251d39f4
(cherry picked from commit aa87c8aa93)
2025-02-09 12:54:48 -08:00
Dusk Banksandeldritch horrors 5fa27057b2 libexpr: fix --debugger --ignore-try
a65e9e5828 did not inform `tryEval` that
(as far as it's concerned) `state.debug` moved to `state.errors.debug`
and changed types. this resulted in the REPL erroneously coming up, that
REPL having a non-debug state, and segfaulting after that REPL exited.

it's probably good that `state.debug` isn't mutated by `--ignore-try`
anymore.

Change-Id: I1918e93edacd626452aa423fc2eb825080738835
Fixes: a65e9e5828 ("libexpr: extract eval error creation into new type")
Signed-off-by: Dusk Banks <me@bb010g.com>
(cherry picked from commit 6a583136b7)
2025-01-31 12:36:14 +00:00
Dusk Banksandeldritch horrors d2b1af70ee doc: add bb010g to change-authors
Change-Id: Iddd3c21b2c42669cec394ac6b80f4e766e4cb81c
Signed-off-by: Dusk Banks <me@bb010g.com>
(cherry picked from commit 050cf17307)
2025-01-31 03:21:44 +00:00
Raito Bezariusandjade 50def3fa73 fix(gc): log sudden "path in use" exceptions and recover during GC gracefully
Original-Author: picnoir <picnoir@alternativebit.fr>

Inspired from
https://github.com/NixOS/nix/pull/11922/commits/ced8d311a593fcf9c3823e4e118474ac132d8e60
and adapted for Lix needs.

TL;DR: The topological sort should ensure that it is possible to delete
the path iterated upon. Nonetheless, in some cases,
`invalidatePathChecked` can still throw `PathInUse`, the exception
bubbles up and cancel the garbage collection procedure, leaving the rest
of the paths untouched. This change ensure that the error is logged for
further investigation but doesn't prevent the GC to continue when it
can.

After code review, we decided to make it a `printInfo` to inform the
user about sudden "in use" dependencies during garbage collection and
let them re-run garbage collection if they care about this.

References: https://github.com/NixOS/nix/issues/11923
References: https://git.lix.systems/lix-project/lix/issues/621
Change-Id: I5606c9afd16b5faa747b713fde2dc24016990ba3
Signed-off-by: Raito Bezarius <raito@lix.systems>
(cherry picked from commit 6a41dae49a)
2025-01-23 06:04:00 +00:00
eldritch horrorsandjade 8e2ab5532c FileTransfer: fix race condition on awaitData
There's a race condition where awaitData could early-return for data
coming from a 404 response or similar and thus not rethrow the exception
that is forthcoming, and a related race during transfer setup (which
could retry a transfer *twice* per retry round).

This would then cause substitution failures like below since the exception
isn't caught in HttpBinaryCacheStore::getFile as intended, but instead
by an exception handler downstream of `drain()` which would error out
the entire operation.

Symptom:

 » nix-build ./docs-service.nix -o "docs-service-result"
error: unable to download 'https://cache.nixos.org/7mr3fy8w66gi5inmf0jkkkl90lxy4jyg.narinfo': HTTP e
rror 404 ()

       response body:

This is kind of a hack in how it is implemented: it assumes that you
can't intentionally be receiving a large unsuccessful response since in
such a case, `awaitData` will wait for finish() to be called to throw an
exception and will never escape until the download finishes, while
continuing to buffer the entire response into memory, which could be bad
if an error response had a large payload.

That said, nobody is sending Lix 1GiB of 404, so meh I guess, and this
is how it is seemingly intended to work. That was a design flaw of the
thing before any of the Lix team got our paws on it.

I tested this by adding _exit(0) inside the expected exception catch and
then running the offending command repeatedly to see if the symptom ever
appeared again, and it did not.

Needs cherry-pick to 2.92 and a 2.92.1 release once reviewed.

Fixes: https://git.lix.systems/lix-project/lix/issues/635
Co-Authored-By: lix@jade.fyi
Change-Id: If54f6eeaad60b5ca9d5b77d4d9232da1d295e7d1
(cherry picked from commit de58cd6e80)
2025-01-22 21:06:58 +00:00
Jade Lovelace 079528098f release: merge release 2.92.0 back to mainline
This merge commit returns to the previous state prior to the release but leaves the tag in the branch history.
Release created with releng/create_release.xsh

Change-Id: I4e4650d96de82b46c35171b3a9fc1e3a6ca8e6a1
2025-01-18 13:04:46 -08:00
Jade Lovelace 9446732466 release: 2.92.0 "Bombe glacée"
Release produced with releng/create_release.xsh

Change-Id: Ife8c3ed7dc44b6ed334e53ec260fd17a931bf55f
2025-01-18 13:04:46 -08:00
Jade Lovelace b5c3c21792 release: release notes for 2.92.0
Release created with releng/create_release.xsh

Change-Id: Ie02e27b7328758727e78c85925e04629cd4d6d14
2025-01-18 13:04:35 -08:00
Jade Lovelace fc8bd88cf4 releng: fix for newer xonsh
Looks like a module rename

Change-Id: I281e41b8781fa5aa75a3bcb6e6907e582f46dcb7
2025-01-18 13:04:20 -08:00
Jade Lovelace 9b2761d66d version.json: codename and remove -dev suffix in prep for 2.92
Change-Id: I30e45aac44e7fd5bd5cfe56a9514cee95b1d4d8b
2025-01-17 19:03:49 -08:00
Jade Lovelace fe79b90055 doc: add some missing release notes for 2.92
Change-Id: I4861f8885aac53ce76322aae0387facfdd5f3e88
2025-01-17 19:03:49 -08:00
Justin ! 0d14c2b67a libmain: always print all information when calling nix --version
This commit remove a check for the log level in the `printVersion()`
function that was making `nix --version` behaving weirdly and
inconsistently compared to other `nix-*` commands.

The root cause is the following code in `lix/nix/main.cc` that change
the log level at runtime if nix is call interactively:

```cpp
if (isatty(STDERR_FILENO)) {
  verbosity = lvlNotice;
} else {
  verbosity = lvlInfo;
}
```

This should be removed, but since it has more implication it's gonna be
done in another CL.

Fix: https://git.lix.systems/lix-project/lix/issues/620
Change-Id: Id2c83c51d7ef799ee2f9b8dbdd2bfaeaf2df6188
2025-01-16 22:56:42 -05:00
Jade Lovelace 9760c00591 clang-tidy: Obliterate FixIncludes check
We have obliterated the support across the build system for these
include paths anywhere, so the codemod is now redundant and can go.

Change-Id: I54082f39752c6aac6429e1c24026211adae8221a
2025-01-16 00:12:57 -08:00
Jade Lovelace bf3ebde25f pkg-config: remove legacy include paths!
This is a breaking change for non-migrated external clients. External
users always need to use <lix/libcmd/foo.hh> type include paths now.

This is as was always planned with the include rearrangement.

Change-Id: I269be91ff9f9cc94d5d3043cf3e0bdf8db1d8edb
2025-01-16 00:12:57 -08:00
eldritch horrorsandGerrit Code Review 423d8b03c8 Merge "libutil: thread-pool: ensure threads finished on error" into main 2025-01-15 22:33:30 +00:00
Jade Lovelace acbb3cff2d Merge remote-tracking branch 'pennae/path-access' into HEAD
This fixes a bug where flakes do not actually do purity path checks
correctly.

Tested-By: Jade Lovelace <lix@jade.fyi>
Change-Id: If7d131a8e73a5874fb15cfaa0dea3b8811ba35d2
2025-01-13 17:19:54 -08:00
Maximilian BoschandGerrit Code Review 38dd196b03 Merge "libstore: fix "illegal reference specifier 'man'"-error in postgresql_14" into main 2025-01-12 11:12:12 +00:00
Jade Lovelaceandeldritch horrors d46adb45eb tests: validate that flakes do path traversal checking
Apparently we had zero test coverage of this, let's fix that.

Change-Id: I00c906daf5acfc01913562036ca88abbf63dd3d9
2025-01-11 20:42:30 +01:00
eldritch horrors 0dbfa7b26e libexpr: forbid allowed -> disallowed -> allowed links
this is more of a theoretical problem, but it does allow changing the
behavior of a flake depending on mutable machine state. it's unlikely
that this could be used to reliably do anything bad, but it does lead
to even more non-determinstic evaluation of (notionally) pure flakes.

Change-Id: I5bac7ed045046da08a36c764ab887bc9c7551542
2025-01-11 20:42:30 +01:00
Dominique Martinet 4737d8b65e libutil: thread-pool: ensure threads finished on error
This fixes segfaults with nix copy when there was an error processing
addMultipleToStore.

Running with ASAN/TSAN pointed at an use-after-free with threads from
the pool accessing the graph declared in processGraph after the function
was exiting and destructing the variables.

It turns out that if there is an error before pool.process() is called,
for example while we are still enqueueing tasks, then pool.process()
isn't called and threads are still left to run.

By creating the pool last we ensure that it is stopped first before
running other destructors even if an exception happens early.

fixes #618

Change-Id: I42a355f632aa0354df94c5d5d8cbe7ab5196c9a6
2025-01-12 04:14:28 +09:00
eldritch horrorsandJade Lovelace c948b350fb libutil: add CheckedSourcePath for accessing things
SourcePath only manipulates path names now. all accesses must go through
a checked path going forward to ensure we don't escape restriction lists
of pure and restricted evaluation. if a directory path is checked it can
safely be assumed that the directory itself is allowed, and its contents
will likewise be safe to access. it is tempting to assumed that contents
will also be fine, but that's only true if the content is not a symlink.

Change-Id: Icec3098d53fe9dce50997954ba958fe4f304d59b
2025-01-10 15:20:27 -08:00
eldritch horrorsandJade Lovelace 8db8ac9a67 always checkSource before accessing anything
like earlier, anything accessed during eval must be checked against the
list of path restrictions. this notably excludes `Pos::getSource` which
is run only from an unrestricted context (resolving line/column numbers
for expressions), but since positions require the parser to run and the
parser requires a checked input to produce positions this is not a leak

Change-Id: I337859e9c780590d4434885125a3ef70a11f6e93
2025-01-10 15:20:27 -08:00
eldritch horrorsandJade Lovelace 3f6a1e45c9 libexpr: always checkSourcePath in resolveExprPath
the purpose of resolveExprPath is to produce a parser input path. parser
input paths must be validated against the path allow list so they do not
escape the restricted/pure eval sandbox. checking the input path and any
intermediate paths during resolving makes this a lot harder to do badly.

Change-Id: Ib31b5bca63fe26a5e08458a871cdc9f92f9b6a10
2025-01-10 15:20:27 -08:00
eldritch horrorsandJade Lovelace ede0851fb4 libexpr: move resolveExprPath to EvalPaths
Change-Id: I4f8e27bb816d6498df4d73a57e10b654eb995c32
2025-01-10 15:20:27 -08:00
eldritch horrorsandJade Lovelace 5af069b248 libutil: remove SourcePath::resolveSymlinks
in pure mode it is entirely useless. in impure mode it's mostly useless
since the way in which it is used is either equivalent to not being run
at all, or is equivalent to turning the following lstat into a stat. we
add a stat method instead for all those who need final symlinks stat'd.

Change-Id: I801886d18eb34b26e62b4c05d53318c6421a69bf
2025-01-10 15:20:27 -08:00
eldritch horrorsandJade Lovelace f93af1db1f libutil: make SourcePath::path private
use canonical() to get the disk path, to_string() to get the string form.

Change-Id: I95bb6df53356f30290b487d1cca0aa2fb37249ed
2025-01-10 15:20:27 -08:00
eldritch horrorsandJade Lovelace 3acba7951a libexpr: use StorePath::to_string in string contexts
`path.abs()` does the same thing, but `to_string` communicates intent as well.

Change-Id: I9619a9f2e32317f0a1cc8e092ce8898471b980ac
2025-01-10 15:20:27 -08:00
eldritch horrorsandJade Lovelace 44255c316d libutil: remove SourcePath::getPhysicalPath
all files are physical, so this doesn't have to be optional. if it only
returns a copy of a member it's not useful either, but performance cost

Change-Id: Ib2f935ae247d96418d55bc100e04765dc586528b
2025-01-10 15:20:27 -08:00
jadeandGerrit Code Review d953e138a2 Merge "fix: macOS build broken on nixos-unstable" into main 2025-01-10 22:23:14 +00:00
eldritch horrors fafe647bc2 libstore: fix UAF in FileTransfer
after startTransfer the transfer, and thus downloadState, is gone. asan
hasn't caught this, presumably because the access is in libc somewhere.
we don't even need access to the old state; the assertion is not useful
here and clearing the previous exception is invisible to the new round.

Change-Id: I32dc1a487b96cbaabcf5061c9b1f96dbc5aaae49
2025-01-10 17:05:02 +01:00
Jade Lovelace 37e8904ba7 fix: macOS build broken on nixos-unstable
This is definitely a bug in rapidcheck but also rapidcheck is
unmaintained (author vanished) so we can't really do anything about it
on that side.

lix> FAILED: tests/unit/liblixutil-test-support.dylib.p/libutil-support_tests_hash.cc.o
lix> clang++ -Itests/unit/liblixutil-test-support.dylib.p -Itests/unit -I../tests/unit -I../tests/unit/libutil-support -I. -I.. -Ilix/libutil -Ilix/libexpr -fdiagnostics-color=always -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST -Wall -Winvalid-pch -Wextra -std=c++23 -O2 -g -fpch-instantiate-templates -include config.h -Wno-unused-parameter -Wno-deprecated-declarations -Wno-missing-field-initializers -Wimplicit-fallthrough -Werror=switch -Werror=switch-enum -Werror=unused-result -Wdeprecated-copy -Wignored-qualifiers -Werror=suggest-override -fsanitize=signed-integer-overflow -fsanitize-undefined-trap-on-error -ffile-prefix-map=../lix=lix -MD -MQ tests/unit/liblixutil-test-support.dylib.p/libutil-support_tests_hash.cc.o -MF tests/unit/liblixutil-test-support.dylib.p/libutil-support_tests_hash.cc.o.d -o tests/unit/liblixutil-test-support.dylib.p/libutil-support_tests_hash.cc.o -c ../tests/unit/libutil-support/tests/hash.cc
lix> In file included from ../tests/unit/libutil-support/tests/hash.cc:3:
lix> In file included from /nix/store/k8b7wc0lkr3lqxybzv458ijddpqnzlgw-rapidcheck-0-unstable-2023-12-14-dev/include/rapidcheck.h:18:
lix> In file included from /nix/store/k8b7wc0lkr3lqxybzv458ijddpqnzlgw-rapidcheck-0-unstable-2023-12-14-dev/include/rapidcheck/Gen.h:74:
lix> /nix/store/k8b7wc0lkr3lqxybzv458ijddpqnzlgw-rapidcheck-0-unstable-2023-12-14-dev/include/rapidcheck/Gen.hpp:72:22: error: calling 'current_exception' with incomplete return type 'exception_ptr'
lix>    72 |     auto exception = std::current_exception();
lix>       |                      ^~~~~~~~~~~~~~~~~~~~~~~~
lix> /nix/store/0nhi47d5ip48wprxnava6vv973zzzndf-libcxx-19.1.5-dev/include/c++/v1/__exception/operations.h:37:41: note: 'current_exception' declared here
lix>    37 | _LIBCPP_EXPORTED_FROM_ABI exception_ptr current_exception() _NOEXCEPT;
lix>       |                                         ^
lix> /nix/store/0nhi47d5ip48wprxnava6vv973zzzndf-libcxx-19.1.5-dev/include/c++/v1/__exception/operations.h:35:33: note: forward declaration of 'std::exception_ptr'
lix>    35 | class _LIBCPP_EXPORTED_FROM_ABI exception_ptr;
lix>       |                                 ^
lix> 1 error generated.

Change-Id: Id5d0c4aa8143f2469c5a636d6773a2f5a25a0268
2025-01-07 17:11:04 -08:00
piegamesandGerrit Code Review 3413ab5629 Merge "libexpr: Undeprecate overriding __findFile" into main 2025-01-07 18:39:48 +00:00
Maximilian Bosch a1c09bed7b libstore: fix "illegal reference specifier 'man'"-error in postgresql_14
Nixpkgs issues / PRs:
* https://github.com/NixOS/nixpkgs/pull/368091
* https://github.com/NixOS/nixpkgs/issues/369366

This can be triggered with the postgresql_14 derivation from nixpkgs rev
19305d94dacca226ca048b78e6de00f599c65858
(/nix/store/bxp6g57limvwiga61vdlyvhy7i8rp6wd-postgresql-14.15.drv on
x86_64-linux): for reasons unknown to me, only the `man` and `lib` outputs
are cached on cache.nixos.org:

    $ nix derivation show  /nix/store/bxp6g57limvwiga61vdlyvhy7i8rp6wd-postgresql-14.15.drv | jq '.[].outputs.[].path' -r | xargs nix path-info --store https://cache.nixos.org
    warning: The interpretation of store paths arguments ending in `.drv` recently changed. If this command is now failing try again with '/nix/store/bxp6g57limvwiga61vdlyvhy7i8rp6wd-postgresql-14.15.drv^*'
    don't know how to build these paths:
      /nix/store/m9vb40xxr6gckjzpfxnqcmjqsks2gx03-postgresql-14.15
      /nix/store/nm1415wa53iawar9axwxy0an6ximhayn-postgresql-14.15-dev
      /nix/store/v9vrvfhiw9gk8hj9895sb15fxvxnyylj-postgresql-14.15-debug
      /nix/store/zi12g1p99g2173i8093ixbqkfh9ng87b-postgresql-14.15-doc
    /nix/store/3i3fpz0xss9inampf51gp3pkx24ypxpj-postgresql-14.15-man
    /nix/store/db8797h2cp4rm1cnsqrf87apkkxwwdff-postgresql-14.15-lib
    error: path '/nix/store/m9vb40xxr6gckjzpfxnqcmjqsks2gx03-postgresql-14.15' does not exist in the store

Also, the derivation uses the `outputChecks` feature (and thus `__structuredAttrs`)
to make sure that e.g. the `out` output doesn't reference the `man`
output:

    __structuredAttrs = true;
    outputs = [ "out" "dev" "doc" "lib" "man" ];
    outputChecks.out.disallowedReferences = [ "dev" "doc" "man" ];

With all that in place, the following error was hit on all CppNix / Lix
versions currently supported when trying to build the derivation above:

    error: derivation contains an illegal reference specifier 'man'

The following happened here:

* The `man` & `lib` outputs were substituted at some point.
* When register outputs, the reference checks are made.
* `LocalDerivationGoal::checkOutputs` gets a map of all outputs that
  were built and are NOT already registered in the store. In the example
  above this means `out`, `dev`, `debug` and `doc`.
* `checkOutputs` tries to resolve the `man` output and fails to do so
  because it's a store-path that's already registered and thus not part
  of the map passed to `checkOutputs`.

Since the map passed to `checkOutputs` is used in various other places
that appear to assume that the paths aren't registered already, I didn't
write the already registered paths into it. Instead, I created a second
map that contains all already registered outputs and pass it as third
argument to `checkOutputs`. If the other lookups fail, this map will be
now checked before the "illegal reference specifier"-error is thrown.

This fixes the problem with `postgresql_14` for me.

Also wrote a small regression test that fails locally without the patch
in place.

Change-Id: Ieacca80c001fcfbebf6f5fe97e25c49d2724c3ff
2025-01-05 14:50:45 +01:00
eldritch horrors 8c1ece93cc libstore: fix download deadlock on interruption
in a daemon all calls to the logger can throw an Interrupted exception,
which so far has silently stopped the curl thread without notifying its
transfers and leaving them stuck as a result. ensuring that the loggers
can never throw Interrupted will have very unpleasant side-effects, and
throwing depending on context requires large amount of bookkeeping. for
now it is easiest to abort all transfers on Interrupted during cleanup.

the test for this is extremely sketchy because we want to hit a single,
very specifically chosen, loger call in TransferItem::finish(). the bug
was triggered by the `act.progress` further down from what we're aiming
for, but that one is much harder to select for than the debug log here.

fixes #613

Change-Id: Id72efa64dd30cbbf256d2ab2a328457a0b095c6a
2025-01-05 01:19:12 +01:00
eldritch horrors c314c5e581 benchmarks: fix config for nixos 24.11
the flake input bump broke these due to packages being moved. also fix
renamed options while we are here, before they inevitably break later.

Change-Id: If16bb0a221e63ae7394af6b8d904c5ecd8994e6f
2025-01-05 01:19:12 +01:00
eldritch horrors 6e8bc92ca5 flake: fix nixpkgs lib test
the bump to 24.11 broke this, and buildbot simply ignored it. great.

Change-Id: I9a6c177c0679beb87891c05c34272c6c437c65fa
2025-01-05 01:19:12 +01:00
piegames 539c340c66 libexpr: Undeprecate overriding __findFile
After gathering more community feedback, more non-trivial use cases for
overriding `__findFile` emerged. Unlike the use case of Tvix mentioned
in #599, these can't easily be worked around by overriding `nixPath`
instead.

This is the second fixup/partial revert for
81d5f0a7d9.
See also #599.

Change-Id: I7ca75e1a2b196c0da341969c61f4c168b5f657f9
2025-01-04 19:07:35 +01:00
Chip Bilbrey 5c7ea4f446 Use locked flake-compat in shell.nix
Change-Id: I059aa6da6365f1a5a46b506e57d9b3ab2b73d8d8
2024-12-30 07:25:23 -08:00
Yureka 92ed9fee7d fix(libstore): correctly trigger embedded sandbox shell logic
Change-Id: I8131c7a94b3cd54d7e75fcdddf2fb8d561b6e38f
2024-12-11 16:48:34 +01:00
V.andGerrit Code Review 4f5e5e9bdf Merge "refactor(libutil): add new constructor to SysError using std::error_code" into main 2024-12-11 08:44:38 +00:00
rebecca “wiggles” turnerandGerrit Code Review 06996718c3 Merge "repl: tab-complete quoted attribute names" into main 2024-12-11 03:27:15 +00:00
jadeandGerrit Code Review cb44fe7c15 Merge changes Ic890f781,I77b2de10,I3202aba0,I6ddc4296,I18776984, ... into main
* changes:
  doc: fix broken table in operators list
  terminal code eaters: implement OSC
  libexpr: significantly improve error messages for bad attr paths
  tests/functional2: add terminal code eater
  tests/functional2: fix occasional pytest haunting
  attr path parser: fix bug in not rejecting empty attr paths, add unparser
2024-12-11 02:28:11 +00:00
Ian ChamberlainandRebecca Turner 67f07e05df repl: tab-complete quoted attribute names
Attribute names containing special characters like @ or . need to be
quoted, so we need to do our own tokenization of the command line for
completion, and quote the attribute names when we provide the completion.

Fixes: https://git.lix.systems/lix-project/lix/issues/450

Change-Id: I55a30dd272880c89445d9ded49b3f2c90cb19326
2024-12-10 18:10:16 -08:00
beviuandRebecca Turner bc63b007e5 Mention support for ~/ paths in nix.conf in manual
Fixes: https://git.lix.systems/lix-project/lix/issues/497

Change-Id: I9606900d17f62359cef4fe2c8a01e5791390a870
2024-12-10 16:36:35 -08:00
Jade Lovelace f51943f171 doc: fix broken table in operators list
Fixes: https://git.lix.systems/lix-project/lix/issues/597
Change-Id: Ic890f781af10fbcab23deae5ab9c10eebfbab070
2024-12-10 15:43:31 -08:00
Jade Lovelace 5530de4673 terminal code eaters: implement OSC
This is a useful piece of functionality to being able to eat URL
hyperlinks, for instance, which is a bug that Lix has while dealing with
terminal output today.

Change-Id: I77b2de107b2525cad7ea5dea28bfba2cc78b9e6d
2024-12-10 15:43:31 -08:00
Jade Lovelace faf00ad022 libexpr: significantly improve error messages for bad attr paths
This commit makes Lix include the summarized content of the value being
indexed when it is bad.

lix/lix2 » nix eval --expr '{x.y = 2;}' 'x.y.z'
error: the value being indexed in the selection path 'x.y.z' at 'x.y' should be a set but is an integer: 2

lix/lix2 » nix eval --expr '{x.y = { a = 3; };}' 'x.y.z'
error: attribute 'z' in selection path 'x.y.z' not found inside path 'x.y', whose contents are: { a = 3; }
       Did you mean a?

lix/lix2 » nix eval --expr '{x.y = { a = 3; };}' 'x.y.1'
error: the expression selected by the selection path 'x.y.1' should be a list but is a set: { a = 3; }

Change-Id: I3202aba0e437e00b4c6d3ee287a2d9a7c6892dbf
2024-12-10 15:43:31 -08:00
Jade Lovelace c0808bd855 tests/functional2: add terminal code eater
I want this for being able to write reasonable expect-test style tests
for oneliners. We will still probably want something like insta for more
complicated test cases where you actually *want* the output in a
different file, but for now this will do.

cc: https://git.lix.systems/lix-project/lix/issues/595
Change-Id: I6ddc42963cc49177762cfca206fe9a9efe1ae65d
2024-12-10 15:43:31 -08:00
Jade Lovelace 93d5221b9b tests/functional2: fix occasional pytest haunting
I don't know what the heck the xonsh module is doing but its obviously
crimes so it has to go. It was never intended to be running here anyway.

Fixes: https://git.lix.systems/lix-project/lix/issues/593
Change-Id: I1877698469392f85884945aaa60987c68c4e0ebc
2024-12-10 13:32:28 -08:00
Jade Lovelace 21ad02c1d0 attr path parser: fix bug in not rejecting empty attr paths, add unparser
The following behaviour was previously present and has been fixed:

lix/lix2 » nix eval --expr '{x."" = 2;}' 'x.""'
{ "" = 2; }
lix/lix2 » nix eval --expr '{x."".y = 2;}' 'x."".y'
error: empty attribute name in selection path 'x."".y'

Change-Id: Iad21988f1191c33a3661c72a6b7f01a8b8b3e6eb
2024-12-10 13:32:28 -08:00
Jade Lovelace 8ddcdfb59c build: fix on x86_64-darwin
Lix requires a non-antiquated macOS SDK, and 24.11 does not yet have a
non-antiquated one as default.

Fixes: https://git.lix.systems/lix-project/lix/issues/588
Change-Id: Iad19c06d7fefe3a736cdcb39ced185e52dcfcbb8
2024-12-10 12:32:42 -08:00
Artemis TosiniandJade Lovelace 445b7c7d8a flake: Use nixpkgs 24.11
nixpkgs 24.11 changes how we access xonsh yet again
and updates clang.

Unfortunately, clang 18 produces significantly more
warnings on existing code that is challenging to fix.
Make sure that doesn't error when we're running
`-Werror` builds.

n.b. I had to change the "SSL certificate problem: self-signed
certificate" to the old error prior to the improved libcurl errors,
since what is presumably a difference in which TLS library is used has
cropped up between releases? Either way the curl error buffer is empty.
Seems like we aggressively cannot do anything about this.

Change-Id: If0141a46a8b445a0e7d6f86f939e8c8e03569bf5
2024-12-10 12:32:42 -08:00
jadeandGerrit Code Review 9a96086381 Merge "tree-wide: fix a pile of lints" into main 2024-12-10 20:29:53 +00:00
piegames 7c76053d93 libexpr: Undeprecate overriding __nixPath
It being overridable was an intended feature with good use cases, and
should not have been removed. However, this feature is generally in a
bad state and needs revisiting in the future.

Fixup for 81d5f0a7d9
Fixes #599

Change-Id: I2d93e012caa65aa795bce3a71d8e56d7052ef9df
2024-12-10 19:02:41 +01:00
vigress8 95e088de72 refactor(libutil): add new constructor to SysError using std::error_code
Change-Id: I6150d7966442551f0b6f7a0cc9739a4f9d6c4e7e
2024-12-09 20:43:40 +04:00
piegamesandGerrit Code Review 369e3f82f0 Merge changes I4771fe87,Ifc7a9516,I1f5e05ab,I63f2fbcd into main
* changes:
  libexpr: Rework error messages on ExprSelct::eval
  libexpr: Track position information in attrpaths
  libexpr: Assert: Don't print assertion in error message
  libexpr: Split ExprAttrs and ExprLet
2024-12-09 10:28:22 +00:00
piegames a2ae14bfd8 libexpr: Rework error messages on ExprSelct::eval
Calls to `show` have been removed. To counter the loss of information,
the error positions have been improved and now correctly point to the
current selector instead of the entire select expression.

Change-Id: I4771fe874af1ac15828a9863550cd4369a8f0e94
2024-12-08 20:52:18 +01:00
piegames 172515bd8b libexpr: Track position information in attrpaths
This is a pretty small change because the parser already has all
necessary information (needed for parse errors), now we want to keep it
to also provide better eval errors.

Change-Id: Ifc7a9516b9b0c8d9698f1899a6912ae91f6696ab
2024-12-08 20:51:45 +01:00
piegames cdda2454f6 libexpr: Assert: Don't print assertion in error message
The `show` functionality needs to be removed because it is deeply
flawed, and given that we already print position information in the
error message (which probably wasn't always the case in the past) the
assertion printing is redundant anyways.

Change-Id: I1f5e05ab73aaa0ec92994c2211463260fd374898
2024-12-08 20:51:44 +01:00
sethandeldritch horrors f7687fa414 libstore/linux: include sys/syscall.h
Per `seccomp(2)`, this header defines the `SYS_*` constants used

Found in https://github.com/NixOS/nixpkgs/pull/353576

Change-Id: I64ebc5513c53500b7a9012eaa664c2e59f49ceac
2024-12-08 18:49:27 +00:00
piegamesandeldritch horrors 9611018c27 libexpr: Split ExprAttrs and ExprLet
ExprLet was previously inheriting from ExprAttrs for the data, while
ignoring all
set-specific operations on it. The set specific code has now been split
off so that
let doesn't inherit it anymore:

- ExprAttrs (not an Expr), containing the attributes and the related
logic
- ExprLet : Expr, ExprAttrs
- ExprSet : Expr, ExprAttrs

Co-authored-by: eldritch horrors <pennae@lix.systems>
Change-Id: I63f2fbcd1e790b3cffb56eec1e7565ee3cdbf964
2024-12-08 12:54:38 +01:00
jadeandGerrit Code Review 2a9e560570 Merge "libcmd: munge evalSettings in the right place" into main 2024-12-06 01:25:12 +00:00
Jade Lovelace 53e0b6ecfb tree-wide: fix a pile of lints
Mostly these are bugprone-unused-local-non-trivial-variable.

Also fix instances of:
- bugprone-optional-value-conversion
- bugprone-inc-dec-in-conditions (please check this loop is correct, it
  is the only non trivial code change in here)
- bugprone-unused-return-value (well, by fixing the lint config)

There are three notable changes relating to undefined vars:
- openLogFile ignoring the result. This is because openLogFile does a
  whole bunch of mutation of member variables
- hiliteMatches: i am guessing this is because showing the derivation
  name was unhelpful and it just got changed
- canonPath in NarAccessor: canonPath inside of a thing that is supposed
  to be vfs based cannot possibly be correct, so let's delete it given
  it is unused.

Fixes: https://git.lix.systems/lix-project/lix/issues/584

Change-Id: I887adc9ff28b61f726dcfed197e6796b414c2fcf
2024-12-05 17:13:37 -08:00
eldritch horrors fb76a4d335 libcmd: munge evalSettings in the right place
these must be tampered with before the evaluator is created, *never*
after. doing it any other way leads to interesting things like #596.

fixes #596

Change-Id: Iea253ccce44b94b1243833837a3df93c795967d9
2024-12-06 00:59:53 +01:00
Jade Lovelace 9dd58224ea libexpr: rename misleading type alias Bindings::size_t
Calling a custom type size_t is incredibly sneaky and makes all the code
around this extremely context dependent.

Change-Id: Idae684781f45fe615020d8642f12a656ab2c15ad
2024-12-05 12:24:15 -08:00
eldritch horrors 61eed2c97c libexpr: fully separate evaluator and running evaluation
this finally gives us a witness type we can use to prove that a certain
call graph subtree can't be used in kj promises using only a single new
assumption: if EvalState& is never held as a reference member of a type
and instead only ever passes as an argument or held on the stack we can
be certain that anything that has access to en EvalState ref must never
be run inside a promise and, crucially, that anything that doesn't have
access to an EvalState& *can* be run inside a promise without problems.

Change-Id: I6c15ada479175ad7e6cd3e4a729a5586b3ba30d6
2024-12-05 13:30:35 +00:00
eldritch horrors 07a26fc776 libexpr associate caches and statistics with EvalContext
Change-Id: Ie4740070881bdd056b112ffd79634e30c29e8aaa
2024-12-04 23:50:31 +00:00
eldritch horrors 71c392ae0f libexpr: associate parsing and builtins with EvalContext
Change-Id: Iff56246bb344405c9acecb967423554d25fe47f5
2024-12-04 23:50:31 +00:00
eldritch horrors 7fa6e785ff libexpr: associate path config with EvalContext
Change-Id: Id25f990ab4ae9d9e76c91c07d7397689acf57eca
2024-12-04 23:50:31 +00:00
eldritch horrors 1f37fc85ac libexpr: associate memory with EvalContext
Change-Id: I791ae16cd42a674972c782a869b7d790880313ff
2024-12-04 23:50:31 +00:00
eldritch horrors 5db5eed43d libexpr: associate stores and repair flag with EvalContext
Change-Id: I5fd911415707f0b250888dcabdf6290691fcdbf3
2024-12-04 23:50:31 +00:00
eldritch horrors d30027a37a libexpr: associate error creation and debugging with EvalContext
Change-Id: I80beedd0c58361ebc67cebcee97d64879baed050
2024-12-04 23:50:31 +00:00
eldritch horrors 2f2bc90d92 libexpr: associate positions with EvalContext
Change-Id: I43e827a9119324c23a6d72cfc1b7ed4d5ff32dd2
2024-12-04 23:50:31 +00:00
eldritch horrors 014845ddf1 libexpr: associate symbols with EvalContext
Change-Id: I4ca99dc1f5183c7e7383db2b9c896c1946aadd29
2024-12-04 23:50:31 +00:00
eldritch horrors 8e74d1ceeb libexpr: extract persistent eval state to new struct
"persistent" here means state that outlives any particular evaluation,
i.e. everything that exist outside of a call to eval() and friends. do
note that this leave EvalState nearly empty, containing only things of
importance to evaluation *itself*, e.g. the call depth tracker. do not
worry about the self-reference ctx member. nothing to see here, go on.

Change-Id: Ie8d0af3f09d33902f81e0c36a1096dd9f334a537
2024-12-04 23:48:47 +00:00
eldritch horrors ce698198f0 libcmd: pass EvalState& to installables
for the same reason the eval caches should not keep them as references,
only worse: installables cannot fully lose their reference member since
some types of installable (flakes -sigh-) need access to caches held by
their member reference. passing that around as well at all times is not
feasible; passing in EvalState& all the time is quite bad enough as is.

Change-Id: I05d438623b5ef10fc22e7e256b89447e61a0657d
2024-12-03 20:38:41 +01:00
eldritch horrors 81559ea8ad treewide: add evaluator aliases for eval states
this is not necessary in any way, but it will make the following changes
smaller and easier to review. the aliases could also be added piecemeal,
but doing it here lets us lean heavily on our compilers for correctness.

(teacher notes: here the author foreshadows the shape of things to come.
not all names change, and only the names unchanged are those which will,
over time, become ever more unrecognizable. note especially nix/main.cc,
where `state` is not only cloned, but itself changes pointerness. it can
be seen as a nod to the trans community, but more realistically it is no
more than foreshadowing the future where `state` is only seen by proxy.)

Change-Id: I7732025e58df089b7f8e564fc63960cd91729d09
2024-12-03 20:38:41 +01:00
eldritch horrors 473c1bdcab libexpr: remove EvalState& from EvalCache constructor
it's not used for anything any more. it was only passed on to the AttrDb
constructor, which itself only stashed it away to an unused member field

Change-Id: I334fde751a3754e6580f573c21ae3d04be00345a
2024-12-03 20:38:41 +01:00
eldritch horrors 9ff702554d libexpr: eval caches should not have an EvalState&
and for the same reason DrvInfo shouldn't, only even more so. the eval
cache infrastructure wants to be a wrapper around evaluation, with its
lifetimes fully decoupled from the lifetime of the wrapped evaluation.
this can't not create problems when eval lifetime must become bounded.

Change-Id: Ib8eec649995b4decd7290c2266322f67d73b6b46
2024-12-03 20:38:41 +01:00
eldritch horrors 8468dc65c7 libcmd: DrvInfo should not have an EvalState*
in the future eval states will be asyncio root contexts. proving that
they are used correctly is impossible if the are referenced by things
with weakly scoped lifetimes, or entirely unscoped lifetimes, as we'd
have to deal with in the case of `DrvInfos` passed as out parameters.

Change-Id: I9ca5c46a714c6c914a77c7c7500cb4939ac9aff5
2024-12-03 20:38:41 +01:00
eldritch horrors f47cd0fc88 libcmd, libexpr: remove dead code
Change-Id: I555d81bd78770dd1b773c1e90f1a41779fccad7f
2024-12-03 20:38:41 +01:00
eldritch horrors d6a7fb5f6a cli: remove duplicated declarations
Change-Id: I78c6666cea8b6604a4fae9cb047662571bd6f99b
2024-12-03 20:38:41 +01:00
eldritch horrors a5d02bcdf9 libcmd: make a back-compat hack more robust
otherwise eval fails if getEvalState runs before parseInstallables runs.

Change-Id: Ia0d66dd6297212f7835c4a9b2db0fb4d9e4c9b9d
2024-12-03 20:38:41 +01:00
eldritch horrors 9a3e3a5560 libexpr: standardize on strings for attr cache traversal
it's all strings anyway. the db stores strings, the cli wants to
interact with attr paths as strings, so we will just use strings

Change-Id: Id9ea07d92343de77e8d47af8fec1e86ae225e9a1
2024-12-03 20:38:41 +01:00
eldritch horrors dd7d7450a5 libexpr: move eval statistics into new struct
there's no reason for these to be private. much more important things
that are now part of EvalState are not private, and having statistics
be private forces us to add otherwise unnecessary friend declarations

Change-Id: Ib37097d94a9f55c2b21969fb6c51049b1c914515
2024-12-03 20:38:41 +01:00
eldritch horrors 276a771210 libexpr: move thunk counter into eval state
this is relative to the current state. why was it made global?

Change-Id: I58b3ce0213e791a05ce9fd7a2cb2394b9454c653
2024-12-03 20:38:41 +01:00
eldritch horrors ea931d4d03 libexpr: mkThunk_ -> evalLazily
thunk creation doesn't have to be called that. thunks are more of an
implementation detail, calling it evalLazily seems a lot more clear.

Change-Id: I64bdac422eadb629002195cd6b6a89dce1b4a60f
2024-12-03 20:38:41 +01:00
eldritch horrors 32f7a93f71 libexpr: extract eval path handling into new type
Change-Id: Icf38113076a4dd0f8515a27b501f405033aec73b
2024-12-03 20:38:41 +01:00
eldritch horrors a65e9e5828 libexpr: extract eval error creation into new type
this will let us pass the capability to create debuggable eval errors
without having to pass an entire EvalState. we could pass symbols and
debug states around just as easily, but if we add new capabilities to
our debugger we might have to change many more places than with this.

Change-Id: I2f8893012e5d98a986ef1fc888234c2dd8d5e096
2024-12-03 20:38:41 +01:00
eldritch horrors a4fa93d469 libexpr: simplify EvalErrorBuilder interface
if we hold the error being built behind a pointer we no longer need to
allocate the error builder itself to avoid impacting eval performance.
adding && ref qualifiers to builder functions and adding the nodiscard
attribute to the class itself also makes the nodiscard attributes kept
on the builder functions unnecessary. we do keep the noinlines though,
removing them still regresses eval performance even after this change.

Change-Id: Ibc14a66955ac32142d97fb3680b0a7e14db250cd
2024-12-03 20:38:41 +01:00
eldritch horrors ef1d62ec6c libexpr: abstract DebugState from EvalState
this is necessary to share a debug state between multiple eval states.
while doings so makes little sense at present it will be necessary for
async io support since eval states must be async io roots, which means
we must create them as needed from a shared evaluation context object.

Change-Id: Id9d4b37aae40706f65c741e3b961855582e035ab
2024-12-03 20:38:41 +01:00
eldritch horrors 0bfa58ff53 libexpr: move runtime caches to new struct
Change-Id: Ic5492853d39eaffdf03d961b3e69c93c4a75ae33
2024-12-03 20:38:41 +01:00
227 changed files with 4877 additions and 3637 deletions
+3
View File
@@ -16,6 +16,8 @@ Checks:
- -bugprone-unchecked-optional-access
# many warnings, seems like a questionable lint
- -bugprone-branch-clone
# extremely noisy before clang 19: https://github.com/llvm/llvm-project/issues/93959
- -bugprone-multi-level-implicit-pointer-conversion
# all thrown exceptions must derive from std::exception
- hicpp-exception-baseclass
# capturing async lambdas are dangerous
@@ -33,3 +35,4 @@ Checks:
CheckOptions:
bugprone-reserved-identifier.AllowedIdentifiers: '__asan_default_options'
bugprone-unused-return-value.AllowCastToVoid: true
+10 -21
View File
@@ -32,8 +32,8 @@
hardware = {
enableRedistributableFirmware = true;
cpu.intel.updateMicrocode = true;
opengl.driSupport32Bit = true;
opengl.extraPackages = with pkgs; [
graphics.enable32Bit = true;
graphics.extraPackages = with pkgs; [
vaapiIntel
intel-media-driver
intel-compute-runtime
@@ -93,16 +93,17 @@
i18n = {
defaultLocale = "en_US.UTF-8";
inputMethod.enabled = "ibus";
inputMethod.enable = true;
inputMethod.type = "ibus";
};
services = {
libinput.enable = true;
xserver = {
enable = true;
layout = "us";
xkbVariant = "altgr-intl";
xkbOptions = "ctrl:nocaps";
libinput.enable = true;
xkb.layout = "us";
xkb.variant = "altgr-intl";
xkb.options = "ctrl:nocaps";
wacom.enable = true;
videoDrivers = [ "modesetting" ];
modules = [ pkgs.xf86_input_wacom ];
@@ -121,17 +122,6 @@
'';
};
sound.enable = true;
hardware.pulseaudio = {
enable = true;
package = pkgs.pulseaudioFull;
daemon.config = {
lock-memory = "yes";
realtime-scheduling = "yes";
rlimit-rtprio = "-1";
};
};
programs = {
light.enable = true;
wireshark = {
@@ -146,7 +136,7 @@
fonts.packages = with pkgs; [
font-awesome
noto-fonts
noto-fonts-cjk
noto-fonts-cjk-sans
noto-fonts-emoji
noto-fonts-extra
dejavu_fonts
@@ -239,7 +229,7 @@
file
firefox
fluidsynth
gnome3.adwaita-icon-theme
adwaita-icon-theme
gnuplot
graphviz
helm
@@ -252,7 +242,6 @@
libqalculate
libreoffice
man-pages
nheko
nix-diff
nix-index
nix-output-monitor
+15
View File
@@ -40,6 +40,11 @@ artemist:
display_name: Artemis Tosini
forgejo: artemist
bb010g:
display_name: Dusk Banks
forgejo: bb010g
github: bb010g
cole-h:
display_name: Cole Helbling
github: cole-h
@@ -57,6 +62,9 @@ ericson:
display_name: John Ericson
github: ericson2314
gilice:
forgejo: gilice
goldstein:
display_name: goldstein
forgejo: goldstein
@@ -70,6 +78,10 @@ horrors:
iFreilicht:
github: iFreilicht
ian-h-chamberlain:
forgejo: ian-h-chamberlain
github: ian-h-chamberlain
isabelroses:
forgejo: isabelroses
github: isabelroses
@@ -78,6 +90,9 @@ jade:
forgejo: jade
github: lf-
just1602:
forgejo: just1602
kiara:
github: KiaraGrouwstra
@@ -1,21 +0,0 @@
---
synopsis: "Build failures caused by `allowSubstitutes = false` while being the wrong system now produce a decent error"
issues: [fj#484]
cls: [1841]
category: Fixes
credits: jade
---
Nix allows derivations to set `allowSubstitutes = false` in order to force them to be built locally without querying substituters for them.
This is useful for derivations that are very fast to build (especially if they produce large output).
However, this can shoot you in the foot if the derivation *has* to be substituted such as if the derivation is for another architecture, which is what `--always-allow-substitutes` is for.
Perhaps such derivations that are known to be impossible to build locally should ignore `allowSubstitutes` (irrespective of remote builders) in the future, but this at least reports the failure and solution directly.
```
$ nix build -f fail.nix
error: a 'unicornsandrainbows-linux' with features {} is required to build '/nix/store/...-meow.drv', but I am a 'x86_64-linux' with features {...}
Hint: the failing derivation has allowSubstitutes set to false, forcing it to be built rather than substituted.
Passing --always-allow-substitutes to force substitution may resolve this failure if the path is available in a substituter.
```
@@ -1,10 +0,0 @@
---
synopsis: "`Alt+Left` and `Alt+Right` go back/forwards by words in `nix repl`"
issues: [fj#501]
cls: [1883]
category: Fixes
credits: 9999years
---
`nix repl` now recognizes `Alt+Left` and `Alt+Right` for navigating by words
when entering input in `nix repl` on more terminals/platforms.
-15
View File
@@ -1,15 +0,0 @@
---
synopsis: "Drop support for `xz` and `bzip2` Content-Encoding"
category: Miscellany
cls: [2134]
credits: horrors
---
Lix no longer supports the non-standard HTTP Content-Encoding values `xz` and `bzip2`.
We do not expect this to cause any problems in practice since these encodings *aren't*
standard, and any server delivering them anyway without being asked to is already well
and truly set on the path of causing inexplicable client breakages.
Lix's ability to decompress files compressed with `xz` or `bzip2` is unaffected. We're
only bringing Lix more in line with the HTTP standard; all post-transfer data handling
remains as it was before.
-13
View File
@@ -1,13 +0,0 @@
---
synopsis: Ctrl-C stops Nix commands much more reliably and responsively
issues: [7245, fj#393]
cls: [2016]
prs: [11618]
category: Fixes
credits: [roberth, 9999years]
---
CTRL-C will now stop Nix commands much more reliably and responsively. While
there are still some cases where a Nix command can be slow or unresponsive
following a `SIGINT` (please report these as issues!), the vast majority of
signals will now cause the Nix command to quit quickly and consistently.
-18
View File
@@ -1,18 +0,0 @@
---
synopsis: Deprecated language features
issues: [fj#437, 861]
cls: [1785, 1736, 1735, 1744, 2206]
category: Breaking Changes
credits: [piegames, horrors]
---
A system for deprecation (and then the planned removal) of undesired language features has been put into place.
It is controlled via feature flags much like experimental features, except that the deprecations are enabled default,
and can be disabled via the flags for backwards compatibility (opt-out with `--extra-deprecated-features` or the Nix configuration file).
- `url-literals`: **URL literals** have long been obsolete and discouraged of use, and now they are officially deprecated.
This means that all URLs must be properly put within quotes like all other strings.
- `rec-set-overrides`: **__overrides** is an old arcane syntax which has not been in use for more than a decade.
It is soft-deprecated with a warning only, with the plan to turn that into an error in a future release.
- `ancient-let`: **The old `let` syntax** (`let { body = …; … }`) is soft-deprecated with a warning as well. Use the regular `let … in` instead.
- `shadow-internal-symbols`: Arithmetic expressions like `5 - 3` internally expand to `__sub 5 3`, where `__sub` maps to a subtraction builtin. Shadowing such a symbols would affect the evaluation of such operations, but in a very inconsistent way, and is therefore deprecated now. **Affected symbols are:** `__sub`, `__mul`, `__div`, `__lessThan`, `__findFile` and `__nixPath`. Note that these symbols may still be used as variable names as long as they do not shadow internal operations, so e.g. `let __sub = x: y: x + y; in __sub 3 5` remains valid code.
-10
View File
@@ -1,10 +0,0 @@
---
synopsis: "transfers no longer allow arbitrary url schemas"
category: Breaking Changes
cls: [2106]
credits: horrors
---
Lix no longer allows transfers using arbitrary url schemas. Only `http://`, `https://`, `ftp://`, `ftps://`, and `file://` urls are supported going forward. This affects `builtins.fetchurl`, `<nix/fetchurl.nix>`, transfers to and from binary caches, and all other uses of the internal file transfer code. Flake inputs using multi-protocol schemas (e.g. `git+ssh`) are not affected as those use external utilities to transfer data.
The `s3://` scheme is not affected at all by this change and continues to work if S3 support is built into Lix.
-23
View File
@@ -1,23 +0,0 @@
---
synopsis: restore backwards-compatibility of `builtins.fetchGit` with Nix 2.3
issues: [5291, 5128]
credits: [ma27]
category: Fixes
---
Compatibility with `builtins.fetchGit` from Nix 2.3 has been restored as follows:
* Until now, each `ref` was prefixed with `refs/heads` unless it starts with `refs/` itself.
Now, this is not done if the `ref` looks like a commit hash.
* Specifying `builtins.fetchGit { ref = "a-tag"; /* … */ }` was broken because `refs/heads` was appended.
Now, the fetcher doesn't turn a ref into `refs/heads/ref`, but into `refs/*/ref`. That way,
the value in `ref` can be either a tag or a branch.
* The ref resolution happens the same way as in git:
* If `refs/ref` exists, it's used.
* If a tag `refs/tags/ref` exists, it's used.
* If a branch `refs/heads/ref` exists, it's used.
-10
View File
@@ -1,10 +0,0 @@
---
synopsis: HTTP proxy environment variables are now respected for S3 binary cache stores
issues: [fj#433]
cls: [1788]
category: Fixes
credits: jade
---
Due to "legacy reasons" (according to the AWS C++ SDK docs), the AWS SDK ignores system proxy configuration by default.
We turned it back on.
@@ -1,20 +0,0 @@
---
synopsis: "Includes are now qualified with library name"
category: Development
cls: [2178]
credits: jade
---
The Lix includes have all been rearranged to be of the form `"lix/libexpr/foo.hh"` instead of `"foo.hh"`.
This was already supported externally for a migration period, but it is now being applied to all the internal usages within Lix itself.
The goal of this change is to both clarify where a file is from and to avoid polluting global include paths with things like `config.h` that might conflict with other projects.
For other details, see the release notes of Lix 2.90.0, under "Rename all the libraries" in Breaking Changes.
To fix an external project with sources in `src` which has a separate build directory (such that headers are in `../src` relative to where the compiler is running):
```
lix_root=$HOME/lix
(cd $lix_root && nix develop -c 'meson setup build && ninja -C build subprojects/lix-clang-tidy/liblix-clang-tidy.so')
run-clang-tidy -checks='-*,lix-fixincludes' -load=$lix_root/build/subprojects/lix-clang-tidy/liblix-clang-tidy.so -p build/ -header-filter '\.\./src/.*\.h' -fix src
```
@@ -1,13 +0,0 @@
---
synopsis: "Improvements to interactive flake config"
cls: [2066]
category: Improvements
credits: ma27
---
If `accept-flake-config` is set to `ask` and a `flake.nix` defines `nixConfig`,
Lix will ask on the CLI which of these settings should be used for the command.
Now, it's possible to answer with `N` (as opposed to `n` to only reject the setting
that is asked for) to reject _all untrusted_ entries from the flake's `nixConf`
section.
-13
View File
@@ -1,13 +0,0 @@
---
synopsis: Fix potential store corruption with auto-optimise-store
issues: [7273]
cls: [2100]
category: Fixes
credits: lilyball
---
Optimising store paths (and other operations involving temporary files) no longer use `random(3)`
to generate filenames. On darwin systems this was observed to potentially cause store corruption
when using [`auto-optimise-store`](@docroot@/command-ref/conf-file.md#conf-auto-optimise-store),
though this corruption was possible on any system whose `random(3)` does not have locking around
the global state.
-13
View File
@@ -1,13 +0,0 @@
---
synopsis: Change `nix-build -o ""` to behave like `--no-out-link`
cls: [2103]
category: Fixes
credits: lilyball
---
[`nix-build`](@docroot@/command-ref/nix-build.md)now treats <code>[--out-link](@docroot@/command-ref/nix-build.md#opt-out-link) ''</code>
the same as [`--no-out-link`](@docroot@/command-ref/nix-build.md#opt-no-out-link). This matches
[`nix build`](@docroot@/command-ref/new-cli/nix3-build.md) behavior. Previously when building the default output it
would have resulted in throwing an error saying the current working directory already exists, and when building any
other output it would have resulted in a symlink starting with a hyphen such as `-doc`, which is a footgun for
terminal commands.
@@ -1,38 +0,0 @@
---
synopsis: Removing the `.` default argument passed to the `nix fmt` formatter
issues: []
prs: [11438]
cls: [1902]
category: Breaking Changes
credits: zimbatm
---
The underlying formatter no longer receives the ". " default argument when `nix fmt` is called with no arguments.
This change was necessary as the formatter wasn't able to distinguish between
a user wanting to format the current folder with `nix fmt .` or the generic
`nix fmt`.
The default behaviour is now the responsibility of the formatter itself, and
allows tools such as treefmt to format the whole tree instead of only the
current directory and below.
This may cause issues with some formatters: nixfmt, nixpkgs-fmt and alejandra currently format stdin when no arguments are passed.
Here is a small wrapper example that will restore the previous behaviour for such a formatter:
```nix
{
outputs = { self, nixpkgs, systems }:
let
eachSystem = nixpkgs.lib.genAttrs (import systems) (system: nixpkgs.legacyPackages.${system});
in
{
formatter = eachSystem (pkgs:
pkgs.writeShellScriptBin "formatter" ''
if [[ $# = 0 ]]; set -- .; fi
exec "${pkgs.nixfmt-rfc-style}/bin/nixfmt "$@"
'');
};
}
```
-10
View File
@@ -1,10 +0,0 @@
---
synopsis: "The beginnings of a new pytest-based functional test suite"
category: Development
cls: [2036, 2037]
credits: jade
---
The existing integration/functional test suite is based on a large volume of shell scripts.
This often makes it somewhat challenging to debug at the best of times.
The goal of the pytest test suite is to make tests have more obvious dependencies on files and to make tests more concise and easier to write, as well as making new testing methods like snapshot testing easy.
@@ -1,17 +0,0 @@
---
synopsis: readline support removed
cls: [1885]
category: Packaging
credits: [9999years]
---
Support for building Lix with [`readline`][readline] instead of
[`editline`][editline] has been removed. `readline` support hasn't worked for a
long time (attempting to use it would lead to build errors) and would make Lix
subject to the GPL if it did work. In the future, we're hoping to replace
`editline` with [`rustyline`][rustyline] for improved ergonomics in the `nix
repl`.
[readline]: https://en.wikipedia.org/wiki/GNU_Readline
[editline]: https://github.com/troglobit/editline
[rustyline]: https://github.com/kkawakam/rustyline
@@ -1,30 +0,0 @@
---
synopsis: Relative and tilde paths in configuration
issues: [fj#482]
cls: [1851, 1863, 1864]
category: Features
credits: [9999years]
---
[Configuration settings](@docroot@/command-ref/conf-file.md) can now refer to
files with paths relative to the file they're written in or relative to your
home directory (with `~/`).
This makes settings like
[`repl-overlays`](@docroot@/command-ref/conf-file.md#conf-repl-overlays) and
[`secret-key-files`](@docroot@/command-ref/conf-file.md#conf-repl-overlays)
much easier to set, especially if you'd like to refer to files in an existing
dotfiles repo cloned into your home directory.
If you put `repl-overlays = repl.nix` in your `~/.config/nix/nix.conf`, it'll
load `~/.config/nix/repl.nix`. Similarly, you can set `repl-overlays =
~/.dotfiles/repl.nix` to load a file relative to your home directory.
Configuration files can also
[`include`](@docroot@/command-ref/conf-file.md#file-format) paths relative to
your home directory.
Only user configuration files (like `$XDG_CONFIG_HOME/nix/nix.conf` or the
files listed in `$NIX_USER_CONF_FILES`) can use tilde paths relative to your
home directory. Configuration listed in the `$NIX_CONFIG` environment variable
may not use relative paths.
@@ -1,8 +0,0 @@
---
synopsis: "Dependency on monolithic coreutils removed"
category: Development
cls: [2108]
credits: vigress8
---
Previously, the build erroneously depended on a `coreutils` binary, which requires `coreutils` to be built with a specific configuration. This was only used in one test and was not required to be a single binary. This dependency is removed now.
@@ -1,22 +0,0 @@
---
synopsis: "Reproducibility check builds now report all differing outputs"
cls: [2069]
category: Improvements
credits: [lheckemann]
---
`nix-build --check` allows rerunning the build of an already-built derivation to check that it produces the same output again.
If a multiple-output derivation with impure behaviour is built with `--check`, only the first output would be shown in the resulting error message (and kept for comparison):
```
error: derivation '/nix/store/4spy3nz1661zm15gkybsy1h5f36aliwx-python3.11-test-1.0.0.drv' may not be deterministic: output '/nix/store/ccqcp01zg18wp9iadzmzimqzdi3ll08d-python3.11-test
-1.0.0-dist' differs from '/nix/store/ccqcp01zg18wp9iadzmzimqzdi3ll08d-python3.11-test-1.0.0-dist.check'
```
Now, all differing outputs are kept and reported:
```
error: derivation '4spy3nz1661zm15gkybsy1h5f36aliwx-python3.11-test-1.0.0.drv' may not be deterministic: outputs differ
output differs: output '/nix/store/ccqcp01zg18wp9iadzmzimqzdi3ll08d-python3.11-test-1.0.0-dist' differs from '/nix/store/ccqcp01zg18wp9iadzmzimqzdi3ll08d-python3.11-test-1.0.0-dist.check'
output differs: output '/nix/store/yl59v08356i841c560alb0zmk7q16klb-python3.11-test-1.0.0' differs from '/nix/store/yl59v08356i841c560alb0zmk7q16klb-python3.11-test-1.0.0.check'
```
-12
View File
@@ -1,12 +0,0 @@
---
synopsis: Ignore broken `/etc/ssl/certs/ca-certificates.crt` symlink
issues: [fj#560]
cls: [2144]
category: Fixes
credits: lilyball
---
[`ssl-cert-file`](@docroot@/command-ref/conf-file.md#conf-ssl-cert-file) now checks its default
value for a broken symlink before using it. This fixes a problem on macOS where uninstalling
nix-darwin may leave behind a broken symlink at `/etc/ssl/certs/ca-certificates.crt` that was
stopping Lix from using the cert at `/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt`.
-26
View File
@@ -1,26 +0,0 @@
---
synopsis: "Some Lix crashes now produce reporting instructions and a stack trace, then abort"
cls: [1854]
category: Improvements
credits: jade
---
Lix, being a C++ program, can crash in a few kinds of ways.
It can obviously do a memory access violation, which will generate a core dump and thus be relatively debuggable.
But, worse, it could throw an unhandled exception, and, in the past, we would just show the message but not where it comes from, in spite of this always being a bug, since we expect all such errors to be translated to a Lix specific error.
Now the latter kind of bug should print reporting instructions, a rudimentary stack trace and (depending on system configuration) generate a core dump.
Sample output:
```
Lix crashed. This is a bug. We would appreciate if you report it along with what caused it at https://git.lix.systems/lix-project/lix/issues with the following information included:
Exception: std::runtime_error: test exception
Stack trace:
0# nix::printStackTrace() in /home/jade/lix/lix3/build/lix/nix/../libutil/liblixutil.so
1# 0x000073C9862331F2 in /home/jade/lix/lix3/build/lix/nix/../libmain/liblixmain.so
2# 0x000073C985F2E21A in /nix/store/p44qan69linp3ii0xrviypsw2j4qdcp2-gcc-13.2.0-lib/lib/libstdc++.so.6
3# 0x000073C985F2E285 in /nix/store/p44qan69linp3ii0xrviypsw2j4qdcp2-gcc-13.2.0-lib/lib/libstdc++.so.6
4# nix::handleExceptions(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::function<void ()>) in /home/jade/lix/lix3/build/lix/nix/../libmain/liblixmain.so
...
```
-15
View File
@@ -1,15 +0,0 @@
---
synopsis: Add a `temp-dir` setting to set the temporary directory location
issues: [7731, 8995, fj#112, fj#253]
cls: [2103]
category: Improvements
credits: lilyball
---
[`temp-dir`](@docroot@/command-ref/conf-file.md#conf-temp-dir) can now be set in the Nix
configuration to change the temporary directory. This can be used to relocate all temporary files
to another filesystem without affecting the `TMPDIR` env var inherited by interactive
`nix-shell`/`nix shell` shells or `nix run` commands.
Also on macOS, the `TMPDIR` env var is no longer unset for interactive shells when pointing
to a per-session `/var/folders/` directory.
-17
View File
@@ -1,17 +0,0 @@
---
synopsis: All Lix threads are named
cls: [2210]
category: Development
credits: [jade]
---
Lix now sets thread names on all of its secondary threads, which will make debugger usage slightly nicer and easier.
```
(gdb) info thr
Id Target Id Frame
* 1 LWP 3719283 "nix-daemon" 0x00007e558587da0f in accept ()
from target:/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libc.so.6
2 LWP 3719284 "signal handler" 0x00007e55857b2bea in sigtimedwait ()
from target:/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libc.so.6
```
-10
View File
@@ -1,10 +0,0 @@
---
synopsis: "`<nix/fetchurl.nix>` now uses TLS verification"
category: Fixes
prs: [11585]
credits: edolstra
---
Previously `<nix/fetchurl.nix>` did not do TLS verification. This was because the Nix sandbox in the past did not have access to TLS certificates, and Nix checks the hash of the fetched file anyway. However, this can expose authentication data from `netrc` and URLs to man-in-the-middle attackers. In addition, Nix now in some cases (such as when using impure derivations) does *not* check the hash. Therefore we have now enabled TLS verification. This means that downloads by `<nix/fetchurl.nix>` will now fail if you're fetching from a HTTPS server that does not have a valid certificate.
`<nix/fetchurl.nix>` is also known as the builtin derivation builder `builtin:fetchurl`. It's not to be confused with the evaluation-time function `builtins.fetchurl`, which was not affected by this issue.
-23
View File
@@ -1,23 +0,0 @@
---
synopsis: "Set `X-GitHub-Api-Version` header"
issues: [fj#255]
cls: [1925]
category: Development
credits: kiara
---
Sets the `X-GitHub-Api-Version` header to `2022-11-28` for calls to the
GitHub API.
This follows the later version as per
https://docs.github.com/en/rest/about-the-rest-api/api-versions?apiVersion=2022-11-28.
This affected the check on whether to use the API versus unauthenticated
calls as well, given the headers would no longer be empty if the
authentication token were missing.
The workaround to this used here is to use a check similar to an existing
check for the token.
In the current implementation, headers are (still) similarly sent to
non-authenticated as well as GitHub on-prem calls.
For what it's worth, manual curl calls with such a header seemed to
break nor unauthenticated calls nor ones to the github.com API.
+1
View File
@@ -198,6 +198,7 @@
- [Release Notes](release-notes/release-notes.md)
- [Upcoming release](release-notes/rl-next.md)
<!-- RELENG-AUTO-INSERTION-MARKER (see releng/release_notes.py) -->
- [Lix 2.92 (2025-01-18)](release-notes/rl-2.92.md)
- [Lix 2.91 (2024-08-12)](release-notes/rl-2.91.md)
- [Lix 2.90 (2024-07-10)](release-notes/rl-2.90.md)
- [Nix 2.18 (2023-09-20)](release-notes/rl-2.18.md)
+7 -1
View File
@@ -34,9 +34,15 @@ keep-outputs = true # Nice for developers
keep-derivations = true # Idem
```
Other files can be included with a line like `include <path>`, where `<path>` is interpreted relative to the current configuration file.
Other files can be included with a line like `include <path>`.
A missing file is an error unless `!include` is used instead.
Paths in `include`s and option values are interpreted relative to the current configuration file.
In user configuration files, paths starting with `~/` are tilde expanded (by replacing the tilde by the value of `$HOME`).
Only user configuration files (like `$XDG_CONFIG_HOME/nix/nix.conf` or the files listed in `$NIX_USER_CONF_FILES`) can use tilde paths relative to your home directory.
Configuration listed in the `$NIX_CONFIG` environment variable may not use relative paths.
A configuration setting usually overrides any previous value.
However, for settings that take a list of items, you can prefix the name of the setting by `extra-` to *append* to the previous value.
+2 -2
View File
@@ -26,8 +26,8 @@
| Logical conjunction (`AND`) | *bool* `&&` *bool* | left | 12 |
| Logical disjunction (`OR`) | *bool* <code>\|\|</code> *bool* | left | 13 |
| [Logical implication] | *bool* `->` *bool* | none | 14 |
| \[Experimental\] [Function piping] | *expr* |> *func* | left | 15 |
| \[Experimental\] [Function piping] | *expr* <| *func* | right | 16 |
| \[Experimental\] [Function piping] | *expr* `\|>` *func* | left | 15 |
| \[Experimental\] [Function piping] | *expr* `<\|` *func* | right | 16 |
[string]: ./values.md#type-string
[path]: ./values.md#type-path
+504
View File
@@ -0,0 +1,504 @@
# Lix 2.92 "Bombe glacée" (2025-01-18)
# Lix 2.92.2 (2025-06-23)
## Breaking Changes
- Fixed output derivations can be run using `pasta` network isolation [fj#285](https://git.lix.systems/lix-project/lix/issues/285) [cl/3430](https://gerrit.lix.systems/c/lix/+/3430)
Fixed output derivations traditionally run in the host network namespace.
On Linux this allows such derivations to communicate with other sandboxes
or the host using the abstract Unix domains socket namespace; this hasn't
been unproblematic in the past and has been used in two distinct exploits
to break out of the sandbox. For this reason fixed output derivations can
now run in a network namespace (provided by [`pasta`]), restricted to TCP
and UDP communication with the rest of the world. When enabled this could
be a breaking change and we classify it as such, even though we don't yet
enable or require such isolation by default. We may enforce this in later
releases of Lix once we have sufficient confidence that breakage is rare.
[`pasta`]: https://passt.top/
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) and [puck](https://git.lix.systems/puck) for this.
## Fixes
- Always clean up scratch paths after derivations failed to build [cl/3432](https://gerrit.lix.systems/c/lix/+/3432)
Previously, scratch paths created during builds were not always cleaned up if
the derivation failed, potentially leaving behind unnecessary temporary files
or directories in the Nix store.
This fix ensures that such paths are consistently removed after a failed build,
improving Nix store hygiene, hardening Lix against mis-reuse of failed builds
scratch paths.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) and [eldritch horrors](https://git.lix.systems/pennae) for this.
- `build-dir` no longer defaults to `temp-dir` [cl/3431](https://gerrit.lix.systems/c/lix/+/3431)
The directory in which temporary build directories are created no longer defaults
to the value of the `temp-dir` setting to avoid builders making their directories
world-accessible. This behavior has been used to escape the build sandbox and can
cause build impurities even when not used maliciously. We now default to `builds`
in `NIX_STATE_DIR` (which is `/nix/var/nix/builds` in the default configuration).
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
- Forbid impure path accesses in pure evaluation mode again [cl/2708](https://gerrit.lix.systems/c/lix/+/2708)
Lix 2.92.0 mistakenly started allowing the access to ancestors of allowed paths in pure evaluation mode.
This made it possible to bypass the purity restrictions, for example by copying arbitrary files to the store:
```nix
builtins.path {
path = "/";
filter = …;
}
```
Restore the previous behaviour of prohibiting such impure accesses.
Many thanks to [alois31](https://git.lix.systems/alois31) for this.
- Parsing failures in flake.lock no longer crash Lix [fj#559](https://git.lix.systems/lix-project/lix/issues/559) [cl/2401](https://gerrit.lix.systems/c/lix/+/2401)
Failure to parse `flake.lock` no longer hard-crashes Lix and instead produces a nice error message.
```
error:
… while updating the lock file of flake 'git+file:///Users/jade/lix/lix2'
… while parsing the lock file at /nix/store/mm5dqh8a729yazzj82cjffxl97n5c62s-source//flake.lock
error: [json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - invalid literal;
last read: '#'
```
Many thanks to [gilice](https://git.lix.systems/gilice) for this.
- Fix `--debugger --ignore-try` [cl/2440](https://gerrit.lix.systems/c/lix/+/2440)
When in debug mode (e.g. from using the `--debugger` flag), enabling [`ignore-try`](@docroot@/command-ref/conf-file.md#conf-ignore-try) once again properly disables debug REPLs within [`builtins.tryEval`](@docroot@/language/builtins.md#builtins-tryEval) calls. Previously, a debug REPL would be started as if `ignore-try` was disabled, but that REPL wouldn't actually be in debug mode, and upon exiting the REPL the evaluating process would segfault.
Many thanks to [Dusk Banks](https://git.lix.systems/bb010g) for this.
# Lix 2.92.0 (2025-01-18)
## Breaking Changes
- Deprecated language features [fj#437](https://git.lix.systems/lix-project/lix/issues/437) [#861](https://github.com/NixOS/nix/issues/861) [cl/1785](https://gerrit.lix.systems/c/lix/+/1785) [cl/1736](https://gerrit.lix.systems/c/lix/+/1736) [cl/1735](https://gerrit.lix.systems/c/lix/+/1735) [cl/1744](https://gerrit.lix.systems/c/lix/+/1744) [cl/2206](https://gerrit.lix.systems/c/lix/+/2206)
A system for deprecation (and then the planned removal) of undesired language features has been put into place.
It is controlled via feature flags much like experimental features, except that the deprecations are enabled default,
and can be disabled via the flags for backwards compatibility (opt-out with `--extra-deprecated-features` or the Nix configuration file).
- `url-literals`: **URL literals** have long been obsolete and discouraged of use, and now they are officially deprecated.
This means that all URLs must be properly put within quotes like all other strings.
- `rec-set-overrides`: **__overrides** is an old arcane syntax which has not been in use for more than a decade.
It is soft-deprecated with a warning only, with the plan to turn that into an error in a future release.
- `ancient-let`: **The old `let` syntax** (`let { body = …; … }`) is soft-deprecated with a warning as well. Use the regular `let … in` instead.
- `shadow-internal-symbols`: Arithmetic expressions like `5 - 3` internally expand to `__sub 5 3`, where `__sub` maps to a subtraction builtin. Shadowing such a symbols would affect the evaluation of such operations, but in a very inconsistent way, and is therefore deprecated now. **Affected symbols are:** `__sub`, `__mul`, `__div` and `__lessThan`. Note that these symbols may still be used as variable names as long as they do not shadow internal operations, so e.g. `let __sub = x: y: x + y; in __sub 3 5` remains valid code.
- **Call to action:** If you have any use cases or workflows that depend on being able to override the `__nixPath` and `__findFile` symbols, please reach out to us. We want to eventually deprecate overriding these as well, and need input on how to design a better alternative.
Many thanks to [piegames](https://git.lix.systems/piegames) and [eldritch horrors](https://git.lix.systems/pennae) for this.
- transfers no longer allow arbitrary url schemas [cl/2106](https://gerrit.lix.systems/c/lix/+/2106)
Lix no longer allows transfers using arbitrary url schemas. Only `http://`, `https://`, `ftp://`, `ftps://`, and `file://` urls are supported going forward. This affects `builtins.fetchurl`, `<nix/fetchurl.nix>`, transfers to and from binary caches, and all other uses of the internal file transfer code. Flake inputs using multi-protocol schemas (e.g. `git+ssh`) are not affected as those use external utilities to transfer data.
The `s3://` scheme is not affected at all by this change and continues to work if S3 support is built into Lix.
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
- Removing the `.` default argument passed to the `nix fmt` formatter [#11438](https://github.com/NixOS/nix/pull/11438) [cl/1902](https://gerrit.lix.systems/c/lix/+/1902)
The underlying formatter no longer receives the ". " default argument when `nix fmt` is called with no arguments.
This change was necessary as the formatter wasn't able to distinguish between
a user wanting to format the current folder with `nix fmt .` or the generic
`nix fmt`.
The default behaviour is now the responsibility of the formatter itself, and
allows tools such as treefmt to format the whole tree instead of only the
current directory and below.
This may cause issues with some formatters: nixfmt, nixpkgs-fmt and alejandra currently format stdin when no arguments are passed.
Here is a small wrapper example that will restore the previous behaviour for such a formatter:
```nix
{
outputs = { self, nixpkgs, systems }:
let
eachSystem = nixpkgs.lib.genAttrs (import systems) (system: nixpkgs.legacyPackages.${system});
in
{
formatter = eachSystem (pkgs:
pkgs.writeShellScriptBin "formatter" ''
if [[ $# = 0 ]]; set -- .; fi
exec "${pkgs.nixfmt-rfc-style}/bin/nixfmt "$@"
'');
};
}
```
Many thanks to [zimbatm](https://github.com/zimbatm) for this.
## Features
- Relative and tilde paths in configuration [fj#482](https://git.lix.systems/lix-project/lix/issues/482) [cl/1851](https://gerrit.lix.systems/c/lix/+/1851) [cl/1863](https://gerrit.lix.systems/c/lix/+/1863) [cl/1864](https://gerrit.lix.systems/c/lix/+/1864)
[Configuration settings](@docroot@/command-ref/conf-file.md) can now refer to
files with paths relative to the file they're written in or relative to your
home directory (with `~/`).
This makes settings like
[`repl-overlays`](@docroot@/command-ref/conf-file.md#conf-repl-overlays) and
[`secret-key-files`](@docroot@/command-ref/conf-file.md#conf-repl-overlays)
much easier to set, especially if you'd like to refer to files in an existing
dotfiles repo cloned into your home directory.
If you put `repl-overlays = repl.nix` in your `~/.config/nix/nix.conf`, it'll
load `~/.config/nix/repl.nix`. Similarly, you can set `repl-overlays =
~/.dotfiles/repl.nix` to load a file relative to your home directory.
Configuration files can also
[`include`](@docroot@/command-ref/conf-file.md#file-format) paths relative to
your home directory.
Only user configuration files (like `$XDG_CONFIG_HOME/nix/nix.conf` or the
files listed in `$NIX_USER_CONF_FILES`) can use tilde paths relative to your
home directory. Configuration listed in the `$NIX_CONFIG` environment variable
may not use relative paths.
Many thanks to [wiggles](https://git.lix.systems/rbt) for this.
## Improvements
- Improved error messages for bad attr paths [cl/2277](https://gerrit.lix.systems/c/lix/+/2277) [cl/2280](https://gerrit.lix.systems/c/lix/+/2280)
Lix now includes much more detail when a bad attribute path is accessed at the command line:
```
» nix eval -f '<nixpkgs>' lixVersions.lix_2_92
error: attribute 'lix_2_92' in selection path 'lixVersions.lix_2_92' not found
Did you mean one of lix_2_90 or lix_2_91?
```
After:
```
» nix eval --impure -f '<nixpkgs>' lixVersions.lix_2_92
error: attribute 'lix_2_92' in selection path 'lixVersions.lix_2_92' not found inside path 'lixVersions', whose contents are: { __unfix__ = «lambda @ /nix/store/hfz1qqd0z8amlgn8qwich1dvkmldik36-source/lib/fixed-points.nix:
447:7»; buildLix = «thunk»; extend = «thunk»; latest = «thunk»; lix_2_90 = «thunk»; lix_2_91 = «thunk»; override = «thunk»; overrideDerivation = «thunk»; recurseForDerivations = true; stable = «thunk»; }
Did you mean one of lix_2_90 or lix_2_91?
```
This should avoid some unnecessary trips to the repl or to the debugger by giving some information about the value being selected on that was unexpected.
Many thanks to [jade](https://git.lix.systems/jade) for this.
- Small error message improvements [cl/2185](https://gerrit.lix.systems/c/lix/+/2185) [cl/2187](https://gerrit.lix.systems/c/lix/+/2187)
When an attribute selection fails, the error message now correctly points to the attribute in the chain that failed instead of at the beginning of the entire chain.
```diff
error: attribute 'x' missing
- at /pwd/lang/eval-fail-remove.nix:4:3:
+ at /pwd/lang/eval-fail-remove.nix:4:29:
3| in
4| (removeAttrs attrs ["x"]).x
- | ^
+ | ^
5|
```
Failed asserts don't print the failed assertion expression anymore in the error message. That code was buggy and the information was redundant anyways, given that the error position already more accurately shows what exactly failed.
Many thanks to [piegames](https://git.lix.systems/piegames) for this.
- Improvements to interactive flake config [cl/2066](https://gerrit.lix.systems/c/lix/+/2066)
If `accept-flake-config` is set to `ask` and a `flake.nix` defines `nixConfig`,
Lix will ask on the CLI which of these settings should be used for the command.
Now, it's possible to answer with `N` (as opposed to `n` to only reject the setting
that is asked for) to reject _all untrusted_ entries from the flake's `nixConf`
section.
Many thanks to [ma27](https://git.lix.systems/ma27) for this.
- `nix --version` now shows details about the installation by default [fj#620](https://git.lix.systems/lix-project/lix/issues/620) [cl/2365](https://gerrit.lix.systems/c/lix/+/2365)
This happened with `nix-env --version` by default, but due to [oddities around the nix3 CLI's verbosity](https://gerrit.lix.systems/c/lix/+/1370), it used to be `nix --verbose --version`.
No longer:
```
$ nix --version
nix (Lix, like Nix) 2.92.0-dev-pre20250117-0d14c2b
System type: x86_64-linux
Additional system types: i686-linux, x86_64-v1-linux, x86_64-v2-linux, x86_64-v3-linux
Features: gc, signed-caches
System configuration file: /etc/nix/nix.conf
User configuration files: /home/jade/.config/nix/nix.conf:/etc/xdg/nix/nix.conf
Store directory: /nix/store
State directory: /nix/var/nix
Data directory: /nix/store/rliimcnqkplrqdgm4z6yqclpr6c32wh6-lix-2.92.0-dev-pre20250117-0d14c2b/share
```
Many thanks to [just1602](https://git.lix.systems/just1602) for this.
- `nix repl` correctly tab-completes attribute names that require quotes [cl/1783](https://gerrit.lix.systems/c/lix/+/1783)
The REPL (`nix repl`) now includes quotes as part of attribute names while completing with `<TAB>`,
if necessary. For example, attribute names like `"hello@example.com"` or `"hello world"` would
be suggested without quotes, resulting in invalid syntax.
Many thanks to [ian-h-chamberlain](https://git.lix.systems/ian-h-chamberlain) for this.
- Reproducibility check builds now report all differing outputs [cl/2069](https://gerrit.lix.systems/c/lix/+/2069)
`nix-build --check` allows rerunning the build of an already-built derivation to check that it produces the same output again.
If a multiple-output derivation with impure behaviour is built with `--check`, only the first output would be shown in the resulting error message (and kept for comparison):
```
error: derivation '/nix/store/4spy3nz1661zm15gkybsy1h5f36aliwx-python3.11-test-1.0.0.drv' may not be deterministic: output '/nix/store/ccqcp01zg18wp9iadzmzimqzdi3ll08d-python3.11-test
-1.0.0-dist' differs from '/nix/store/ccqcp01zg18wp9iadzmzimqzdi3ll08d-python3.11-test-1.0.0-dist.check'
```
Now, all differing outputs are kept and reported:
```
error: derivation '4spy3nz1661zm15gkybsy1h5f36aliwx-python3.11-test-1.0.0.drv' may not be deterministic: outputs differ
output differs: output '/nix/store/ccqcp01zg18wp9iadzmzimqzdi3ll08d-python3.11-test-1.0.0-dist' differs from '/nix/store/ccqcp01zg18wp9iadzmzimqzdi3ll08d-python3.11-test-1.0.0-dist.check'
output differs: output '/nix/store/yl59v08356i841c560alb0zmk7q16klb-python3.11-test-1.0.0' differs from '/nix/store/yl59v08356i841c560alb0zmk7q16klb-python3.11-test-1.0.0.check'
```
Many thanks to [lheckemann](https://git.lix.systems/lheckemann) for this.
- Some Lix crashes now produce reporting instructions and a stack trace, then abort [cl/1854](https://gerrit.lix.systems/c/lix/+/1854)
Lix, being a C++ program, can crash in a few kinds of ways.
It can obviously do a memory access violation, which will generate a core dump and thus be relatively debuggable.
But, worse, it could throw an unhandled exception, and, in the past, we would just show the message but not where it comes from, in spite of this always being a bug, since we expect all such errors to be translated to a Lix specific error.
Now the latter kind of bug should print reporting instructions, a rudimentary stack trace and (depending on system configuration) generate a core dump.
Sample output:
```
Lix crashed. This is a bug. We would appreciate if you report it along with what caused it at https://git.lix.systems/lix-project/lix/issues with the following information included:
Exception: std::runtime_error: test exception
Stack trace:
0# nix::printStackTrace() in /home/jade/lix/lix3/build/lix/nix/../libutil/liblixutil.so
1# 0x000073C9862331F2 in /home/jade/lix/lix3/build/lix/nix/../libmain/liblixmain.so
2# 0x000073C985F2E21A in /nix/store/p44qan69linp3ii0xrviypsw2j4qdcp2-gcc-13.2.0-lib/lib/libstdc++.so.6
3# 0x000073C985F2E285 in /nix/store/p44qan69linp3ii0xrviypsw2j4qdcp2-gcc-13.2.0-lib/lib/libstdc++.so.6
4# nix::handleExceptions(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::function<void ()>) in /home/jade/lix/lix3/build/lix/nix/../libmain/liblixmain.so
...
```
Many thanks to [jade](https://git.lix.systems/jade) for this.
- Add a `temp-dir` setting to set the temporary directory location [#7731](https://github.com/NixOS/nix/issues/7731) [#8995](https://github.com/NixOS/nix/issues/8995) [fj#112](https://git.lix.systems/lix-project/lix/issues/112) [fj#253](https://git.lix.systems/lix-project/lix/issues/253) [cl/2103](https://gerrit.lix.systems/c/lix/+/2103)
[`temp-dir`](@docroot@/command-ref/conf-file.md#conf-temp-dir) can now be set in the Nix
configuration to change the temporary directory. This can be used to relocate all temporary files
to another filesystem without affecting the `TMPDIR` env var inherited by interactive
`nix-shell`/`nix shell` shells or `nix run` commands.
Also on macOS, the `TMPDIR` env var is no longer unset for interactive shells when pointing
to a per-session `/var/folders/` directory.
Many thanks to [lilyball](https://git.lix.systems/lilyball) for this.
## Fixes
- Build failures caused by `allowSubstitutes = false` while being the wrong system now produce a decent error [fj#484](https://git.lix.systems/lix-project/lix/issues/484) [cl/1841](https://gerrit.lix.systems/c/lix/+/1841)
Nix allows derivations to set `allowSubstitutes = false` in order to force them to be built locally without querying substituters for them.
This is useful for derivations that are very fast to build (especially if they produce large output).
However, this can shoot you in the foot if the derivation *has* to be substituted such as if the derivation is for another architecture, which is what `--always-allow-substitutes` is for.
Perhaps such derivations that are known to be impossible to build locally should ignore `allowSubstitutes` (irrespective of remote builders) in the future, but this at least reports the failure and solution directly.
```
$ nix build -f fail.nix
error: a 'unicornsandrainbows-linux' with features {} is required to build '/nix/store/...-meow.drv', but I am a 'x86_64-linux' with features {...}
Hint: the failing derivation has allowSubstitutes set to false, forcing it to be built rather than substituted.
Passing --always-allow-substitutes to force substitution may resolve this failure if the path is available in a substituter.
```
Many thanks to [jade](https://git.lix.systems/jade) for this.
- `Alt+Left` and `Alt+Right` go back/forwards by words in `nix repl` [fj#501](https://git.lix.systems/lix-project/lix/issues/501) [cl/1883](https://gerrit.lix.systems/c/lix/+/1883)
`nix repl` now recognizes `Alt+Left` and `Alt+Right` for navigating by words
when entering input in `nix repl` on more terminals/platforms.
Many thanks to [wiggles](https://git.lix.systems/rbt) for this.
- Ctrl-C stops Nix commands much more reliably and responsively [#7245](https://github.com/NixOS/nix/issues/7245) [fj#393](https://git.lix.systems/lix-project/lix/issues/393) [#11618](https://github.com/NixOS/nix/pull/11618) [cl/2016](https://gerrit.lix.systems/c/lix/+/2016)
CTRL-C will now stop Nix commands much more reliably and responsively. While
there are still some cases where a Nix command can be slow or unresponsive
following a `SIGINT` (please report these as issues!), the vast majority of
signals will now cause the Nix command to quit quickly and consistently.
Many thanks to [Robert Hensing](https://github.com/roberth) and [wiggles](https://git.lix.systems/rbt) for this.
- restore backwards-compatibility of `builtins.fetchGit` with Nix 2.3 [#5291](https://github.com/NixOS/nix/issues/5291) [#5128](https://github.com/NixOS/nix/issues/5128)
Compatibility with `builtins.fetchGit` from Nix 2.3 has been restored as follows:
* Until now, each `ref` was prefixed with `refs/heads` unless it starts with `refs/` itself.
Now, this is not done if the `ref` looks like a commit hash.
* Specifying `builtins.fetchGit { ref = "a-tag"; /* … */ }` was broken because `refs/heads` was appended.
Now, the fetcher doesn't turn a ref into `refs/heads/ref`, but into `refs/*/ref`. That way,
the value in `ref` can be either a tag or a branch.
* The ref resolution happens the same way as in git:
* If `refs/ref` exists, it's used.
* If a tag `refs/tags/ref` exists, it's used.
* If a branch `refs/heads/ref` exists, it's used.
Many thanks to [ma27](https://git.lix.systems/ma27) for this.
- Flakes/restrict-eval no longer allow reading contents of impure paths
Flakes and `--restrict-eval` now correctly restrict access to paths as intended.
In prior versions since at least 2.18, `nix eval --raw .#lol` for the following flake didn't throw an error and acted as if `--impure` was passed.
Thanks to the person who reported this for telling us about it.
This was handled as a low-severity security bug, but is not a violation of the [documented security model](../installation/multi-user.md) as untrusted Nix code should be assumed to have the privileges of the user running the evaluator.
To report a security bug, email a report to `security at lix dot systems`.
```nix
{
inputs = {};
outputs = {...}: {
lol = builtins.readFile "${/etc/passwd}";
};
}
```
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
- HTTP proxy environment variables are now respected for S3 binary cache stores [fj#433](https://git.lix.systems/lix-project/lix/issues/433) [cl/1788](https://gerrit.lix.systems/c/lix/+/1788)
Due to "legacy reasons" (according to the AWS C++ SDK docs), the AWS SDK ignores system proxy configuration by default.
We turned it back on.
Many thanks to [jade](https://git.lix.systems/jade) for this.
- Fix potential store corruption with auto-optimise-store [#7273](https://github.com/NixOS/nix/issues/7273) [cl/2100](https://gerrit.lix.systems/c/lix/+/2100)
Optimising store paths (and other operations involving temporary files) no longer use `random(3)`
to generate filenames. On darwin systems this was observed to potentially cause store corruption
when using [`auto-optimise-store`](@docroot@/command-ref/conf-file.md#conf-auto-optimise-store),
though this corruption was possible on any system whose `random(3)` does not have locking around
the global state.
Many thanks to [lilyball](https://git.lix.systems/lilyball) for this.
- Change `nix-build -o ""` to behave like `--no-out-link` [cl/2103](https://gerrit.lix.systems/c/lix/+/2103)
[`nix-build`](@docroot@/command-ref/nix-build.md) now treats <code>[--out-link](@docroot@/command-ref/nix-build.md#opt-out-link) ''</code>
the same as [`--no-out-link`](@docroot@/command-ref/nix-build.md#opt-no-out-link). This matches
[`nix build`](@docroot@/command-ref/new-cli/nix3-build.md) behavior. Previously when building the default output it
would have resulted in throwing an error saying the current working directory already exists, and when building any
other output it would have resulted in a symlink starting with a hyphen such as `-doc`, which is a footgun for
terminal commands.
Many thanks to [lilyball](https://git.lix.systems/lilyball) for this.
- Ignore broken `/etc/ssl/certs/ca-certificates.crt` symlink [fj#560](https://git.lix.systems/lix-project/lix/issues/560) [cl/2144](https://gerrit.lix.systems/c/lix/+/2144)
[`ssl-cert-file`](@docroot@/command-ref/conf-file.md#conf-ssl-cert-file) now checks its default
value for a broken symlink before using it. This fixes a problem on macOS where uninstalling
nix-darwin may leave behind a broken symlink at `/etc/ssl/certs/ca-certificates.crt` that was
stopping Lix from using the cert at `/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt`.
Many thanks to [lilyball](https://git.lix.systems/lilyball) for this.
- `<nix/fetchurl.nix>` now uses TLS verification [#11585](https://github.com/NixOS/nix/pull/11585)
Previously `<nix/fetchurl.nix>` did not do TLS verification. This was because the Nix sandbox in the past did not have access to TLS certificates, and Nix checks the hash of the fetched file anyway. However, this can expose authentication data from `netrc` and URLs to man-in-the-middle attackers. In addition, Nix now in some cases (such as when using impure derivations) does *not* check the hash. Therefore we have now enabled TLS verification. This means that downloads by `<nix/fetchurl.nix>` will now fail if you're fetching from a HTTPS server that does not have a valid certificate.
`<nix/fetchurl.nix>` is also known as the builtin derivation builder `builtin:fetchurl`. It's not to be confused with the evaluation-time function `builtins.fetchurl`, which was not affected by this issue.
Many thanks to [Eelco Dolstra](https://github.com/edolstra) for this.
## Packaging
- readline support removed [cl/1885](https://gerrit.lix.systems/c/lix/+/1885)
Support for building Lix with [`readline`][readline] instead of
[`editline`][editline] has been removed. `readline` support hasn't worked for a
long time (attempting to use it would lead to build errors) and would make Lix
subject to the GPL if it did work. In the future, we're hoping to replace
`editline` with [`rustyline`][rustyline] for improved ergonomics in the `nix
repl`.
[readline]: https://en.wikipedia.org/wiki/GNU_Readline
[editline]: https://github.com/troglobit/editline
[rustyline]: https://github.com/kkawakam/rustyline
Many thanks to [wiggles](https://git.lix.systems/rbt) for this.
## Development
- Includes are now qualified with library name everywhere [cl/2178](https://gerrit.lix.systems/c/lix/+/2178) [cl/2362](https://gerrit.lix.systems/c/lix/+/2362)
The Lix includes have all been rearranged to be of the form `"lix/libexpr/foo.hh"` instead of `"foo.hh"`.
This was already supported externally for a migration period, but it is now being applied to all the internal usages within Lix itself.
The goal of this change is to both clarify where a file is from and to avoid polluting global include paths with things like `config.h` that might conflict with other projects.
Lix 2.92 removes support for the old `"foo.hh"` include form either internally or externally (that is, via pkg-config for things linking to Lix).
For other details, see the release notes of Lix 2.90.0, under "Rename all the libraries" in Breaking Changes.
To fix an external project with sources in `src` which has a separate build directory (such that headers are in `../src` relative to where the compiler is running), use a checkout of Lix 2.91 to run the following:
```
lix_root=$HOME/lix
(cd $lix_root && nix develop -c 'meson setup build && ninja -C build subprojects/lix-clang-tidy/liblix-clang-tidy.so')
run-clang-tidy -checks='-*,lix-fixincludes' -load=$lix_root/build/subprojects/lix-clang-tidy/liblix-clang-tidy.so -p build/ -header-filter '\.\./src/.*\.h' -fix src
```
Many thanks to [jade](https://git.lix.systems/jade) for this.
- The beginnings of a new pytest-based functional test suite [cl/2036](https://gerrit.lix.systems/c/lix/+/2036) [cl/2037](https://gerrit.lix.systems/c/lix/+/2037)
The existing integration/functional test suite is based on a large volume of shell scripts.
This often makes it somewhat challenging to debug at the best of times.
The goal of the pytest test suite is to make tests have more obvious dependencies on files and to make tests more concise and easier to write, as well as making new testing methods like snapshot testing easy.
Many thanks to [jade](https://git.lix.systems/jade) for this.
- Dependency on monolithic coreutils removed [cl/2108](https://gerrit.lix.systems/c/lix/+/2108)
Previously, the build erroneously depended on a `coreutils` binary, which requires `coreutils` to be built with a specific configuration. This was only used in one test and was not required to be a single binary. This dependency is removed now.
Many thanks to [Vigress](https://git.lix.systems/vigress8) for this.
- All Lix threads are named [cl/2210](https://gerrit.lix.systems/c/lix/+/2210)
Lix now sets thread names on all of its secondary threads, which will make debugger usage slightly nicer and easier.
```
(gdb) info thr
Id Target Id Frame
* 1 LWP 3719283 "nix-daemon" 0x00007e558587da0f in accept ()
from target:/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libc.so.6
2 LWP 3719284 "signal handler" 0x00007e55857b2bea in sigtimedwait ()
from target:/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libc.so.6
```
Many thanks to [jade](https://git.lix.systems/jade) for this.
- Set `X-GitHub-Api-Version` header [fj#255](https://git.lix.systems/lix-project/lix/issues/255) [cl/1925](https://gerrit.lix.systems/c/lix/+/1925)
Sets the `X-GitHub-Api-Version` header to `2022-11-28` for calls to the
GitHub API.
This follows the later version as per
https://docs.github.com/en/rest/about-the-rest-api/api-versions?apiVersion=2022-11-28.
This affected the check on whether to use the API versus unauthenticated
calls as well, given the headers would no longer be empty if the
authentication token were missing.
The workaround to this used here is to use a check similar to an existing
check for the token.
In the current implementation, headers are (still) similarly sent to
non-authenticated as well as GitHub on-prem calls.
For what it's worth, manual curl calls with such a header seemed to
break nor unauthenticated calls nor ones to the github.com API.
Many thanks to [kiara](https://github.com/KiaraGrouwstra) for this.
## Miscellany
- Drop support for `xz` and `bzip2` Content-Encoding [cl/2134](https://gerrit.lix.systems/c/lix/+/2134)
Lix no longer supports the non-standard HTTP Content-Encoding values `xz` and `bzip2`.
We do not expect this to cause any problems in practice since these encodings *aren't*
standard, and any server delivering them anyway without being asked to is already well
and truly set on the path of causing inexplicable client breakages.
Lix's ability to decompress files compressed with `xz` or `bzip2` is unaffected. We're
only bringing Lix more in line with the HTTP standard; all post-transfer data handling
remains as it was before.
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
Generated
+7 -7
View File
@@ -34,16 +34,16 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1727184566,
"narHash": "sha256-mgdK8BcFsLSNhe780+cHbEUbZ3OruLa1T/xgQlL4Aj4=",
"lastModified": 1733348545,
"narHash": "sha256-b4JrUmqT0vFNx42aEN9LTWOHomkTKL/ayLopflVf81U=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "48c3030083c46042584531bc9d931020f1975677",
"rev": "9ecb50d2fae8680be74c08bb0a995c5383747f89",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05-small",
"ref": "nixos-24.11-small",
"repo": "nixpkgs",
"type": "github"
}
@@ -67,11 +67,11 @@
"pre-commit-hooks": {
"flake": false,
"locked": {
"lastModified": 1726745158,
"narHash": "sha256-D5AegvGoEjt4rkKedmxlSEmC+nNLMBPWFxvmYnVLhjk=",
"lastModified": 1733318908,
"narHash": "sha256-SVQVsbafSM1dJ4fpgyBqLZ+Lft+jcQuMtEL3lQWx2Sk=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "4e743a6920eab45e8ba0fbe49dc459f1423a4b74",
"rev": "6f4e2a2112050951a314d2733a994fbab94864c6",
"type": "github"
},
"original": {
+4 -4
View File
@@ -2,7 +2,7 @@
description = "Lix: A modern, delicious implementation of the Nix package manager";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05-small";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11-small";
nixpkgs-regression.url = "github:NixOS/nixpkgs/215d4d0fd80ca5163643b03a33fde804a29cc1e2";
pre-commit-hooks = {
url = "github:cachix/git-hooks.nix";
@@ -350,10 +350,10 @@
name = "nixpkgs-lib-tests";
paths =
[ testWithNix ]
# FIXME: This is disabled on darwin due to a nixpkgs bug https://github.com/NixOS/nixpkgs/issues/319147
# After that is fixed, it should be restored to use lib/tests/release.nix as before, rather than this reimplementation.
# NOTE: nixpkgs 24.11 is being ... *creative*, and requires this dance to override
# the evaluator used for the test. it will break again in the future, don't worry.
++ lib.optionals pkgs.stdenv.isLinux [
(import (nixpkgs + "/pkgs/test/release") { inherit pkgs lib nix; })
(pkgs.callPackage "${nixpkgs}/ci/eval" { nixVersions.nix_2_24 = nix; }).attrpathsSuperset
];
}
);
+4 -10
View File
@@ -1,4 +1,5 @@
#include <algorithm>
#include <chrono>
#include <set>
#include <memory>
#include <tuple>
@@ -23,9 +24,6 @@
namespace nix {
static void handleAlarm(int sig) {
}
std::string escapeUri(std::string uri)
{
std::replace(uri.begin(), uri.end(), '/', '_');
@@ -137,7 +135,7 @@ static int main_build_remote(std::string programName, Strings argv)
while (true) {
bestSlotLock.reset();
AutoCloseFD lock = openLockFile(currentLoad + "/main-lock", true);
lockFile(lock.get(), ltWrite, true);
lockFile(lock.get(), ltWrite);
bool rightType = false;
@@ -156,7 +154,7 @@ static int main_build_remote(std::string programName, Strings argv)
uint64_t load = 0;
for (uint64_t slot = 0; slot < m.maxJobs; ++slot) {
auto slotLock = openSlotLock(m, slot);
if (lockFile(slotLock.get(), ltWrite, false)) {
if (tryLockFile(slotLock.get(), ltWrite)) {
if (!free) {
free = std::move(slotLock);
}
@@ -279,12 +277,8 @@ connected:
{
Activity act(*logger, lvlTalkative, actUnknown, fmt("waiting for the upload lock to '%s'", storeUri));
auto old = signal(SIGALRM, handleAlarm);
alarm(15 * 60);
if (!lockFile(uploadLock.get(), ltWrite, true))
if (!unsafeLockFileSingleThreaded(uploadLock.get(), ltWrite, std::chrono::minutes(15)))
printError("somebody is hogging the upload lock for '%s', continuing...");
alarm(0);
signal(SIGALRM, old);
}
auto substitute = settings.buildersUseSubstitutes ? Substitute : NoSubstitute;
+22 -17
View File
@@ -198,15 +198,16 @@ static void main_nix_build(std::string programName, Strings argv)
auto store = openStore();
auto evalStore = myArgs.evalStoreUrl ? openStore(*myArgs.evalStoreUrl) : store;
auto state = std::make_unique<EvalState>(myArgs.searchPath, evalStore, store);
state->repair = myArgs.repair;
auto evaluator = std::make_unique<Evaluator>(myArgs.searchPath, evalStore, store);
evaluator->repair = myArgs.repair;
auto state = evaluator->begin();
if (myArgs.repair) buildMode = bmRepair;
auto autoArgs = myArgs.getAutoArgs(*state);
auto autoArgs = myArgs.getAutoArgs(*evaluator);
auto autoArgsWithInNixShell = autoArgs;
if (runEnv) {
auto newArgs = state->buildBindings(autoArgsWithInNixShell->size() + 1);
auto newArgs = evaluator->buildBindings(autoArgsWithInNixShell->size() + 1);
newArgs.alloc("inNixShell").mkBool(true);
for (auto & i : *autoArgs) newArgs.insert(i);
autoArgsWithInNixShell = newArgs.finish();
@@ -236,11 +237,11 @@ static void main_nix_build(std::string programName, Strings argv)
std::vector<std::reference_wrapper<Expr>> exprs;
if (readStdin)
exprs = {state->parseStdin()};
exprs = {evaluator->parseStdin()};
else
for (auto i : left) {
if (fromArgs)
exprs.push_back(state->parseExprFromString(std::move(i), CanonPath::fromCwd()));
exprs.push_back(evaluator->parseExprFromString(std::move(i), CanonPath::fromCwd()));
else {
auto absolute = i;
try {
@@ -248,12 +249,16 @@ static void main_nix_build(std::string programName, Strings argv)
} catch (Error & e) {};
auto [path, outputNames] = parsePathWithOutputs(absolute);
if (evalStore->isStorePath(path) && path.ends_with(".drv"))
drvs.push_back(DrvInfo(*state, evalStore, absolute));
drvs.push_back(DrvInfo(evalStore, absolute));
else
/* If we're in a #! script, interpret filenames
relative to the script. */
exprs.push_back(state->parseExprFromFile(resolveExprPath(state->checkSourcePath(lookupFileArg(*state,
inShebang && !packages ? absPath(i, absPath(dirOf(script))) : i)))));
exprs.push_back(evaluator->parseExprFromFile(
evaluator->paths.resolveExprPath(lookupFileArg(
*evaluator,
inShebang && !packages ? absPath(i, absPath(dirOf(script))) : i
))
));
}
}
@@ -272,7 +277,7 @@ static void main_nix_build(std::string programName, Strings argv)
bool add = false;
if (v.type() == nFunction && v.lambda.fun->hasFormals()) {
for (auto & i : v.lambda.fun->formals->formals) {
if (state->symbols[i.name] == "inNixShell") {
if (evaluator->symbols[i.name] == "inNixShell") {
add = true;
break;
}
@@ -300,7 +305,7 @@ static void main_nix_build(std::string programName, Strings argv)
}
}
state->maybePrintStats();
evaluator->maybePrintStats();
auto buildPaths = [&](const std::vector<DerivedPath> & paths) {
/* Note: we do this even when !printMissing to efficiently
@@ -322,7 +327,7 @@ static void main_nix_build(std::string programName, Strings argv)
throw UsageError("nix-shell requires a single derivation");
auto & drvInfo = drvs.front();
auto drv = evalStore->derivationFromPath(drvInfo.requireDrvPath());
auto drv = evalStore->derivationFromPath(drvInfo.requireDrvPath(*state));
std::vector<DerivedPath> pathsToBuild;
RealisedPath::Set pathsToCopy;
@@ -336,7 +341,7 @@ static void main_nix_build(std::string programName, Strings argv)
if (!shell) {
try {
auto & expr = state->parseExprFromString(
auto & expr = evaluator->parseExprFromString(
"(import <nixpkgs> {}).bashInteractive",
CanonPath::fromCwd());
@@ -347,7 +352,7 @@ static void main_nix_build(std::string programName, Strings argv)
if (!drv)
throw Error("the 'bashInteractive' attribute in <nixpkgs> did not evaluate to a derivation");
auto bashDrv = drv->requireDrvPath();
auto bashDrv = drv->requireDrvPath(*state);
pathsToBuild.push_back(DerivedPath::Built {
.drvPath = makeConstantStorePathRef(bashDrv),
.outputs = OutputsSpec::Names {"out"},
@@ -463,7 +468,7 @@ static void main_nix_build(std::string programName, Strings argv)
for (const auto & [inputDrv, inputNode] : drv.inputDrvs.map)
accumInputClosure(inputDrv, inputNode);
ParsedDerivation parsedDrv(drvInfo.requireDrvPath(), drv);
ParsedDerivation parsedDrv(drvInfo.requireDrvPath(*state), drv);
if (auto structAttrs = parsedDrv.prepareStructuredAttrs(*store, inputs)) {
auto json = structAttrs.value();
@@ -566,9 +571,9 @@ static void main_nix_build(std::string programName, Strings argv)
std::map<StorePath, std::pair<size_t, StringSet>> drvMap;
for (auto & drvInfo : drvs) {
auto drvPath = drvInfo.requireDrvPath();
auto drvPath = drvInfo.requireDrvPath(*state);
auto outputName = drvInfo.queryOutputName();
auto outputName = drvInfo.queryOutputName(*state);
if (outputName == "")
throw Error("derivation '%s' lacks an 'outputName' attribute", store->printStorePath(drvPath));
+146 -129
View File
@@ -1,3 +1,4 @@
#include "lix/libcmd/cmd-profiles.hh"
#include "lix/libexpr/attr-path.hh"
#include "lix/libcmd/common-eval-args.hh"
#include "lix/libstore/derivations.hh"
@@ -58,7 +59,7 @@ struct Globals
{
InstallSourceInfo instSource;
Path profile;
std::shared_ptr<EvalState> state;
std::shared_ptr<Evaluator> state;
bool dryRun;
bool preserveInstalled;
bool removeAll;
@@ -95,19 +96,24 @@ static bool parseInstallSourceOptions(Globals & globals,
}
static bool isNixExpr(const SourcePath & path, struct InputAccessor::Stat & st)
static bool isNixExpr(EvalPaths & paths, const CheckedSourcePath & path, struct InputAccessor::Stat & st)
{
return
st.type == InputAccessor::tRegular
|| (st.type == InputAccessor::tDirectory && (path + "default.nix").pathExists());
if (st.type == InputAccessor::tRegular) {
return true;
} else if (st.type != InputAccessor::tDirectory) {
return false;
} else {
auto defaultNix = paths.checkSourcePath(path + "default.nix");
return defaultNix.pathExists();
}
}
static constexpr size_t maxAttrs = 1024;
static void getAllExprs(EvalState & state,
const SourcePath & path, StringSet & seen, BindingsBuilder & attrs)
static void getAllExprs(Evaluator & state,
const CheckedSourcePath & path, StringSet & seen, BindingsBuilder & attrs)
{
StringSet namesSorted;
for (auto & [name, _] : path.readDirectory()) namesSorted.insert(name);
@@ -118,16 +124,16 @@ static void getAllExprs(EvalState & state,
are implemented using profiles). */
if (i == "manifest.nix") continue;
SourcePath path2 = path + i;
auto path2 = state.paths.checkSourcePath(path + i);
InputAccessor::Stat st;
try {
st = path2.resolveSymlinks().lstat();
st = path2.stat();
} catch (Error &) {
continue; // ignore dangling symlinks in ~/.nix-defexpr
}
if (isNixExpr(path2, st) && (st.type != InputAccessor::tRegular || path2.baseName().ends_with(".nix"))) {
if (isNixExpr(state.paths, path2, st) && (st.type != InputAccessor::tRegular || path2.baseName().ends_with(".nix"))) {
/* Strip off the `.nix' filename suffix (if applicable),
otherwise the attribute cannot be selected with the
`-A' option. Useful if you want to stick a Nix
@@ -137,7 +143,7 @@ static void getAllExprs(EvalState & state,
attrName = std::string(attrName, 0, attrName.size() - 4);
if (!seen.insert(attrName).second) {
std::string suggestionMessage = "";
if (path2.path.abs().find("channels") != std::string::npos && path.path.abs().find("channels") != std::string::npos)
if (path2.to_string().find("channels") != std::string::npos && path.to_string().find("channels") != std::string::npos)
suggestionMessage = fmt("\nsuggestion: remove '%s' from either the root channels or the user channels", attrName);
printError("warning: name collision in input Nix expressions, skipping '%1%'"
"%2%", path2, suggestionMessage);
@@ -145,7 +151,7 @@ static void getAllExprs(EvalState & state,
}
/* Load the expression on demand. */
auto vArg = state.mem.allocValue();
vArg->mkString(path2.path.abs());
vArg->mkString(path2.canonical().abs());
if (seen.size() == maxAttrs)
throw Error("too many Nix expressions in directory '%1%'", path);
attrs.alloc(attrName).mkApp(&state.builtins.get("import"), vArg);
@@ -159,11 +165,12 @@ static void getAllExprs(EvalState & state,
static void loadSourceExpr(EvalState & state, const SourcePath & path, Value & v)
static void loadSourceExpr(EvalState & state, const SourcePath & path_, Value & v)
{
auto st = path.resolveSymlinks().lstat();
auto path = state.ctx.paths.checkSourcePath(path_);
auto st = path.stat();
if (isNixExpr(path, st))
if (isNixExpr(state.ctx.paths, path, st))
state.evalFile(path, v);
/* The path is a directory. Put the Nix expressions in the
@@ -173,10 +180,10 @@ static void loadSourceExpr(EvalState & state, const SourcePath & path, Value & v
~/.nix-defexpr directory that includes some system-wide
directory). */
else if (st.type == InputAccessor::tDirectory) {
auto attrs = state.buildBindings(maxAttrs);
auto attrs = state.ctx.buildBindings(maxAttrs);
attrs.alloc("_combineChannels").mkList(0);
StringSet seen;
getAllExprs(state, path, seen, attrs);
getAllExprs(state.ctx, path, seen, attrs);
v.mkAttrs(attrs);
}
@@ -199,7 +206,7 @@ static void loadDerivations(EvalState & state, const SourcePath & nixExprPath,
system. */
for (DrvInfos::iterator i = elems.begin(), j; i != elems.end(); i = j) {
j = i; j++;
if (systemFilter != "*" && i->querySystem() != systemFilter)
if (systemFilter != "*" && i->querySystem(state) != systemFilter)
elems.erase(i);
}
}
@@ -207,7 +214,7 @@ static void loadDerivations(EvalState & state, const SourcePath & nixExprPath,
static NixInt getPriority(EvalState & state, DrvInfo & drv)
{
return drv.queryMetaInt("priority", NixInt(0));
return drv.queryMetaInt(state, "priority", NixInt(0));
}
@@ -221,9 +228,9 @@ static std::strong_ordering comparePriorities(EvalState & state, DrvInfo & drv1,
// at a time.
static bool isPrebuilt(EvalState & state, DrvInfo & elem)
{
auto path = elem.queryOutPath();
if (state.store->isValidPath(path)) return true;
return state.store->querySubstitutablePaths({path}).count(path);
auto path = elem.queryOutPath(state);
if (state.ctx.store->isValidPath(path)) return true;
return state.ctx.store->querySubstitutablePaths({path}).count(path);
}
@@ -238,11 +245,11 @@ static void checkSelectorUse(DrvNames & selectors)
namespace {
std::set<std::string> searchByPrefix(DrvInfos & allElems, std::string_view prefix) {
std::set<std::string> searchByPrefix(EvalState & state, DrvInfos & allElems, std::string_view prefix) {
constexpr std::size_t maxResults = 3;
std::set<std::string> result;
for (auto & drvInfo : allElems) {
const auto drvName = DrvName { drvInfo.queryName() };
const auto drvName = DrvName { drvInfo.queryName(state) };
if (drvName.name.starts_with(prefix)) {
result.emplace(drvName.name);
@@ -280,7 +287,7 @@ std::vector<Match> pickNewestOnly(EvalState & state, std::vector<Match> matches)
for (auto & match : matches) {
auto & oneDrv = match.drvInfo;
const auto drvName = DrvName { oneDrv.queryName() };
const auto drvName = DrvName { oneDrv.queryName(state) };
std::strong_ordering comparison = std::strong_ordering::greater;
const auto itOther = newest.find(drvName.name);
@@ -289,13 +296,13 @@ std::vector<Match> pickNewestOnly(EvalState & state, std::vector<Match> matches)
auto & newestDrv = itOther->second.drvInfo;
comparison =
oneDrv.querySystem() == newestDrv.querySystem() ? std::strong_ordering::equal :
oneDrv.querySystem() == settings.thisSystem ? std::strong_ordering::greater :
newestDrv.querySystem() == settings.thisSystem ? std::strong_ordering::less : std::strong_ordering::equal;
oneDrv.querySystem(state) == newestDrv.querySystem(state) ? std::strong_ordering::equal :
oneDrv.querySystem(state) == settings.thisSystem ? std::strong_ordering::greater :
newestDrv.querySystem(state) == settings.thisSystem ? std::strong_ordering::less : std::strong_ordering::equal;
if (comparison == 0)
comparison = comparePriorities(state, oneDrv, newestDrv);
if (comparison == 0)
comparison = compareVersions(drvName.version, DrvName { newestDrv.queryName() }.version);
comparison = compareVersions(drvName.version, DrvName{newestDrv.queryName(state)}.version);
}
if (comparison > 0) {
@@ -334,7 +341,7 @@ static DrvInfos filterBySelector(EvalState & state, DrvInfos & allElems,
for (auto & selector : selectors) {
std::vector<Match> matches;
for (auto && [index, drvInfo] : enumerate(allElems)) {
const auto drvName = DrvName { drvInfo.queryName() };
const auto drvName = DrvName { drvInfo.queryName(state) };
if (selector.matches(drvName)) {
++selector.hits;
matches.emplace_back(drvInfo, index);
@@ -352,7 +359,7 @@ static DrvInfos filterBySelector(EvalState & state, DrvInfos & allElems,
elems.push_back(match.drvInfo);
if (selector.hits == 0 && selector.fullName != "*") {
const auto prefixHits = searchByPrefix(allElems, selector.name);
const auto prefixHits = searchByPrefix(state, allElems, selector.name);
if (prefixHits.empty()) {
throw Error("selector '%1%' matches no derivations", selector.fullName);
@@ -415,7 +422,7 @@ static void queryInstSources(EvalState & state,
loadSourceExpr(state, *instSource.nixExprPath, vArg);
for (auto & i : args) {
Expr & eFun = state.parseExprFromString(i, CanonPath::fromCwd());
Expr & eFun = state.ctx.parseExprFromString(i, CanonPath::fromCwd());
Value vFun, vTmp;
state.eval(eFun, vFun);
vTmp.mkApp(&vFun, &vArg);
@@ -431,16 +438,16 @@ static void queryInstSources(EvalState & state,
case srcStorePaths: {
for (auto & i : args) {
auto path = state.store->followLinksToStorePath(i);
auto path = state.ctx.store->followLinksToStorePath(i);
std::string name(path.name());
DrvInfo elem(state, "", nullptr);
DrvInfo elem("", nullptr);
elem.setName(name);
if (path.isDerivation()) {
elem.setDrvPath(path);
auto outputs = state.store->queryDerivationOutputMap(path);
auto outputs = state.ctx.store->queryDerivationOutputMap(path);
elem.setOutPath(outputs.at("out"));
if (name.size() >= drvExtension.size() &&
std::string(name, name.size() - drvExtension.size()) == drvExtension)
@@ -485,23 +492,23 @@ static void printMissing(EvalState & state, DrvInfos & elems)
{
std::vector<DerivedPath> targets;
for (auto & i : elems)
if (auto drvPath = i.queryDrvPath())
if (auto drvPath = i.queryDrvPath(state))
targets.emplace_back(DerivedPath::Built{
.drvPath = makeConstantStorePathRef(*drvPath),
.outputs = OutputsSpec::All { },
});
else
targets.emplace_back(DerivedPath::Opaque{
.path = i.queryOutPath(),
.path = i.queryOutPath(state),
});
printMissing(state.store, targets);
printMissing(state.ctx.store, targets);
}
static bool keep(DrvInfo & drv)
static bool keep(EvalState & state, DrvInfo & drv)
{
return drv.queryMetaBool("keep", false);
return drv.queryMetaBool(state, "keep", false);
}
@@ -510,13 +517,15 @@ static void installDerivations(Globals & globals,
{
debug("installing derivations");
auto state = globals.state->begin();
/* Get the set of user environment elements to be installed. */
DrvInfos newElems, newElemsTmp;
queryInstSources(*globals.state, globals.instSource, args, newElemsTmp, true);
queryInstSources(*state, globals.instSource, args, newElemsTmp, true);
/* If --prebuilt-only is given, filter out source-only packages. */
for (auto & i : newElemsTmp)
if (!globals.prebuiltOnly || isPrebuilt(*globals.state, i))
if (!globals.prebuiltOnly || isPrebuilt(*state, i))
newElems.push_back(i);
StringSet newNames;
@@ -527,7 +536,7 @@ static void installDerivations(Globals & globals,
`java-front-0.9pre15899'). */
if (globals.forceName != "")
i.setName(globals.forceName);
newNames.insert(DrvName(i.queryName()).name);
newNames.insert(DrvName(i.queryName(*state)).name);
}
@@ -539,27 +548,27 @@ static void installDerivations(Globals & globals,
/* Add in the already installed derivations, unless they have
the same name as a to-be-installed element. */
if (!globals.removeAll) {
DrvInfos installedElems = queryInstalled(*globals.state, profile);
DrvInfos installedElems = queryInstalled(*state, profile);
for (auto & i : installedElems) {
DrvName drvName(i.queryName());
DrvName drvName(i.queryName(*state));
if (!globals.preserveInstalled &&
newNames.find(drvName.name) != newNames.end() &&
!keep(i))
printInfo("replacing old '%s'", i.queryName());
!keep(*state, i))
printInfo("replacing old '%s'", i.queryName(*state));
else
allElems.push_back(i);
}
for (auto & i : newElems)
printInfo("installing '%s'", i.queryName());
printInfo("installing '%s'", i.queryName(*state));
}
printMissing(*globals.state, newElems);
printMissing(*state, newElems);
if (globals.dryRun) return;
if (createUserEnv(*globals.state, allElems,
if (createUserEnv(*state, allElems,
profile, settings.envKeepDerivations, lockToken)) break;
}
}
@@ -589,6 +598,8 @@ static void upgradeDerivations(Globals & globals,
{
debug("upgrading derivations");
auto state = globals.state->begin();
/* Upgrade works as follows: we take all currently installed
derivations, and for any derivation matching any selector, look
for a derivation in the input Nix expression that has the same
@@ -597,20 +608,20 @@ static void upgradeDerivations(Globals & globals,
while (true) {
auto lockToken = optimisticLockProfile(globals.profile);
DrvInfos installedElems = queryInstalled(*globals.state, globals.profile);
DrvInfos installedElems = queryInstalled(*state, globals.profile);
/* Fetch all derivations from the input file. */
DrvInfos availElems;
queryInstSources(*globals.state, globals.instSource, args, availElems, false);
queryInstSources(*state, globals.instSource, args, availElems, false);
/* Go through all installed derivations. */
DrvInfos newElems;
for (auto & i : installedElems) {
DrvName drvName(i.queryName());
DrvName drvName(i.queryName(*state));
try {
if (keep(i)) {
if (keep(*state, i)) {
newElems.push_back(i);
continue;
}
@@ -625,9 +636,9 @@ static void upgradeDerivations(Globals & globals,
DrvInfos::iterator bestElem = availElems.end();
std::string bestVersion;
for (auto j = availElems.begin(); j != availElems.end(); ++j) {
if (comparePriorities(*globals.state, i, *j) > 0)
if (comparePriorities(*state, i, *j) > 0)
continue;
DrvName newName(j->queryName());
DrvName newName(j->queryName(*state));
if (newName.name == drvName.name) {
std::strong_ordering d = compareVersions(drvName.version, newName.version);
if ((upgradeType == utLt && d < 0) ||
@@ -637,10 +648,10 @@ static void upgradeDerivations(Globals & globals,
{
std::strong_ordering d2 = std::strong_ordering::less;
if (bestElem != availElems.end()) {
d2 = comparePriorities(*globals.state, *bestElem, *j);
d2 = comparePriorities(*state, *bestElem, *j);
if (d2 == 0) d2 = compareVersions(bestVersion, newName.version);
}
if (d2 < 0 && (!globals.prebuiltOnly || isPrebuilt(*globals.state, *j))) {
if (d2 < 0 && (!globals.prebuiltOnly || isPrebuilt(*state, *j))) {
bestElem = j;
bestVersion = newName.version;
}
@@ -649,27 +660,27 @@ static void upgradeDerivations(Globals & globals,
}
if (bestElem != availElems.end() &&
i.queryOutPath() !=
bestElem->queryOutPath())
i.queryOutPath(*state) !=
bestElem->queryOutPath(*state))
{
const char * action = compareVersions(drvName.version, bestVersion) <= 0
? "upgrading" : "downgrading";
printInfo("%1% '%2%' to '%3%'",
action, i.queryName(), bestElem->queryName());
action, i.queryName(*state), bestElem->queryName(*state));
newElems.push_back(*bestElem);
} else newElems.push_back(i);
} catch (Error & e) {
e.addTrace(nullptr, "while trying to find an upgrade for '%s'", i.queryName());
e.addTrace(nullptr, "while trying to find an upgrade for '%s'", i.queryName(*state));
throw;
}
}
printMissing(*globals.state, newElems);
printMissing(*state, newElems);
if (globals.dryRun) return;
if (createUserEnv(*globals.state, newElems,
if (createUserEnv(*state, newElems,
globals.profile, settings.envKeepDerivations, lockToken)) break;
}
}
@@ -695,9 +706,9 @@ static void opUpgrade(Globals & globals, Strings opFlags, Strings opArgs)
static void setMetaFlag(EvalState & state, DrvInfo & drv,
const std::string & name, const std::string & value)
{
auto v = state.mem.allocValue();
auto v = state.ctx.mem.allocValue();
v->mkString(value);
drv.setMeta(name, v);
drv.setMeta(state, name, v);
}
@@ -713,19 +724,21 @@ static void opSetFlag(Globals & globals, Strings opFlags, Strings opArgs)
std::string flagValue = *arg++;
DrvNames selectors = drvNamesFromArgs(Strings(arg, opArgs.end()));
auto state = globals.state->begin();
while (true) {
std::string lockToken = optimisticLockProfile(globals.profile);
DrvInfos installedElems = queryInstalled(*globals.state, globals.profile);
DrvInfos installedElems = queryInstalled(*state, globals.profile);
/* Update all matching derivations. */
for (auto & i : installedElems) {
DrvName drvName(i.queryName());
DrvName drvName(i.queryName(*state));
for (auto & j : selectors)
if (j.matches(drvName)) {
printInfo("setting flag on '%1%'", i.queryName());
printInfo("setting flag on '%1%'", i.queryName(*state));
j.hits++;
setMetaFlag(*globals.state, i, flagName, flagValue);
setMetaFlag(*state, i, flagName, flagValue);
break;
}
}
@@ -733,7 +746,7 @@ static void opSetFlag(Globals & globals, Strings opFlags, Strings opArgs)
checkSelectorUse(selectors);
/* Write the new user environment. */
if (createUserEnv(*globals.state, installedElems,
if (createUserEnv(*state, installedElems,
globals.profile, settings.envKeepDerivations, lockToken)) break;
}
}
@@ -741,6 +754,8 @@ static void opSetFlag(Globals & globals, Strings opFlags, Strings opArgs)
static void opSet(Globals & globals, Strings opFlags, Strings opArgs)
{
auto state = globals.state->begin();
auto store2 = globals.state->store.dynamic_pointer_cast<LocalFSStore>();
if (!store2) throw Error("--set is not supported for this Nix store");
@@ -751,7 +766,7 @@ static void opSet(Globals & globals, Strings opFlags, Strings opArgs)
}
DrvInfos elems;
queryInstSources(*globals.state, globals.instSource, opArgs, elems, true);
queryInstSources(*state, globals.instSource, opArgs, elems, true);
if (elems.size() != 1)
throw Error("--set requires exactly one derivation");
@@ -761,7 +776,7 @@ static void opSet(Globals & globals, Strings opFlags, Strings opArgs)
if (globals.forceName != "")
drv.setName(globals.forceName);
auto drvPath = drv.queryDrvPath();
auto drvPath = drv.queryDrvPath(*state);
std::vector<DerivedPath> paths {
drvPath
? (DerivedPath) (DerivedPath::Built {
@@ -769,7 +784,7 @@ static void opSet(Globals & globals, Strings opFlags, Strings opArgs)
.outputs = OutputsSpec::All { },
})
: (DerivedPath) (DerivedPath::Opaque {
.path = drv.queryOutPath(),
.path = drv.queryOutPath(*state),
}),
};
printMissing(globals.state->store, paths);
@@ -780,7 +795,7 @@ static void opSet(Globals & globals, Strings opFlags, Strings opArgs)
Path generation = createGeneration(
*store2,
globals.profile,
drv.queryOutPath());
drv.queryOutPath(*state));
switchLink(globals.profile, generation);
}
@@ -788,10 +803,12 @@ static void opSet(Globals & globals, Strings opFlags, Strings opArgs)
static void uninstallDerivations(Globals & globals, Strings & selectors,
Path & profile)
{
auto state = globals.state->begin();
while (true) {
auto lockToken = optimisticLockProfile(profile);
DrvInfos workingElems = queryInstalled(*globals.state, profile);
DrvInfos workingElems = queryInstalled(*state, profile);
for (auto & selector : selectors) {
DrvInfos::iterator split = workingElems.begin();
@@ -799,16 +816,16 @@ static void uninstallDerivations(Globals & globals, Strings & selectors,
StorePath selectorStorePath = globals.state->store->followLinksToStorePath(selector);
split = std::partition(
workingElems.begin(), workingElems.end(),
[&selectorStorePath, globals](auto &elem) {
return selectorStorePath != elem.queryOutPath();
[&selectorStorePath, &state](auto &elem) {
return selectorStorePath != elem.queryOutPath(*state);
}
);
} else {
DrvName selectorName(selector);
split = std::partition(
workingElems.begin(), workingElems.end(),
[&selectorName](auto &elem){
DrvName elemName(elem.queryName());
[&selectorName, &state](auto &elem){
DrvName elemName(elem.queryName(*state));
return !selectorName.matches(elemName);
}
);
@@ -816,14 +833,14 @@ static void uninstallDerivations(Globals & globals, Strings & selectors,
if (split == workingElems.end())
warn("selector '%s' matched no installed derivations", selector);
for (auto removedElem = split; removedElem != workingElems.end(); removedElem++) {
printInfo("uninstalling '%s'", removedElem->queryName());
printInfo("uninstalling '%s'", removedElem->queryName(*state));
}
workingElems.erase(split, workingElems.end());
}
if (globals.dryRun) return;
if (createUserEnv(*globals.state, workingElems,
if (createUserEnv(*state, workingElems,
profile, settings.envKeepDerivations, lockToken)) break;
}
}
@@ -843,10 +860,10 @@ static bool cmpChars(char a, char b)
}
static bool cmpElemByName(DrvInfo & a, DrvInfo & b)
static bool cmpElemByName(EvalState & state, DrvInfo & a, DrvInfo & b)
{
auto a_name = a.queryName();
auto b_name = b.queryName();
auto a_name = a.queryName(state);
auto b_name = b.queryName(state);
return lexicographical_compare(
a_name.begin(), a_name.end(),
b_name.begin(), b_name.end(), cmpChars);
@@ -896,15 +913,15 @@ void printTable(Table & table)
typedef enum { cvLess, cvEqual, cvGreater, cvUnavail } VersionDiff;
static VersionDiff compareVersionAgainstSet(
DrvInfo & elem, DrvInfos & elems, std::string & version)
EvalState & state, DrvInfo & elem, DrvInfos & elems, std::string & version)
{
DrvName name(elem.queryName());
DrvName name(elem.queryName(state));
VersionDiff diff = cvUnavail;
version = "?";
for (auto & i : elems) {
DrvName name2(i.queryName());
DrvName name2(i.queryName(state));
if (name.name == name2.name) {
std::strong_ordering d = compareVersions(name.version, name2.version);
if (d < 0) {
@@ -927,7 +944,7 @@ static VersionDiff compareVersionAgainstSet(
}
static void queryJSON(Globals & globals, std::vector<DrvInfo> & elems, bool printOutPath, bool printDrvPath, bool printMeta)
static void queryJSON(EvalState & state, Globals & globals, std::vector<DrvInfo> & elems, bool printOutPath, bool printDrvPath, bool printMeta)
{
using nlohmann::json;
json topObj = json::object();
@@ -936,18 +953,18 @@ static void queryJSON(Globals & globals, std::vector<DrvInfo> & elems, bool prin
if (i.hasFailed()) continue;
auto drvName = DrvName(i.queryName());
auto drvName = DrvName(i.queryName(state));
json &pkgObj = topObj[i.attrPath];
pkgObj = {
{"name", drvName.fullName},
{"pname", drvName.name},
{"version", drvName.version},
{"system", i.querySystem()},
{"outputName", i.queryOutputName()},
{"system", i.querySystem(state)},
{"outputName", i.queryOutputName(state)},
};
{
DrvInfo::Outputs outputs = i.queryOutputs(printOutPath);
DrvInfo::Outputs outputs = i.queryOutputs(state, printOutPath);
json &outputObj = pkgObj["outputs"];
outputObj = json::object();
for (auto & j : outputs) {
@@ -959,29 +976,29 @@ static void queryJSON(Globals & globals, std::vector<DrvInfo> & elems, bool prin
}
if (printDrvPath) {
auto drvPath = i.queryDrvPath();
auto drvPath = i.queryDrvPath(state);
if (drvPath) pkgObj["drvPath"] = globals.state->store->printStorePath(*drvPath);
}
if (printMeta) {
json &metaObj = pkgObj["meta"];
metaObj = json::object();
StringSet metaNames = i.queryMetaNames();
StringSet metaNames = i.queryMetaNames(state);
for (auto & j : metaNames) {
Value * v = i.queryMeta(j);
Value * v = i.queryMeta(state, j);
if (!v) {
printError("derivation '%s' has invalid meta attribute '%s'", i.queryName(), j);
printError("derivation '%s' has invalid meta attribute '%s'", i.queryName(state), j);
metaObj[j] = nullptr;
} else {
NixStringContext context;
metaObj[j] = printValueAsJSON(*globals.state, true, *v, noPos, context);
metaObj[j] = printValueAsJSON(state, true, *v, noPos, context);
}
}
}
} catch (AssertionError & e) {
printMsg(lvlTalkative, "skipping derivation named '%1%' which gives an assertion failure", i.queryName());
printMsg(lvlTalkative, "skipping derivation named '%1%' which gives an assertion failure", i.queryName(state));
} catch (Error & e) {
e.addTrace(nullptr, "while querying the derivation named '%1%'", i.queryName());
e.addTrace(nullptr, "while querying the derivation named '%1%'", i.queryName(state));
throw;
}
}
@@ -992,6 +1009,7 @@ static void queryJSON(Globals & globals, std::vector<DrvInfo> & elems, bool prin
static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
{
auto & store { *globals.state->store };
auto state = globals.state->begin();
Strings remaining;
std::string attrPath;
@@ -1040,14 +1058,14 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
DrvInfos availElems, installedElems;
if (source == sInstalled || compareVersions || printStatus)
installedElems = queryInstalled(*globals.state, globals.profile);
installedElems = queryInstalled(*state, globals.profile);
if (source == sAvailable || compareVersions)
loadDerivations(*globals.state, *globals.instSource.nixExprPath,
loadDerivations(*state, *globals.instSource.nixExprPath,
globals.instSource.systemFilter, *globals.instSource.autoArgs,
attrPath, availElems);
DrvInfos elems_ = filterBySelector(*globals.state,
DrvInfos elems_ = filterBySelector(*state,
source == sInstalled ? installedElems : availElems,
opArgs, false);
@@ -1058,8 +1076,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
/* !!! */
std::vector<DrvInfo> elems;
for (auto & i : elems_) elems.push_back(i);
sort(elems.begin(), elems.end(), cmpElemByName);
sort(elems.begin(), elems.end(), [&] (auto& a, auto& b) { return cmpElemByName(*state, a, b); });
/* We only need to know the installed paths when we are querying
the status of the derivation. */
@@ -1067,7 +1084,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
if (printStatus)
for (auto & i : installedElems)
installed.insert(i.queryOutPath());
installed.insert(i.queryOutPath(*state));
/* Query which paths have substitutes. */
@@ -1077,9 +1094,9 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
StorePathSet paths;
for (auto & i : elems)
try {
paths.insert(i.queryOutPath());
paths.insert(i.queryOutPath(*state));
} catch (AssertionError & e) {
printMsg(lvlTalkative, "skipping derivation named '%s' which gives an assertion failure", i.queryName());
printMsg(lvlTalkative, "skipping derivation named '%s' which gives an assertion failure", i.queryName(*state));
i.setFailed();
}
validPaths = store.queryValidPaths(paths);
@@ -1089,7 +1106,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
/* Print the desired columns, or XML output. */
if (jsonOutput) {
queryJSON(globals, elems, printOutPath, printDrvPath, printMeta);
queryJSON(*state, globals, elems, printOutPath, printDrvPath, printMeta);
cout << '\n';
return;
}
@@ -1108,8 +1125,8 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
//Activity act(*logger, lvlDebug, "outputting query result '%1%'", i.attrPath);
if (globals.prebuiltOnly &&
!validPaths.count(i.queryOutPath()) &&
!substitutablePaths.count(i.queryOutPath()))
!validPaths.count(i.queryOutPath(*state)) &&
!substitutablePaths.count(i.queryOutPath(*state)))
continue;
/* For table output. */
@@ -1119,7 +1136,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
XMLAttrs attrs;
if (printStatus) {
auto outPath = i.queryOutPath();
auto outPath = i.queryOutPath(*state);
bool hasSubs = substitutablePaths.count(outPath);
bool isInstalled = installed.count(outPath);
bool isValid = validPaths.count(outPath);
@@ -1140,12 +1157,12 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
columns.push_back(i.attrPath);
if (xmlOutput) {
auto drvName = DrvName(i.queryName());
auto drvName = DrvName(i.queryName(*state));
attrs["name"] = drvName.fullName;
attrs["pname"] = drvName.name;
attrs["version"] = drvName.version;
} else if (printName) {
columns.push_back(i.queryName());
columns.push_back(i.queryName(*state));
}
if (compareVersions) {
@@ -1154,7 +1171,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
elements, or the set of installed elements. !!!
This is O(N * M), should be O(N * lg M). */
std::string version;
VersionDiff diff = compareVersionAgainstSet(i, otherElems, version);
VersionDiff diff = compareVersionAgainstSet(*state, i, otherElems, version);
char ch;
switch (diff) {
@@ -1179,13 +1196,13 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
}
if (xmlOutput) {
if (i.querySystem() != "") attrs["system"] = i.querySystem();
if (i.querySystem(*state) != "") attrs["system"] = i.querySystem(*state);
}
else if (printSystem)
columns.push_back(i.querySystem());
columns.push_back(i.querySystem(*state));
if (printDrvPath) {
auto drvPath = i.queryDrvPath();
auto drvPath = i.queryDrvPath(*state);
if (xmlOutput) {
if (drvPath) attrs["drvPath"] = store.printStorePath(*drvPath);
} else
@@ -1193,10 +1210,10 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
}
if (xmlOutput)
attrs["outputName"] = i.queryOutputName();
attrs["outputName"] = i.queryOutputName(*state);
if (printOutPath && !xmlOutput) {
DrvInfo::Outputs outputs = i.queryOutputs();
DrvInfo::Outputs outputs = i.queryOutputs(*state);
std::string s;
for (auto & j : outputs) {
if (!s.empty()) s += ';';
@@ -1207,7 +1224,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
}
if (printDescription) {
auto descr = i.queryMetaString("description");
auto descr = i.queryMetaString(*state, "description");
if (xmlOutput) {
if (descr != "") attrs["description"] = descr;
} else
@@ -1216,7 +1233,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
if (xmlOutput) {
XMLOpenElement item(xml, "item", attrs);
DrvInfo::Outputs outputs = i.queryOutputs(printOutPath);
DrvInfo::Outputs outputs = i.queryOutputs(*state, printOutPath);
for (auto & j : outputs) {
XMLAttrs attrs2;
attrs2["name"] = j.first;
@@ -1225,15 +1242,15 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
xml.writeEmptyElement("output", attrs2);
}
if (printMeta) {
StringSet metaNames = i.queryMetaNames();
StringSet metaNames = i.queryMetaNames(*state);
for (auto & j : metaNames) {
XMLAttrs attrs2;
attrs2["name"] = j;
Value * v = i.queryMeta(j);
Value * v = i.queryMeta(*state, j);
if (!v)
printError(
"derivation '%s' has invalid meta attribute '%s'",
i.queryName(), j);
i.queryName(*state), j);
else {
if (v->type() == nString) {
attrs2["type"] = "string";
@@ -1282,9 +1299,9 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
cout.flush();
} catch (AssertionError & e) {
printMsg(lvlTalkative, "skipping derivation named '%1%' which gives an assertion failure", i.queryName());
printMsg(lvlTalkative, "skipping derivation named '%1%' which gives an assertion failure", i.queryName(*state));
} catch (Error & e) {
e.addTrace(nullptr, "while querying the derivation named '%1%'", i.queryName());
e.addTrace(nullptr, "while querying the derivation named '%1%'", i.queryName(*state));
throw;
}
}
@@ -1315,7 +1332,7 @@ static void opSwitchGeneration(Globals & globals, Strings opFlags, Strings opArg
throw UsageError("exactly one argument expected");
if (auto dstGen = string2Int<GenerationNumber>(opArgs.front()))
switchGeneration(globals.profile, *dstGen, globals.dryRun);
switchGeneration(globals.profile, dstGen, globals.dryRun);
else
throw UsageError("expected a generation number");
}
@@ -1522,7 +1539,7 @@ static int main_nix_env(std::string programName, Strings argv)
auto store = openStore();
globals.state = std::shared_ptr<EvalState>(new EvalState(myArgs.searchPath, store));
globals.state = std::make_shared<Evaluator>(myArgs.searchPath, store);
globals.state->repair = myArgs.repair;
globals.instSource.nixExprPath = std::make_shared<SourcePath>(
+16 -18
View File
@@ -31,7 +31,7 @@ void processExpr(EvalState & state, const Strings & attrPaths,
bool evalOnly, OutputKind output, bool location, Expr & e)
{
if (parseOnly) {
e.show(state.symbols, std::cout);
e.show(state.ctx.symbols, std::cout);
std::cout << "\n";
return;
}
@@ -58,18 +58,18 @@ void processExpr(EvalState & state, const Strings & attrPaths,
} else {
if (strict) state.forceValueDeep(vRes);
std::set<const void *> seen;
printAmbiguous(vRes, state.symbols, std::cout, &seen, std::numeric_limits<int>::max());
printAmbiguous(vRes, state.ctx.symbols, std::cout, &seen, std::numeric_limits<int>::max());
std::cout << std::endl;
}
} else {
DrvInfos drvs;
getDerivations(state, v, "", autoArgs, drvs, false);
for (auto & i : drvs) {
auto drvPath = i.requireDrvPath();
auto drvPathS = state.store->printStorePath(drvPath);
auto drvPath = i.requireDrvPath(state);
auto drvPathS = state.ctx.store->printStorePath(drvPath);
/* What output do we want? */
std::string outputName = i.queryOutputName();
std::string outputName = i.queryOutputName(state);
if (outputName == "")
throw Error("derivation '%1%' lacks an 'outputName' attribute", drvPathS);
@@ -78,7 +78,7 @@ void processExpr(EvalState & state, const Strings & attrPaths,
else {
Path rootName = absPath(gcRoot);
if (++rootNr > 1) rootName += "-" + std::to_string(rootNr);
auto store2 = state.store.dynamic_pointer_cast<LocalFSStore>();
auto store2 = state.ctx.store.dynamic_pointer_cast<LocalFSStore>();
if (store2)
drvPathS = store2->addPermRoot(drvPath, rootName);
}
@@ -157,26 +157,24 @@ static int main_nix_instantiate(std::string programName, Strings argv)
auto store = openStore();
auto evalStore = myArgs.evalStoreUrl ? openStore(*myArgs.evalStoreUrl) : store;
auto state = std::make_unique<EvalState>(myArgs.searchPath, evalStore, store);
state->repair = myArgs.repair;
auto evaluator = std::make_unique<Evaluator>(myArgs.searchPath, evalStore, store);
auto state = evaluator->begin();
evaluator->repair = myArgs.repair;
Bindings & autoArgs = *myArgs.getAutoArgs(*state);
Bindings & autoArgs = *myArgs.getAutoArgs(*evaluator);
if (attrPaths.empty()) attrPaths = {""};
if (findFile) {
for (auto & i : files) {
auto p = state->findFile(i);
if (auto fn = p.getPhysicalPath())
std::cout << fn->abs() << std::endl;
else
throw Error("'%s' has no physical path", p);
auto p = evaluator->paths.findFile(i);
std::cout << p.canonical().abs() << std::endl;
}
return 0;
}
if (readStdin) {
Expr & e = state->parseStdin();
Expr & e = evaluator->parseStdin();
processExpr(*state, attrPaths, parseOnly, strict, autoArgs,
evalOnly, outputKind, xmlOutputSourceLocation, e);
} else if (files.empty() && !fromArgs)
@@ -184,13 +182,13 @@ static int main_nix_instantiate(std::string programName, Strings argv)
for (auto & i : files) {
Expr & e = fromArgs
? state->parseExprFromString(i, CanonPath::fromCwd())
: state->parseExprFromFile(resolveExprPath(state->checkSourcePath(lookupFileArg(*state, i))));
? evaluator->parseExprFromString(i, CanonPath::fromCwd())
: evaluator->parseExprFromFile(evaluator->paths.resolveExprPath(lookupFileArg(*evaluator, i)));
processExpr(*state, attrPaths, parseOnly, strict, autoArgs,
evalOnly, outputKind, xmlOutputSourceLocation, e);
}
state->maybePrintStats();
evaluator->maybePrintStats();
return 0;
}
+37 -37
View File
@@ -24,65 +24,65 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
exist already. */
std::vector<StorePathWithOutputs> drvsToBuild;
for (auto & i : elems)
if (auto drvPath = i.queryDrvPath())
if (auto drvPath = i.queryDrvPath(state))
drvsToBuild.push_back({*drvPath});
debug("building user environment dependencies");
state.store->buildPaths(
state.ctx.store->buildPaths(
toDerivedPaths(drvsToBuild),
state.repair ? bmRepair : bmNormal);
state.ctx.repair ? bmRepair : bmNormal);
/* Construct the whole top level derivation. */
StorePathSet references;
Value manifest = state.mem.newList(elems.size());
Value manifest = state.ctx.mem.newList(elems.size());
size_t n = 0;
for (auto & i : elems) {
/* Create a pseudo-derivation containing the name, system,
output paths, and optionally the derivation path, as well
as the meta attributes. */
std::optional<StorePath> drvPath = keepDerivations ? i.queryDrvPath() : std::nullopt;
DrvInfo::Outputs outputs = i.queryOutputs(true, true);
StringSet metaNames = i.queryMetaNames();
std::optional<StorePath> drvPath = keepDerivations ? i.queryDrvPath(state) : std::nullopt;
DrvInfo::Outputs outputs = i.queryOutputs(state, true, true);
StringSet metaNames = i.queryMetaNames(state);
auto attrs = state.buildBindings(7 + outputs.size());
auto attrs = state.ctx.buildBindings(7 + outputs.size());
attrs.alloc(state.s.type).mkString("derivation");
attrs.alloc(state.s.name).mkString(i.queryName());
auto system = i.querySystem();
attrs.alloc(state.ctx.s.type).mkString("derivation");
attrs.alloc(state.ctx.s.name).mkString(i.queryName(state));
auto system = i.querySystem(state);
if (!system.empty())
attrs.alloc(state.s.system).mkString(system);
attrs.alloc(state.s.outPath).mkString(state.store->printStorePath(i.queryOutPath()));
attrs.alloc(state.ctx.s.system).mkString(system);
attrs.alloc(state.ctx.s.outPath).mkString(state.ctx.store->printStorePath(i.queryOutPath(state)));
if (drvPath)
attrs.alloc(state.s.drvPath).mkString(state.store->printStorePath(*drvPath));
attrs.alloc(state.ctx.s.drvPath).mkString(state.ctx.store->printStorePath(*drvPath));
// Copy each output meant for installation.
auto & vOutputs = attrs.alloc(state.s.outputs);
vOutputs = state.mem.newList(outputs.size());
auto & vOutputs = attrs.alloc(state.ctx.s.outputs);
vOutputs = state.ctx.mem.newList(outputs.size());
for (const auto & [m, j] : enumerate(outputs)) {
(vOutputs.listElems()[m] = state.mem.allocValue())->mkString(j.first);
auto outputAttrs = state.buildBindings(2);
outputAttrs.alloc(state.s.outPath).mkString(state.store->printStorePath(*j.second));
(vOutputs.listElems()[m] = state.ctx.mem.allocValue())->mkString(j.first);
auto outputAttrs = state.ctx.buildBindings(2);
outputAttrs.alloc(state.ctx.s.outPath).mkString(state.ctx.store->printStorePath(*j.second));
attrs.alloc(j.first).mkAttrs(outputAttrs);
/* This is only necessary when installing store paths, e.g.,
`nix-env -i /nix/store/abcd...-foo'. */
state.store->addTempRoot(*j.second);
state.store->ensurePath(*j.second);
state.ctx.store->addTempRoot(*j.second);
state.ctx.store->ensurePath(*j.second);
references.insert(*j.second);
}
// Copy the meta attributes.
auto meta = state.buildBindings(metaNames.size());
auto meta = state.ctx.buildBindings(metaNames.size());
for (auto & j : metaNames) {
Value * v = i.queryMeta(j);
Value * v = i.queryMeta(state, j);
if (!v) continue;
meta.insert(state.symbols.create(j), v);
meta.insert(state.ctx.symbols.create(j), v);
}
attrs.alloc(state.s.meta).mkAttrs(meta);
attrs.alloc(state.ctx.s.meta).mkAttrs(meta);
(manifest.listElems()[n++] = state.mem.allocValue())->mkAttrs(attrs);
(manifest.listElems()[n++] = state.ctx.mem.allocValue())->mkAttrs(attrs);
if (drvPath) references.insert(*drvPath);
}
@@ -91,21 +91,21 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
the store; we need it for future modifications of the
environment. */
std::ostringstream str;
printAmbiguous(manifest, state.symbols, str, nullptr, std::numeric_limits<int>::max());
auto manifestFile = state.store->addTextToStore("env-manifest.nix",
printAmbiguous(manifest, state.ctx.symbols, str, nullptr, std::numeric_limits<int>::max());
auto manifestFile = state.ctx.store->addTextToStore("env-manifest.nix",
str.str(), references);
/* Get the environment builder expression. */
Value envBuilder;
state.eval(state.parseExprFromString(
state.eval(state.ctx.parseExprFromString(
#include "buildenv.nix.gen.hh"
, CanonPath::root), envBuilder);
/* Construct a Nix expression that calls the user environment
builder with the manifest as argument. */
auto attrs = state.buildBindings(3);
state.mkStorePathString(manifestFile, attrs.alloc("manifest"));
attrs.insert(state.symbols.create("derivations"), &manifest);
auto attrs = state.ctx.buildBindings(3);
state.ctx.paths.mkStorePathString(manifestFile, attrs.alloc("manifest"));
attrs.insert(state.ctx.symbols.create("derivations"), &manifest);
Value args;
args.mkAttrs(attrs);
@@ -116,21 +116,21 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
debug("evaluating user environment builder");
state.forceValue(topLevel, topLevel.determinePos(noPos));
NixStringContext context;
Attr & aDrvPath(*topLevel.attrs->find(state.s.drvPath));
Attr & aDrvPath(*topLevel.attrs->find(state.ctx.s.drvPath));
auto topLevelDrv = state.coerceToStorePath(aDrvPath.pos, *aDrvPath.value, context, "");
Attr & aOutPath(*topLevel.attrs->find(state.s.outPath));
Attr & aOutPath(*topLevel.attrs->find(state.ctx.s.outPath));
auto topLevelOut = state.coerceToStorePath(aOutPath.pos, *aOutPath.value, context, "");
/* Realise the resulting store expression. */
debug("building user environment");
std::vector<StorePathWithOutputs> topLevelDrvs;
topLevelDrvs.push_back({topLevelDrv});
state.store->buildPaths(
state.ctx.store->buildPaths(
toDerivedPaths(topLevelDrvs),
state.repair ? bmRepair : bmNormal);
state.ctx.repair ? bmRepair : bmNormal);
/* Switch the current user environment to the output path. */
auto store2 = state.store.dynamic_pointer_cast<LocalFSStore>();
auto store2 = state.ctx.store.dynamic_pointer_cast<LocalFSStore>();
if (store2) {
PathLocks lock;
-2
View File
@@ -5,8 +5,6 @@
namespace nix {
DrvInfos queryInstalled(EvalState & state, const Path & userEnv);
bool createUserEnv(EvalState & state, DrvInfos & elems,
const Path & profile, bool keepDerivations,
const std::string & lockToken);
+6 -6
View File
@@ -21,7 +21,7 @@ DrvInfos queryInstalled(EvalState & state, const Path & userEnv)
if (pathExists(manifestFile)) {
Value v;
state.evalFile(CanonPath(manifestFile), v);
Bindings & bindings(*state.mem.allocBindings(0));
Bindings & bindings(*state.ctx.mem.allocBindings(0));
getDerivations(state, v, "", bindings, elems, false);
}
return elems;
@@ -136,7 +136,7 @@ ProfileManifest::ProfileManifest(EvalState & state, const Path & profile)
auto & e = elem.value();
ProfileElement element;
for (auto & p : e["storePaths"]) {
element.storePaths.insert(state.store->parseStorePath((std::string) p));
element.storePaths.insert(state.ctx.store->parseStorePath((std::string) p));
}
element.active = e["active"];
if (e.contains("priority")) {
@@ -162,14 +162,14 @@ ProfileManifest::ProfileManifest(EvalState & state, const Path & profile)
}
} else if (pathExists(profile + "/manifest.nix")) {
// FIXME: needed because of pure mode; ugly.
state.allowPath(state.store->followLinksToStore(profile));
state.allowPath(state.store->followLinksToStore(profile + "/manifest.nix"));
state.ctx.paths.allowPath(state.ctx.store->followLinksToStore(profile));
state.ctx.paths.allowPath(state.ctx.store->followLinksToStore(profile + "/manifest.nix"));
auto drvInfos = queryInstalled(state, state.store->followLinksToStore(profile));
auto drvInfos = queryInstalled(state, state.ctx.store->followLinksToStore(profile));
for (auto & drvInfo : drvInfos) {
ProfileElement element;
element.storePaths = {drvInfo.queryOutPath()};
element.storePaths = {drvInfo.queryOutPath(state)};
addElement(std::move(element));
}
}
+6 -9
View File
@@ -102,20 +102,17 @@ ref<Store> EvalCommand::getEvalStore()
return ref<Store>(evalStore);
}
ref<eval_cache::CachingEvalState> EvalCommand::getEvalState()
ref<eval_cache::CachingEvaluator> EvalCommand::getEvaluator()
{
if (!evalState) {
evalState = std::allocate_shared<eval_cache::CachingEvalState>(
TraceableAllocator<EvalState>(), searchPath, getEvalStore(), getStore()
evalState = std::allocate_shared<eval_cache::CachingEvaluator>(
TraceableAllocator<EvalState>(), searchPath, getEvalStore(), getStore(),
startReplOnEvalErrors ? AbstractNixRepl::runSimple : nullptr
);
evalState->repair = repair;
if (startReplOnEvalErrors) {
evalState->debug = std::make_unique<DebugState>(&AbstractNixRepl::runSimple);
};
}
return ref<eval_cache::CachingEvalState>(evalState);
return ref<eval_cache::CachingEvaluator>(evalState);
}
MixOperateOnOptions::MixOperateOnOptions()
@@ -165,7 +162,7 @@ void BuiltPathsCommand::run(ref<Store> store, Installables && installables)
for (auto & p : store->queryAllValidPaths())
paths.emplace_back(BuiltPath::Opaque{p});
} else {
paths = Installable::toBuiltPaths(getEvalStore(), store, realiseMode, operateOn, installables);
paths = Installable::toBuiltPaths(*getEvaluator()->begin(), getEvalStore(), store, realiseMode, operateOn, installables);
if (recursive) {
// XXX: This only computes the store path closure, ignoring
// intermediate realisations
+10 -7
View File
@@ -77,12 +77,12 @@ struct EvalCommand : virtual StoreCommand, MixEvalArgs
ref<Store> getEvalStore();
ref<eval_cache::CachingEvalState> getEvalState();
virtual ref<eval_cache::CachingEvaluator> getEvaluator();
private:
std::shared_ptr<Store> evalStore;
std::shared_ptr<eval_cache::CachingEvalState> evalState;
std::shared_ptr<eval_cache::CachingEvaluator> evalState;
};
/**
@@ -114,11 +114,13 @@ struct SourceExprCommand : virtual Args, MixFlakeOptions
SourceExprCommand();
ref<eval_cache::CachingEvaluator> getEvaluator() override;
Installables parseInstallables(
ref<Store> store, std::vector<std::string> ss);
EvalState & state, ref<Store> store, std::vector<std::string> ss);
ref<Installable> parseInstallable(
ref<Store> store, const std::string & installable);
EvalState & state, ref<Store> store, const std::string & installable);
virtual Strings getDefaultFlakeAttrPaths();
@@ -127,7 +129,7 @@ struct SourceExprCommand : virtual Args, MixFlakeOptions
/**
* Complete an installable from the given prefix.
*/
void completeInstallable(AddCompletions & completions, std::string_view prefix);
void completeInstallable(EvalState & state, AddCompletions & completions, std::string_view prefix);
/**
* Convenience wrapper around the underlying function to make setting the
@@ -324,7 +326,7 @@ struct MixEnvironment : virtual Args {
void completeFlakeInputPath(
AddCompletions & completions,
ref<EvalState> evalState,
EvalState & evalState,
const std::vector<FlakeRef> & flakeRefs,
std::string_view prefix);
@@ -332,7 +334,8 @@ void completeFlakeRef(AddCompletions & completions, ref<Store> store, std::strin
void completeFlakeRefWithFragment(
AddCompletions & completions,
ref<eval_cache::CachingEvalState> evalState,
EvalState & evalState,
ref<eval_cache::CachingEvaluator> evaluator,
flake::LockFlags lockFlags,
Strings attrPathPrefixes,
const Strings & defaultFlakeAttrPaths,
+6 -4
View File
@@ -171,13 +171,15 @@ MixEvalArgs::MixEvalArgs()
});
}
Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
Bindings * MixEvalArgs::getAutoArgs(Evaluator & state)
{
auto res = state.buildBindings(autoArgs.size());
for (auto & i : autoArgs) {
auto v = state.mem.allocValue();
if (i.second[0] == 'E')
state.mkThunk_(*v, state.parseExprFromString(i.second.substr(1), CanonPath::fromCwd()));
state.evalLazily(
state.parseExprFromString(i.second.substr(1), CanonPath::fromCwd()), *v
);
else
v->mkString(((std::string_view) i.second).substr(1));
res.insert(state.symbols.create(i.first), v);
@@ -185,7 +187,7 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
return res.finish();
}
SourcePath lookupFileArg(EvalState & state, std::string_view fileArg)
SourcePath lookupFileArg(Evaluator & state, std::string_view fileArg)
{
if (EvalSettings::isPseudoUrl(fileArg)) {
auto const url = EvalSettings::resolvePseudoUrl(fileArg);
@@ -205,7 +207,7 @@ SourcePath lookupFileArg(EvalState & state, std::string_view fileArg)
return CanonPath(state.store->toRealPath(storePath));
} else if (fileArg.size() > 2 && fileArg.at(0) == '<' && fileArg.at(fileArg.size() - 1) == '>') {
Path p(fileArg.substr(1, fileArg.size() - 2));
return state.findFile(p);
return state.paths.findFile(p);
} else {
return CanonPath::fromCwd(fileArg);
}
+3 -2
View File
@@ -1,6 +1,7 @@
#pragma once
///@file
#include "lix/libexpr/eval.hh"
#include "lix/libutil/args.hh"
#include "lix/libmain/common-args.hh"
#include "lix/libexpr/search-path.hh"
@@ -18,7 +19,7 @@ struct MixEvalArgs : virtual Args, virtual MixRepair
MixEvalArgs();
Bindings * getAutoArgs(EvalState & state);
Bindings * getAutoArgs(Evaluator & state);
SearchPath searchPath;
@@ -48,6 +49,6 @@ private:
*
* @exception nix::ThrownError for failed search path lookup. Probably others.
*/
SourcePath lookupFileArg(EvalState & state, std::string_view fileArg);
SourcePath lookupFileArg(Evaluator & state, std::string_view fileArg);
}
+1 -4
View File
@@ -7,9 +7,6 @@ namespace nix {
Strings editorFor(const SourcePath & file, uint32_t line)
{
auto path = file.getPhysicalPath();
if (!path)
throw Error("cannot open '%s' in an editor because it has no physical path", file);
auto editor = getEnv("EDITOR").value_or("cat");
auto args = tokenizeString<Strings>(editor);
if (line > 0 && (
@@ -18,7 +15,7 @@ Strings editorFor(const SourcePath & file, uint32_t line)
editor.find("vim") != std::string::npos ||
editor.find("kak") != std::string::npos))
args.push_back(fmt("+%d", line));
args.push_back(path->abs());
args.push_back(file.canonical().abs());
return args;
}
+13 -14
View File
@@ -12,7 +12,7 @@
namespace nix {
InstallableAttrPath::InstallableAttrPath(
ref<eval_cache::CachingEvalState> state,
ref<eval_cache::CachingEvaluator> state,
SourceExprCommand & cmd,
Value * v,
const std::string & attrPath,
@@ -24,43 +24,42 @@ InstallableAttrPath::InstallableAttrPath(
, extendedOutputsSpec(std::move(extendedOutputsSpec))
{ }
std::pair<Value *, PosIdx> InstallableAttrPath::toValue()
std::pair<Value *, PosIdx> InstallableAttrPath::toValue(EvalState & state)
{
auto [vRes, pos] = findAlongAttrPath(*state, attrPath, *cmd.getAutoArgs(*state), **v);
state->forceValue(*vRes, pos);
auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(*evaluator), **v);
state.forceValue(*vRes, pos);
return {vRes, pos};
}
DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths()
DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths(EvalState & state)
{
auto [v, pos] = toValue();
auto [v, pos] = toValue(state);
if (std::optional derivedPathWithInfo = trySinglePathToDerivedPaths(
*v,
pos,
fmt("while evaluating the attribute '%s'", attrPath)))
state, *v, pos, fmt("while evaluating the attribute '%s'", attrPath)
))
{
return { *derivedPathWithInfo };
}
Bindings & autoArgs = *cmd.getAutoArgs(*state);
Bindings & autoArgs = *cmd.getAutoArgs(*evaluator);
DrvInfos drvInfos;
getDerivations(*state, *v, "", autoArgs, drvInfos, false);
getDerivations(state, *v, "", autoArgs, drvInfos, false);
// Backward compatibility hack: group results by drvPath. This
// helps keep .all output together.
std::map<StorePath, OutputsSpec> byDrvPath;
for (auto & drvInfo : drvInfos) {
auto drvPath = drvInfo.queryDrvPath();
auto drvPath = drvInfo.queryDrvPath(state);
if (!drvPath)
throw Error("'%s' is not a derivation", what());
auto newOutputs = std::visit(overloaded {
[&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec {
std::set<std::string> outputsToInstall;
for (auto & output : drvInfo.queryOutputs(false, true))
for (auto & output : drvInfo.queryOutputs(state, false, true))
outputsToInstall.insert(output.first);
return OutputsSpec::Names { std::move(outputsToInstall) };
},
@@ -93,7 +92,7 @@ DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths()
}
InstallableAttrPath InstallableAttrPath::parse(
ref<eval_cache::CachingEvalState> state,
ref<eval_cache::CachingEvaluator> state,
SourceExprCommand & cmd,
Value * v,
std::string_view prefix,
+4 -4
View File
@@ -20,7 +20,7 @@ class InstallableAttrPath : public InstallableValue
ExtendedOutputsSpec extendedOutputsSpec;
InstallableAttrPath(
ref<eval_cache::CachingEvalState> state,
ref<eval_cache::CachingEvaluator> state,
SourceExprCommand & cmd,
Value * v,
const std::string & attrPath,
@@ -28,14 +28,14 @@ class InstallableAttrPath : public InstallableValue
std::string what() const override { return attrPath; };
std::pair<Value *, PosIdx> toValue() override;
std::pair<Value *, PosIdx> toValue(EvalState & state) override;
DerivedPathsWithInfo toDerivedPaths() override;
DerivedPathsWithInfo toDerivedPaths(EvalState & state) override;
public:
static InstallableAttrPath parse(
ref<eval_cache::CachingEvalState> state,
ref<eval_cache::CachingEvaluator> state,
SourceExprCommand & cmd,
Value * v,
std::string_view prefix,
+1 -1
View File
@@ -8,7 +8,7 @@ std::string InstallableDerivedPath::what() const
return derivedPath.to_string(*store);
}
DerivedPathsWithInfo InstallableDerivedPath::toDerivedPaths()
DerivedPathsWithInfo InstallableDerivedPath::toDerivedPaths(EvalState & state)
{
return {{
.path = derivedPath,
+1 -1
View File
@@ -16,7 +16,7 @@ struct InstallableDerivedPath : Installable
std::string what() const override;
DerivedPathsWithInfo toDerivedPaths() override;
DerivedPathsWithInfo toDerivedPaths(EvalState & state) override;
std::optional<StorePath> getStorePath() override;
+31 -46
View File
@@ -41,20 +41,6 @@ std::vector<std::string> InstallableFlake::getActualAttrPaths()
return res;
}
Value * InstallableFlake::getFlakeOutputs(const flake::LockedFlake & lockedFlake)
{
auto vFlake = state->mem.allocValue();
callFlake(*state, lockedFlake, *vFlake);
auto aOutputs = vFlake->attrs->get(state->symbols.create("outputs"));
assert(aOutputs);
state->forceValue(*aOutputs->value, aOutputs->value->determinePos(noPos));
return aOutputs->value;
}
static std::string showAttrPaths(const std::vector<std::string> & paths)
{
std::string s;
@@ -67,7 +53,7 @@ static std::string showAttrPaths(const std::vector<std::string> & paths)
InstallableFlake::InstallableFlake(
SourceExprCommand * cmd,
ref<eval_cache::CachingEvalState> state,
ref<eval_cache::CachingEvaluator> state,
FlakeRef && flakeRef,
std::string_view fragment,
ExtendedOutputsSpec extendedOutputsSpec,
@@ -85,23 +71,22 @@ InstallableFlake::InstallableFlake(
throw UsageError("'--arg' and '--argstr' are incompatible with flakes");
}
DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
DerivedPathsWithInfo InstallableFlake::toDerivedPaths(EvalState & state)
{
Activity act(*logger, lvlTalkative, actUnknown, fmt("evaluating derivation '%s'", what()));
auto attr = getCursor();
auto attr = getCursor(state);
auto attrPath = attr->getAttrPathStr();
auto attrPath = attr->getAttrPathStr(state);
if (!attr->isDerivation()) {
if (!attr->isDerivation(state)) {
// FIXME: use eval cache?
auto v = attr->forceValue();
auto v = attr->forceValue(state);
if (std::optional derivedPathWithInfo = trySinglePathToDerivedPaths(
v,
noPos,
fmt("while evaluating the flake output attribute '%s'", attrPath)))
state, v, noPos, fmt("while evaluating the flake output attribute '%s'", attrPath)
))
{
return { *derivedPathWithInfo };
} else {
@@ -109,19 +94,19 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
"expected flake output attribute '%s' to be a derivation or path but found %s: %s",
attrPath,
showType(v),
ValuePrinter(*this->state, v, errorPrintOptions)
ValuePrinter(state, v, errorPrintOptions)
);
}
}
auto drvPath = attr->forceDerivation();
auto drvPath = attr->forceDerivation(state);
std::optional<NixInt::Inner> priority;
if (attr->maybeGetAttr(state->s.outputSpecified)) {
} else if (auto aMeta = attr->maybeGetAttr(state->s.meta)) {
if (auto aPriority = aMeta->maybeGetAttr("priority"))
priority = aPriority->getInt().value;
if (attr->maybeGetAttr(state, "outputSpecified")) {
} else if (auto aMeta = attr->maybeGetAttr(state, "meta")) {
if (auto aPriority = aMeta->maybeGetAttr(state, "priority"))
priority = aPriority->getInt(state).value;
}
return {{
@@ -130,14 +115,14 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
.outputs = std::visit(overloaded {
[&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec {
std::set<std::string> outputsToInstall;
if (auto aOutputSpecified = attr->maybeGetAttr(state->s.outputSpecified)) {
if (aOutputSpecified->getBool()) {
if (auto aOutputName = attr->maybeGetAttr("outputName"))
outputsToInstall = { aOutputName->getString() };
if (auto aOutputSpecified = attr->maybeGetAttr(state, "outputSpecified")) {
if (aOutputSpecified->getBool(state)) {
if (auto aOutputName = attr->maybeGetAttr(state, "outputName"))
outputsToInstall = { aOutputName->getString(state) };
}
} else if (auto aMeta = attr->maybeGetAttr(state->s.meta)) {
if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall"))
for (auto & s : aOutputsToInstall->getListOfStrings())
} else if (auto aMeta = attr->maybeGetAttr(state, "meta")) {
if (auto aOutputsToInstall = aMeta->maybeGetAttr(state, "outputsToInstall"))
for (auto & s : aOutputsToInstall->getListOfStrings(state))
outputsToInstall.insert(s);
}
@@ -159,20 +144,20 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
},
ExtraPathInfoFlake::Flake {
.originalRef = flakeRef,
.lockedRef = getLockedFlake()->flake.lockedRef,
.lockedRef = getLockedFlake(state)->flake.lockedRef,
}),
}};
}
std::pair<Value *, PosIdx> InstallableFlake::toValue()
std::pair<Value *, PosIdx> InstallableFlake::toValue(EvalState & state)
{
return {&getCursor()->forceValue(), noPos};
return {&getCursor(state)->forceValue(state), noPos};
}
std::vector<ref<eval_cache::AttrCursor>>
InstallableFlake::getCursors()
InstallableFlake::getCursors(EvalState & state)
{
auto evalCache = openEvalCache(*state, getLockedFlake());
auto evalCache = openEvalCache(*evaluator, getLockedFlake(state));
auto root = evalCache->getRoot();
@@ -184,7 +169,7 @@ InstallableFlake::getCursors()
for (auto & attrPath : attrPaths) {
debug("trying flake output attribute '%s'", attrPath);
auto attr = root->findAlongAttrPath(parseAttrPath(*state, attrPath));
auto attr = root->findAlongAttrPath(state, parseAttrPath(attrPath));
if (attr) {
res.push_back(ref(*attr));
} else {
@@ -202,20 +187,20 @@ InstallableFlake::getCursors()
return res;
}
std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const
std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake(EvalState & state) const
{
if (!_lockedFlake) {
flake::LockFlags lockFlagsApplyConfig = lockFlags;
// FIXME why this side effect?
lockFlagsApplyConfig.applyNixConfig = true;
_lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlagsApplyConfig));
_lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(state, flakeRef, lockFlagsApplyConfig));
}
return _lockedFlake;
}
FlakeRef InstallableFlake::nixpkgsFlakeRef() const
FlakeRef InstallableFlake::nixpkgsFlakeRef(EvalState & state) const
{
auto lockedFlake = getLockedFlake();
auto lockedFlake = getLockedFlake(state);
if (auto nixpkgsInput = lockedFlake->lockFile.findInput({"nixpkgs"})) {
if (auto lockedNode = std::dynamic_pointer_cast<const flake::LockedNode>(nixpkgsInput)) {
+8 -9
View File
@@ -2,6 +2,7 @@
///@file
#include "lix/libcmd/installable-value.hh"
#include "lix/libexpr/eval.hh"
namespace nix {
@@ -40,7 +41,7 @@ struct InstallableFlake : InstallableValue
InstallableFlake(
SourceExprCommand * cmd,
ref<eval_cache::CachingEvalState> state,
ref<eval_cache::CachingEvaluator> state,
FlakeRef && flakeRef,
std::string_view fragment,
ExtendedOutputsSpec extendedOutputsSpec,
@@ -52,21 +53,19 @@ struct InstallableFlake : InstallableValue
std::vector<std::string> getActualAttrPaths();
Value * getFlakeOutputs(const flake::LockedFlake & lockedFlake);
DerivedPathsWithInfo toDerivedPaths(EvalState & state) override;
DerivedPathsWithInfo toDerivedPaths() override;
std::pair<Value *, PosIdx> toValue() override;
std::pair<Value *, PosIdx> toValue(EvalState & state) override;
/**
* Get a cursor to every attrpath in getActualAttrPaths() that
* exists. However if none exists, throw an exception.
*/
std::vector<ref<eval_cache::AttrCursor>> getCursors() override;
std::vector<ref<eval_cache::AttrCursor>> getCursors(EvalState & state) override;
std::shared_ptr<flake::LockedFlake> getLockedFlake() const;
std::shared_ptr<flake::LockedFlake> getLockedFlake(EvalState & state) const;
FlakeRef nixpkgsFlakeRef() const;
FlakeRef nixpkgsFlakeRef(EvalState & state) const;
};
/**
@@ -83,7 +82,7 @@ static inline FlakeRef defaultNixpkgsFlakeRef()
}
ref<eval_cache::EvalCache> openEvalCache(
eval_cache::CachingEvalState & state,
eval_cache::CachingEvaluator & state,
std::shared_ptr<flake::LockedFlake> lockedFlake);
}
+10 -8
View File
@@ -5,20 +5,20 @@
namespace nix {
std::vector<ref<eval_cache::AttrCursor>>
InstallableValue::getCursors()
InstallableValue::getCursors(EvalState & state)
{
auto evalCache =
std::make_shared<nix::eval_cache::EvalCache>(std::nullopt, *state,
[&]() { return toValue().first; });
std::make_shared<nix::eval_cache::EvalCache>(std::nullopt,
[&](EvalState & state) { return toValue(state).first; });
return {evalCache->getRoot()};
}
ref<eval_cache::AttrCursor>
InstallableValue::getCursor()
InstallableValue::getCursor(EvalState & state)
{
/* Although getCursors should return at least one element, in case it doesn't,
bound check to avoid an undefined behavior for vector[0] */
return getCursors().at(0);
return getCursors(state).at(0);
}
static UsageError nonValueInstallable(Installable & installable)
@@ -42,10 +42,12 @@ ref<InstallableValue> InstallableValue::require(ref<Installable> installable)
return ref { castedInstallable };
}
std::optional<DerivedPathWithInfo> InstallableValue::trySinglePathToDerivedPaths(Value & v, const PosIdx pos, std::string_view errorCtx)
std::optional<DerivedPathWithInfo> InstallableValue::trySinglePathToDerivedPaths(
EvalState & state, Value & v, const PosIdx pos, std::string_view errorCtx
)
{
if (v.type() == nPath) {
auto storePath = fetchToStore(*state->store, v.path());
auto storePath = fetchToStore(*evaluator->store, state.ctx.paths.checkSourcePath(v.path()));
return {{
.path = DerivedPath::Opaque {
.path = std::move(storePath),
@@ -57,7 +59,7 @@ std::optional<DerivedPathWithInfo> InstallableValue::trySinglePathToDerivedPaths
else if (v.type() == nString) {
return {{
.path = DerivedPath::fromSingle(
state->coerceToSingleDerivedPath(pos, v, errorCtx)),
state.coerceToSingleDerivedPath(pos, v, errorCtx)),
.info = make_ref<ExtraPathInfo>(),
}};
}
+11 -8
View File
@@ -3,6 +3,7 @@
#include "lix/libcmd/installables.hh"
#include "lix/libexpr/eval-cache.hh"
#include "lix/libexpr/eval.hh"
#include "lix/libexpr/flake/flake.hh"
namespace nix {
@@ -20,7 +21,7 @@ struct App
struct UnresolvedApp
{
App unresolved;
App resolve(ref<Store> evalStore, ref<Store> store);
App resolve(EvalState & state, ref<Store> evalStore, ref<Store> store);
};
/**
@@ -70,28 +71,28 @@ struct ExtraPathInfoValue : ExtraPathInfo
*/
struct InstallableValue : Installable
{
ref<eval_cache::CachingEvalState> state;
ref<eval_cache::CachingEvaluator> evaluator;
InstallableValue(ref<eval_cache::CachingEvalState> state) : state(state) {}
InstallableValue(ref<eval_cache::CachingEvaluator> evaluator) : evaluator(evaluator) {}
virtual ~InstallableValue() { }
virtual std::pair<Value *, PosIdx> toValue() = 0;
virtual std::pair<Value *, PosIdx> toValue(EvalState & state) = 0;
/**
* Get a cursor to each value this Installable could refer to.
* However if none exists, throw exception instead of returning
* empty vector.
*/
virtual std::vector<ref<eval_cache::AttrCursor>> getCursors();
virtual std::vector<ref<eval_cache::AttrCursor>> getCursors(EvalState & state);
/**
* Get the first and most preferred cursor this Installable could
* refer to, or throw an exception if none exists.
*/
virtual ref<eval_cache::AttrCursor> getCursor();
virtual ref<eval_cache::AttrCursor> getCursor(EvalState & state);
UnresolvedApp toApp();
UnresolvedApp toApp(EvalState & state);
static InstallableValue & require(Installable & installable);
static ref<InstallableValue> require(ref<Installable> installable);
@@ -112,7 +113,9 @@ protected:
* @result A derived path (with empty info, for now) if the value
* matched the above criteria.
*/
std::optional<DerivedPathWithInfo> trySinglePathToDerivedPaths(Value & v, const PosIdx pos, std::string_view errorCtx);
std::optional<DerivedPathWithInfo> trySinglePathToDerivedPaths(
EvalState & state, Value & v, const PosIdx pos, std::string_view errorCtx
);
};
}
+83 -65
View File
@@ -25,12 +25,12 @@ namespace nix {
void completeFlakeInputPath(
AddCompletions & completions,
ref<EvalState> evalState,
EvalState & evalState,
const std::vector<FlakeRef> & flakeRefs,
std::string_view prefix)
{
for (auto & flakeRef : flakeRefs) {
auto flake = flake::getFlake(*evalState, flakeRef, true);
auto flake = flake::getFlake(evalState, flakeRef, true);
for (auto & input : flake.inputs)
if (input.first.starts_with(prefix))
completions.add(input.first);
@@ -86,9 +86,9 @@ MixFlakeOptions::MixFlakeOptions()
}},
.completer = {[&](AddCompletions & completions, size_t n, std::string_view prefix) {
if (n == 0) {
completeFlakeInputPath(completions, getEvalState(), getFlakeRefsForCompletion(), prefix);
completeFlakeInputPath(completions, *getEvaluator()->begin(), getFlakeRefsForCompletion(), prefix);
} else if (n == 1) {
completeFlakeRef(completions, getEvalState()->store, prefix);
completeFlakeRef(completions, getEvaluator()->store, prefix);
}
}}
});
@@ -121,7 +121,7 @@ MixFlakeOptions::MixFlakeOptions()
.category = category,
.labels = {"flake-url"},
.handler = {[&](std::string flakeRef) {
auto evalState = getEvalState();
auto evalState = getEvaluator()->begin();
auto flake = flake::lockFlake(
*evalState,
parseFlakeRef(flakeRef, absPath(".")),
@@ -137,7 +137,7 @@ MixFlakeOptions::MixFlakeOptions()
}
}},
.completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) {
completeFlakeRef(completions, getEvalState()->store, prefix);
completeFlakeRef(completions, getEvaluator()->store, prefix);
}}
});
}
@@ -202,26 +202,26 @@ Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes()
Args::CompleterClosure SourceExprCommand::getCompleteInstallable()
{
return [this](AddCompletions & completions, size_t, std::string_view prefix) {
completeInstallable(completions, prefix);
completeInstallable(*getEvaluator()->begin(), completions, prefix);
};
}
void SourceExprCommand::completeInstallable(AddCompletions & completions, std::string_view prefix)
void SourceExprCommand::completeInstallable(EvalState & state, AddCompletions & completions, std::string_view prefix)
{
try {
if (file) {
completions.setType(AddCompletions::Type::Attrs);
evalSettings.pureEval.override(false);
auto state = getEvalState();
Expr & e = state->parseExprFromFile(
resolveExprPath(state->checkSourcePath(lookupFileArg(*state, *file)))
);
auto evaluator = getEvaluator();
Expr & e = evaluator->parseExprFromFile(
state.ctx.paths.resolveExprPath(lookupFileArg(*evaluator, *file))
);
Value root;
state->eval(e, root);
state.eval(e, root);
auto autoArgs = getAutoArgs(*state);
auto autoArgs = getAutoArgs(*evaluator);
std::string prefix_ = std::string(prefix);
auto sep = prefix_.rfind('.');
@@ -234,15 +234,15 @@ void SourceExprCommand::completeInstallable(AddCompletions & completions, std::s
prefix_ = "";
}
auto [v, pos] = findAlongAttrPath(*state, prefix_, *autoArgs, root);
auto [v, pos] = findAlongAttrPath(state, prefix_, *autoArgs, root);
Value &v1(*v);
state->forceValue(v1, pos);
state.forceValue(v1, pos);
Value v2;
state->autoCallFunction(*autoArgs, v1, v2);
state.autoCallFunction(*autoArgs, v1, v2);
if (v2.type() == nAttrs) {
for (auto & i : *v2.attrs) {
std::string name = state->symbols[i.name];
std::string name = evaluator->symbols[i.name];
if (name.find(searchWord) == 0) {
if (prefix_ == "")
completions.add(name);
@@ -254,7 +254,8 @@ void SourceExprCommand::completeInstallable(AddCompletions & completions, std::s
} else {
completeFlakeRefWithFragment(
completions,
getEvalState(),
state,
getEvaluator(),
lockFlags,
getDefaultFlakeAttrPathPrefixes(),
getDefaultFlakeAttrPaths(),
@@ -267,7 +268,8 @@ void SourceExprCommand::completeInstallable(AddCompletions & completions, std::s
void completeFlakeRefWithFragment(
AddCompletions & completions,
ref<eval_cache::CachingEvalState> evalState,
EvalState & evalState,
ref<eval_cache::CachingEvaluator> evaluator,
flake::LockFlags lockFlags,
Strings attrPathPrefixes,
const Strings & defaultFlakeAttrPaths,
@@ -278,7 +280,7 @@ void completeFlakeRefWithFragment(
try {
auto hash = prefix.find('#');
if (hash == std::string::npos) {
completeFlakeRef(completions, evalState->store, prefix);
completeFlakeRef(completions, evaluator->store, prefix);
} else {
completions.setType(AddCompletions::Type::Attrs);
@@ -291,8 +293,10 @@ void completeFlakeRefWithFragment(
auto flakeRefS = std::string(prefix.substr(0, hash));
auto flakeRef = parseFlakeRef(expandTilde(flakeRefS), absPath("."));
auto evalCache = openEvalCache(*evalState,
std::make_shared<flake::LockedFlake>(lockFlake(*evalState, flakeRef, lockFlags)));
auto evalCache = openEvalCache(
*evaluator,
std::make_shared<flake::LockedFlake>(lockFlake(evalState, flakeRef, lockFlags))
);
auto root = evalCache->getRoot();
@@ -305,25 +309,25 @@ void completeFlakeRefWithFragment(
attrPathPrefixes.push_back("");
for (auto & attrPathPrefixS : attrPathPrefixes) {
auto attrPathPrefix = parseAttrPath(*evalState, attrPathPrefixS);
auto attrPathPrefix = parseAttrPath(attrPathPrefixS);
auto attrPathS = attrPathPrefixS + std::string(fragment);
auto attrPath = parseAttrPath(*evalState, attrPathS);
auto attrPath = parseAttrPath(attrPathS);
std::string lastAttr;
if (!attrPath.empty() && !attrPathS.ends_with(".")) {
lastAttr = evalState->symbols[attrPath.back()];
lastAttr = attrPath.back();
attrPath.pop_back();
}
auto attr = root->findAlongAttrPath(attrPath);
auto attr = root->findAlongAttrPath(evalState, attrPath);
if (!attr) continue;
for (auto & attr2 : (*attr)->getAttrs()) {
if (std::string_view(evalState->symbols[attr2]).starts_with(lastAttr)) {
auto attrPath2 = (*attr)->getAttrPath(attr2);
for (auto & attr2 : (*attr)->getAttrs(evalState)) {
if (std::string_view attr2s = attr2; attr2s.starts_with(lastAttr)) {
auto attrPath2 = (*attr)->getAttrPath(evalState, attr2s);
/* Strip the attrpath prefix. */
attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size());
completions.add(flakeRefS + "#" + prefixRoot + concatStringsSep(".", evalState->symbols.resolve(attrPath2)));
completions.add(flakeRefS + "#" + prefixRoot + concatStringsSep(".", attrPath2));
}
}
}
@@ -332,7 +336,7 @@ void completeFlakeRefWithFragment(
attrpaths. */
if (fragment.empty()) {
for (auto & attrPath : defaultFlakeAttrPaths) {
auto attr = root->findAlongAttrPath(parseAttrPath(*evalState, attrPath));
auto attr = root->findAlongAttrPath(evalState, parseAttrPath(attrPath));
if (!attr) continue;
completions.add(flakeRefS + "#" + prefixRoot);
}
@@ -369,9 +373,9 @@ void completeFlakeRef(AddCompletions & completions, ref<Store> store, std::strin
}
}
DerivedPathWithInfo Installable::toDerivedPath()
DerivedPathWithInfo Installable::toDerivedPath(EvalState & state)
{
auto buildables = toDerivedPaths();
auto buildables = toDerivedPaths(state);
if (buildables.size() != 1)
throw Error("installable '%s' evaluates to %d derivations, where only one is expected", what(), buildables.size());
return std::move(buildables[0]);
@@ -390,25 +394,25 @@ static StorePath getDeriver(
}
ref<eval_cache::EvalCache> openEvalCache(
eval_cache::CachingEvalState & state,
eval_cache::CachingEvaluator & state,
std::shared_ptr<flake::LockedFlake> lockedFlake)
{
auto fingerprint = evalSettings.useEvalCache && evalSettings.pureEval
? std::make_optional(lockedFlake->getFingerprint())
: std::nullopt;
auto rootLoader = [&state, lockedFlake]()
auto rootLoader = [lockedFlake](EvalState & state)
{
/* For testing whether the evaluation cache is
complete. */
if (getEnv("NIX_ALLOW_EVAL").value_or("1") == "0")
throw Error("not everything is cached, but evaluation is not allowed");
auto vFlake = state.mem.allocValue();
auto vFlake = state.ctx.mem.allocValue();
flake::callFlake(state, *lockedFlake, *vFlake);
state.forceAttrs(*vFlake, noPos, "while parsing cached flake data");
auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs"));
auto aOutputs = vFlake->attrs->get(state.ctx.symbols.create("outputs"));
assert(aOutputs);
return aOutputs->value;
@@ -417,12 +421,22 @@ ref<eval_cache::EvalCache> openEvalCache(
if (fingerprint) {
return state.getCacheFor(fingerprint.value(), rootLoader);
} else {
return make_ref<nix::eval_cache::EvalCache>(std::nullopt, state, rootLoader);
return make_ref<nix::eval_cache::EvalCache>(std::nullopt, rootLoader);
}
}
ref<eval_cache::CachingEvaluator> SourceExprCommand::getEvaluator()
{
// FIXME: backward compatibility hack
if (file) {
evalSettings.pureEval.override(false);
}
return EvalCommand::getEvaluator();
}
Installables SourceExprCommand::parseInstallables(
ref<Store> store, std::vector<std::string> ss)
EvalState & state, ref<Store> store, std::vector<std::string> ss)
{
Installables result;
@@ -430,21 +444,18 @@ Installables SourceExprCommand::parseInstallables(
if (file && expr)
throw UsageError("'--file' and '--expr' are exclusive");
// FIXME: backward compatibility hack
if (file) evalSettings.pureEval.override(false);
auto state = getEvalState();
auto vFile = state->mem.allocValue();
auto evaluator = getEvaluator();
auto vFile = evaluator->mem.allocValue();
if (file == "-") {
auto & e = state->parseStdin();
state->eval(e, *vFile);
auto & e = evaluator->parseStdin();
state.eval(e, *vFile);
}
else if (file)
state->evalFile(lookupFileArg(*state, *file), *vFile);
state.evalFile(lookupFileArg(*evaluator, *file), *vFile);
else {
auto & e = state->parseExprFromString(*expr, CanonPath::fromCwd());
state->eval(e, *vFile);
auto & e = evaluator->parseExprFromString(*expr, CanonPath::fromCwd());
state.eval(e, *vFile);
}
for (auto & s : ss) {
@@ -452,7 +463,7 @@ Installables SourceExprCommand::parseInstallables(
result.push_back(
make_ref<InstallableAttrPath>(
InstallableAttrPath::parse(
state, *this, vFile, std::move(prefix), std::move(extendedOutputsSpec))));
evaluator, *this, vFile, std::move(prefix), std::move(extendedOutputsSpec))));
}
} else {
@@ -481,7 +492,7 @@ Installables SourceExprCommand::parseInstallables(
auto [flakeRef, fragment] = parseFlakeRefWithFragment(std::string { prefix }, absPath("."));
result.push_back(make_ref<InstallableFlake>(
this,
getEvalState(),
getEvaluator(),
std::move(flakeRef),
fragment,
std::move(extendedOutputsSpec),
@@ -501,9 +512,9 @@ Installables SourceExprCommand::parseInstallables(
}
ref<Installable> SourceExprCommand::parseInstallable(
ref<Store> store, const std::string & installable)
EvalState & state, ref<Store> store, const std::string & installable)
{
auto installables = parseInstallables(store, {installable});
auto installables = parseInstallables(state, store, {installable});
assert(installables.size() == 1);
return installables.front();
}
@@ -533,6 +544,7 @@ static SingleBuiltPath getBuiltPath(ref<Store> evalStore, ref<Store> store, cons
}
std::vector<BuiltPathWithResult> Installable::build(
EvalState & state,
ref<Store> evalStore,
ref<Store> store,
Realise mode,
@@ -540,7 +552,7 @@ std::vector<BuiltPathWithResult> Installable::build(
BuildMode bMode)
{
std::vector<BuiltPathWithResult> res;
for (auto & [_, builtPathWithResult] : build2(evalStore, store, mode, installables, bMode))
for (auto & [_, builtPathWithResult] : build2(state, evalStore, store, mode, installables, bMode))
res.push_back(builtPathWithResult);
return res;
}
@@ -577,6 +589,7 @@ static void throwBuildErrors(
}
std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> Installable::build2(
EvalState & state,
ref<Store> evalStore,
ref<Store> store,
Realise mode,
@@ -596,7 +609,7 @@ std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> Installable::build
std::map<DerivedPath, std::vector<Aux>> backmap;
for (auto & i : installables) {
for (auto b : i->toDerivedPaths()) {
for (auto b : i->toDerivedPaths(state)) {
pathsToBuild.push_back(b.path);
backmap[b.path].push_back({.info = b.info, .installable = i});
}
@@ -675,6 +688,7 @@ std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> Installable::build
}
BuiltPaths Installable::toBuiltPaths(
EvalState & state,
ref<Store> evalStore,
ref<Store> store,
Realise mode,
@@ -683,7 +697,7 @@ BuiltPaths Installable::toBuiltPaths(
{
if (operateOn == OperateOn::Output) {
BuiltPaths res;
for (auto & p : Installable::build(evalStore, store, mode, installables))
for (auto & p : Installable::build(state, evalStore, store, mode, installables))
res.push_back(p.path);
return res;
} else {
@@ -691,20 +705,21 @@ BuiltPaths Installable::toBuiltPaths(
settings.readOnlyMode = true;
BuiltPaths res;
for (auto & drvPath : Installable::toDerivations(store, installables, true))
for (auto & drvPath : Installable::toDerivations(state, store, installables, true))
res.emplace_back(BuiltPath::Opaque{drvPath});
return res;
}
}
StorePathSet Installable::toStorePathSet(
EvalState & state,
ref<Store> evalStore,
ref<Store> store,
Realise mode, OperateOn operateOn,
const Installables & installables)
{
StorePathSet outPaths;
for (auto & path : toBuiltPaths(evalStore, store, mode, operateOn, installables)) {
for (auto & path : toBuiltPaths(state, evalStore, store, mode, operateOn, installables)) {
auto thisOutPaths = path.outPaths();
outPaths.insert(thisOutPaths.begin(), thisOutPaths.end());
}
@@ -712,13 +727,14 @@ StorePathSet Installable::toStorePathSet(
}
StorePaths Installable::toStorePaths(
EvalState & state,
ref<Store> evalStore,
ref<Store> store,
Realise mode, OperateOn operateOn,
const Installables & installables)
{
StorePaths outPaths;
for (auto & path : toBuiltPaths(evalStore, store, mode, operateOn, installables)) {
for (auto & path : toBuiltPaths(state, evalStore, store, mode, operateOn, installables)) {
auto thisOutPaths = path.outPaths();
outPaths.insert(outPaths.end(), thisOutPaths.begin(), thisOutPaths.end());
}
@@ -726,12 +742,13 @@ StorePaths Installable::toStorePaths(
}
StorePath Installable::toStorePath(
EvalState & state,
ref<Store> evalStore,
ref<Store> store,
Realise mode, OperateOn operateOn,
ref<Installable> installable)
{
auto paths = toStorePathSet(evalStore, store, mode, operateOn, {installable});
auto paths = toStorePathSet(state, evalStore, store, mode, operateOn, {installable});
if (paths.size() != 1)
throw Error("argument '%s' should evaluate to one store path", installable->what());
@@ -740,6 +757,7 @@ StorePath Installable::toStorePath(
}
StorePathSet Installable::toDerivations(
EvalState & state,
ref<Store> store,
const Installables & installables,
bool useDeriver)
@@ -747,7 +765,7 @@ StorePathSet Installable::toDerivations(
StorePathSet drvPaths;
for (const auto & i : installables)
for (const auto & b : i->toDerivedPaths())
for (const auto & b : i->toDerivedPaths(state))
std::visit(overloaded {
[&](const DerivedPath::Opaque & bo) {
drvPaths.insert(
@@ -824,7 +842,7 @@ std::vector<FlakeRef> InstallableCommand::getFlakeRefsForCompletion()
void InstallablesCommand::run(ref<Store> store, std::vector<std::string> && rawInstallables)
{
auto installables = parseInstallables(store, rawInstallables);
auto installables = parseInstallables(*getEvaluator()->begin(), store, rawInstallables);
run(store, std::move(installables));
}
@@ -841,7 +859,7 @@ InstallableCommand::InstallableCommand()
void InstallableCommand::run(ref<Store> store)
{
auto installable = parseInstallable(store, _installable);
auto installable = parseInstallable(*getEvaluator()->begin(), store, _installable);
run(store, std::move(installable));
}
+10 -2
View File
@@ -1,6 +1,7 @@
#pragma once
///@file
#include "lix/libexpr/eval.hh"
#include "lix/libstore/path.hh"
#include "lix/libstore/outputs-spec.hh"
#include "lix/libstore/derived-path.hh"
@@ -127,7 +128,7 @@ struct Installable
*
* This is the main method of this class
*/
virtual DerivedPathsWithInfo toDerivedPaths() = 0;
virtual DerivedPathsWithInfo toDerivedPaths(EvalState & state) = 0;
/**
* A convenience wrapper of the above for when we expect an
@@ -137,7 +138,7 @@ struct Installable
* If no or multiple \ref DerivedPath "derived paths" are produced,
* and error is raised.
*/
DerivedPathWithInfo toDerivedPath();
DerivedPathWithInfo toDerivedPath(EvalState & state);
/**
* Return a value only if this installable is a store path or a
@@ -152,6 +153,7 @@ struct Installable
}
static std::vector<BuiltPathWithResult> build(
EvalState & state,
ref<Store> evalStore,
ref<Store> store,
Realise mode,
@@ -159,6 +161,7 @@ struct Installable
BuildMode bMode = bmNormal);
static std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> build2(
EvalState & state,
ref<Store> evalStore,
ref<Store> store,
Realise mode,
@@ -166,6 +169,7 @@ struct Installable
BuildMode bMode = bmNormal);
static std::set<StorePath> toStorePathSet(
EvalState & state,
ref<Store> evalStore,
ref<Store> store,
Realise mode,
@@ -173,6 +177,7 @@ struct Installable
const Installables & installables);
static std::vector<StorePath> toStorePaths(
EvalState & state,
ref<Store> evalStore,
ref<Store> store,
Realise mode,
@@ -180,6 +185,7 @@ struct Installable
const Installables & installables);
static StorePath toStorePath(
EvalState & state,
ref<Store> evalStore,
ref<Store> store,
Realise mode,
@@ -187,11 +193,13 @@ struct Installable
ref<Installable> installable);
static std::set<StorePath> toDerivations(
EvalState & state,
ref<Store> store,
const Installables & installables,
bool useDeriver = false);
static BuiltPaths toBuiltPaths(
EvalState & state,
ref<Store> evalStore,
ref<Store> store,
Realise mode,
-1
View File
@@ -7,4 +7,3 @@ Description: Lix Package Manager (libcmd)
Version: @PACKAGE_VERSION@
Requires: lix-base lix-util
Libs: -L${libdir} -llixcmd
Cflags: -I${includedir}/lix/libcmd
+93 -48
View File
@@ -18,6 +18,7 @@ extern "C" {
}
#include "lix/libutil/finally.hh"
#include "lix/libutil/strings.hh"
#include "lix/libcmd/repl-interacter.hh"
namespace nix {
@@ -30,52 +31,14 @@ void sigintHandler(int signo)
{
g_signal_received = signo;
}
};
static detail::ReplCompleterMixin * curRepl; // ugly
static char * completionCallback(char * s, int * match)
/**
* @return a null-terminated list of completions as expected by `el_print_columns`
*/
char ** copyCompletions(const StringSet& possible)
{
auto possible = curRepl->completePrefix(s);
if (possible.size() == 1) {
*match = 1;
auto * res = strdup(possible.begin()->c_str() + strlen(s));
if (!res)
throw Error("allocation failure");
return res;
} else if (possible.size() > 1) {
auto checkAllHaveSameAt = [&](size_t pos) {
auto & first = *possible.begin();
for (auto & p : possible) {
if (p.size() <= pos || p[pos] != first[pos])
return false;
}
return true;
};
size_t start = strlen(s);
size_t len = 0;
while (checkAllHaveSameAt(start + len))
++len;
if (len > 0) {
*match = 1;
auto * res = strdup(std::string(*possible.begin(), start, len).c_str());
if (!res)
throw Error("allocation failure");
return res;
}
}
*match = 0;
return nullptr;
}
static int listPossibleCallback(char * s, char *** avp)
{
auto possible = curRepl->completePrefix(s);
if (possible.size() > (INT_MAX / sizeof(char *)))
throw Error("too many completions");
int ac = 0;
char ** vp = nullptr;
@@ -90,15 +53,95 @@ static int listPossibleCallback(char * s, char *** avp)
}
return p;
};
vp = check(static_cast<char **>(malloc(possible.size() * sizeof(char *))));
for (auto & p : possible)
vp[ac++] = check(strdup(p.c_str()));
*avp = vp;
return vp;
}
return ac;
// Instead of using the readline-provided prefix, do our own tokenization
// to avoid the default behavior of treating dots/quotes as word boundaries.
// See the definition of SEPS for what it treats as a boundary:
// https://github.com/troglobit/editline/blob/caf4b3c0ce3b0785791198b11de6f3134e9f05d8/src/editline.c
std::string getLastTokenBeforeCursor()
{
std::string_view line{rl_line_buffer, static_cast<size_t>(rl_point)};
auto tokens = tokenizeString<std::vector<std::string>>(
line,
// Same as editline's SEPS, except for double and single quotes:
"#$&()*:;<=>?[\\]^`{|}~\n\t "
);
if (tokens.empty()) {
return "";
}
return tokens.back();
}
// Sometimes inserting text or listing possible completions has a side effect
// of hiding the text after the cursor (even though it remains in the buffer).
// This helper just refreshes the display while keeping the cursor in place.
//
// Inserting text also sometimes moves the whole buffer down one line, usually
// if the cursor is inside a quoted attr name. I'm not sure why (vs unquoted)
// but it still seems to work pretty well and is just a visual artifact.
el_status_t redisplay()
{
int cursorPos = rl_point;
rl_refresh_line(0, 0);
rl_point = cursorPos;
return (rl_point == rl_end) ? CSstay : CSmove;
}
};
static el_status_t doCompletion() {
auto s = getLastTokenBeforeCursor();
auto possible = curRepl->completePrefix(s);
if (possible.empty()) {
return el_ring_bell();
}
if (possible.size() == 1) {
const auto completion = *possible.cbegin();
if (completion.size() > s.size()) {
rl_insert_text(completion.c_str() + s.size());
return redisplay();
}
return el_ring_bell();
}
auto checkAllHaveSameAt = [&](size_t pos) {
auto & first = *possible.begin();
for (auto & p : possible) {
if (p.size() <= pos || p[pos] != first[pos]) {
return false;
}
}
return true;
};
size_t start = s.size();
size_t len = 0;
while (checkAllHaveSameAt(start + len)) {
++len;
}
if (len > 0) {
auto commonPrefix = possible.begin()->substr(start, len);
rl_insert_text(commonPrefix.c_str());
el_ring_bell();
return redisplay();
}
char** columns = copyCompletions(possible);
el_print_columns(possible.size(), columns);
return redisplay();
}
ReadlineLikeInteracter::Guard ReadlineLikeInteracter::init(detail::ReplCompleterMixin * repl)
@@ -115,8 +158,10 @@ ReadlineLikeInteracter::Guard ReadlineLikeInteracter::init(detail::ReplCompleter
auto oldRepl = curRepl;
curRepl = repl;
Guard restoreRepl([oldRepl] { curRepl = oldRepl; });
rl_set_complete_func(completionCallback);
rl_set_list_possib_func(listPossibleCallback);
// editline does its own escaping of completions, so we rebind tab
// to our own completion function to skip that and do nix escaping
// instead of shell escaping.
el_bind_key(CTL('I'), doCompletion);
return restoreRepl;
}
+98 -76
View File
@@ -12,6 +12,7 @@
#include "lix/libutil/ansicolor.hh"
#include "lix/libmain/shared.hh"
#include "lix/libutil/escape-string.hh"
#include "lix/libexpr/eval.hh"
#include "lix/libexpr/eval-cache.hh"
#include "lix/libexpr/eval-inline.hh"
@@ -119,6 +120,7 @@ struct NixRepl
};
/* clang-format: on */
Evaluator & evaluator;
size_t debugTraceIndex;
Strings loadedFiles;
@@ -231,9 +233,10 @@ static box_ptr<ReplInteracter> makeInteracter() {
NixRepl::NixRepl(const SearchPath & searchPath, nix::ref<Store> store, EvalState & state,
std::function<NixRepl::AnnotatedValues()> getValues)
: AbstractNixRepl(state)
, evaluator(state.ctx)
, debugTraceIndex(0)
, getValues(getValues)
, staticEnv(new StaticEnv(nullptr, state.builtins.staticEnv.get()))
, staticEnv(new StaticEnv(nullptr, evaluator.builtins.staticEnv.get()))
, interacter(makeInteracter())
{
}
@@ -247,7 +250,7 @@ void runNix(Path program, const Strings & args)
.program = settings.nixBinDir+ "/" + program,
.args = args,
.environment = subprocessEnv,
}).wait();
}).waitAndCheck();
return;
}
@@ -281,7 +284,7 @@ ReplExitStatus NixRepl::mainLoop()
{
if (isFirstRepl) {
std::string_view debuggerNotice = "";
if (state.debug && state.debug->inDebugger) {
if (evaluator.debug && evaluator.debug->inDebugger) {
debuggerNotice = " debugger";
}
notice("Lix %1%%2%\nType :? for help.", nixVersion, debuggerNotice);
@@ -306,8 +309,8 @@ ReplExitStatus NixRepl::mainLoop()
// number of chars as the prompt.
if (!interacter->getLine(input, input.empty() ? ReplPromptType::ReplPrompt : ReplPromptType::ContinuationPrompt)) {
// Ctrl-D should exit the debugger.
if (state.debug) {
state.debug->stop = false;
if (evaluator.debug) {
evaluator.debug->stop = false;
}
logger->cout("");
// TODO: Should Ctrl-D exit just the current debugger session or
@@ -348,7 +351,7 @@ ReplExitStatus NixRepl::mainLoop()
}
}
StringSet NixRepl::completePrefix(const std::string & prefix)
StringSet NixRepl::completePrefix(const std::string &prefix)
{
StringSet completions;
@@ -364,7 +367,7 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
}
}
if (state.debug && state.debug->inDebugger) {
if (evaluator.debug && evaluator.debug->inDebugger) {
for (auto const & colonCmd : this->DEBUG_COMMANDS) {
if (colonCmd.starts_with(prefix)) {
completions.insert(std::string(colonCmd));
@@ -413,9 +416,25 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
i++;
}
} else {
// To handle cases like `foo."bar.`, walk back the cursor
// to the previous dot if there are an odd number of quotes.
auto quoteCount =
std::count_if(cur.begin(), cur.begin() + dot, [](char c) { return c == '"'; });
if (quoteCount % 2 != 0) {
// Find the last quote before the dot
auto prevQuote = cur.rfind('"', dot - 1);
if (prevQuote != std::string::npos) {
// And the previous dot prior to that quote
auto prevDot = cur.rfind('.', prevQuote);
if (prevDot != std::string::npos) {
dot = prevDot;
}
}
}
/* Temporarily disable the debugger, to avoid re-entering readline. */
auto debug = std::move(state.debug);
Finally restoreDebug([&]() { state.debug = std::move(debug); });
auto debug = std::move(evaluator.debug);
Finally restoreDebug([&]() { evaluator.debug = std::move(debug); });
try {
/* This is an expression that should evaluate to an
attribute set. Evaluate it to get the names of the
@@ -429,7 +448,10 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
state.forceAttrs(v, noPos, "while evaluating an attrset for the purpose of completion (this error should not be displayed; file an issue?)");
for (auto & i : *v.attrs) {
std::string_view name = state.symbols[i.name];
std::ostringstream output;
printAttributeName(output, evaluator.symbols[i.name]);
std::string name = output.str();
if (name.substr(0, cur2.size()) != cur2) continue;
completions.insert(concatStrings(prev, expr, ".", name));
}
@@ -470,11 +492,11 @@ StorePath NixRepl::getDerivationPath(Value & v) {
auto drvInfo = getDerivation(state, v, false);
if (!drvInfo)
throw Error("expression does not evaluate to a derivation, so I can't build it");
auto drvPath = drvInfo->queryDrvPath();
auto drvPath = drvInfo->queryDrvPath(state);
if (!drvPath)
throw Error("expression did not evaluate to a valid derivation (no 'drvPath' attribute)");
if (!state.store->isValidPath(*drvPath))
throw Error("expression evaluated to invalid derivation '%s'", state.store->printStorePath(*drvPath));
if (!evaluator.store->isValidPath(*drvPath))
throw Error("expression evaluated to invalid derivation '%s'", evaluator.store->printStorePath(*drvPath));
return *drvPath;
}
@@ -482,13 +504,13 @@ void NixRepl::loadDebugTraceEnv(const DebugTrace & dt)
{
initEnv();
auto se = state.debug->staticEnvFor(dt.expr);
auto se = evaluator.debug->staticEnvFor(dt.expr);
if (se) {
auto vm = mapStaticEnvBindings(state.symbols, *se.get(), dt.env);
auto vm = mapStaticEnvBindings(evaluator.symbols, *se.get(), dt.env);
// add staticenv vars.
for (auto & [name, value] : *(vm.get()))
addVarToScope(state.symbols.create(name), *value);
addVarToScope(evaluator.symbols.create(name), *value);
}
}
@@ -538,7 +560,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
<< " errors\n"
<< " :?, :help Brings up this help menu\n"
;
if (state.debug && state.debug->inDebugger) {
if (evaluator.debug && evaluator.debug->inDebugger) {
std::cout
<< "\n"
<< " Debug mode commands\n"
@@ -553,16 +575,16 @@ ProcessLineResult NixRepl::processLine(std::string line)
}
else if (state.debug && state.debug->inDebugger && (command == ":bt" || command == ":backtrace")) {
auto traces = state.debug->traces();
else if (evaluator.debug && evaluator.debug->inDebugger && (command == ":bt" || command == ":backtrace")) {
auto traces = evaluator.debug->traces();
for (const auto & [idx, i] : enumerate(traces)) {
std::cout << "\n" << ANSI_BLUE << idx << ANSI_NORMAL << ": ";
showDebugTrace(std::cout, state.positions, *i);
showDebugTrace(std::cout, evaluator.positions, *i);
}
}
else if (state.debug && state.debug->inDebugger && (command == ":env")) {
auto traces = state.debug->traces();
else if (evaluator.debug && evaluator.debug->inDebugger && (command == ":env")) {
auto traces = evaluator.debug->traces();
for (const auto & [idx, i] : enumerate(traces)) {
if (idx == debugTraceIndex) {
printEnvBindings(state, i->expr, i->env);
@@ -571,17 +593,17 @@ ProcessLineResult NixRepl::processLine(std::string line)
}
}
else if (state.debug && state.debug->inDebugger && (command == ":st")) {
else if (evaluator.debug && evaluator.debug->inDebugger && (command == ":st")) {
try {
// change the DebugTrace index.
debugTraceIndex = stoi(arg);
} catch (...) { }
auto traces = state.debug->traces();
auto traces = evaluator.debug->traces();
for (const auto & [idx, i] : enumerate(traces)) {
if (idx == debugTraceIndex) {
std::cout << "\n" << ANSI_BLUE << idx << ANSI_NORMAL << ": ";
showDebugTrace(std::cout, state.positions, *i);
showDebugTrace(std::cout, evaluator.positions, *i);
std::cout << std::endl;
printEnvBindings(state, i->expr, i->env);
loadDebugTraceEnv(*i);
@@ -590,15 +612,15 @@ ProcessLineResult NixRepl::processLine(std::string line)
}
}
else if (state.debug && state.debug->inDebugger && (command == ":s" || command == ":step")) {
else if (evaluator.debug && evaluator.debug->inDebugger && (command == ":s" || command == ":step")) {
// set flag to stop at next DebugTrace; exit repl.
state.debug->stop = true;
evaluator.debug->stop = true;
return ProcessLineResult::Continue;
}
else if (state.debug && state.debug->inDebugger && (command == ":c" || command == ":continue")) {
else if (evaluator.debug && evaluator.debug->inDebugger && (command == ":c" || command == ":continue")) {
// set flag to run to next breakpoint or end of program; exit repl.
state.debug->stop = false;
evaluator.debug->stop = false;
return ProcessLineResult::Continue;
}
@@ -632,8 +654,8 @@ ProcessLineResult NixRepl::processLine(std::string line)
auto path = state.coerceToPath(noPos, v, context, "while evaluating the filename to edit");
return {path, 0};
} else if (v.isLambda()) {
auto pos = state.positions[v.lambda.fun->pos];
if (auto path = std::get_if<SourcePath>(&pos.origin))
auto pos = evaluator.positions[v.lambda.fun->pos];
if (auto path = std::get_if<CheckedSourcePath>(&pos.origin))
return {*path, pos.line};
else
throw Error("'%s' cannot be shown in an editor", pos);
@@ -650,11 +672,11 @@ ProcessLineResult NixRepl::processLine(std::string line)
// runProgram redirects stdout to a StringSink,
// using runProgram2 to allow editors to display their UI
runProgram2(RunOptions { .program = editor, .searchPath = true, .args = args }).wait();
runProgram2(RunOptions { .program = editor, .searchPath = true, .args = args }).waitAndCheck();
// Reload right after exiting the editor if path is not in store
// Store is immutable, so there could be no changes, so there's no need to reload
if (!state.store->isInStore(path.resolveSymlinks().path.abs())) {
if (!evaluator.store->isInStore(canonPath(path.canonical().abs(), true))) {
state.resetFileCache();
reloadFiles();
}
@@ -673,12 +695,12 @@ ProcessLineResult NixRepl::processLine(std::string line)
state.callFunction(f, v, result, PosIdx());
StorePath drvPath = getDerivationPath(result);
runNix("nix-shell", {state.store->printStorePath(drvPath)});
runNix("nix-shell", {evaluator.store->printStorePath(drvPath)});
}
else if (command == ":log") {
StorePath drvPath = ([&] {
auto maybeDrvPath = state.store->maybeParseStorePath(arg);
auto maybeDrvPath = evaluator.store->maybeParseStorePath(arg);
if (maybeDrvPath && maybeDrvPath->isDerivation()) {
return std::move(*maybeDrvPath);
} else {
@@ -687,7 +709,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
return getDerivationPath(v);
}
})();
Path drvPathRaw = state.store->printStorePath(drvPath);
Path drvPathRaw = evaluator.store->printStorePath(drvPath);
settings.readOnlyMode = true;
Finally roModeReset([&]() {
@@ -695,7 +717,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
});
auto subs = getDefaultSubstituters();
subs.push_front(state.store);
subs.push_front(evaluator.store);
bool foundLog = false;
RunPager pager;
@@ -722,7 +744,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
Value v;
evalString(arg, v);
StorePath drvPath = getDerivationPath(v);
Path drvPathRaw = state.store->printStorePath(drvPath);
Path drvPathRaw = evaluator.store->printStorePath(drvPath);
if (command == ":b" || command == ":bl") {
// TODO: this only shows a progress bar for explicitly initiated builds,
@@ -734,22 +756,22 @@ ProcessLineResult NixRepl::processLine(std::string line)
logger->pause();
});
state.store->buildPaths({
evaluator.store->buildPaths({
DerivedPath::Built {
.drvPath = makeConstantStorePathRef(drvPath),
.outputs = OutputsSpec::All { },
},
});
auto drv = state.store->readDerivation(drvPath);
auto drv = evaluator.store->readDerivation(drvPath);
logger->cout("\nThis derivation produced the following outputs:");
for (auto & [outputName, outputPath] : state.store->queryDerivationOutputMap(drvPath)) {
auto localStore = state.store.dynamic_pointer_cast<LocalFSStore>();
for (auto & [outputName, outputPath] : evaluator.store->queryDerivationOutputMap(drvPath)) {
auto localStore = evaluator.store.dynamic_pointer_cast<LocalFSStore>();
if (localStore && command == ":bl") {
std::string symlink = "repl-result-" + outputName;
localStore->addPermRoot(outputPath, absPath(symlink));
logger->cout(" ./%s -> %s", symlink, state.store->printStorePath(outputPath));
logger->cout(" ./%s -> %s", symlink, evaluator.store->printStorePath(outputPath));
} else {
logger->cout(" %s -> %s", outputName, state.store->printStorePath(outputPath));
logger->cout(" %s -> %s", outputName, evaluator.store->printStorePath(outputPath));
}
}
} else if (command == ":i") {
@@ -771,8 +793,8 @@ ProcessLineResult NixRepl::processLine(std::string line)
}
else if (command == ":q" || command == ":quit") {
if (state.debug) {
state.debug->stop = false;
if (evaluator.debug) {
evaluator.debug->stop = false;
}
return ProcessLineResult::Quit;
}
@@ -780,7 +802,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
else if (command == ":doc") {
Value v;
evalString(arg, v);
if (auto doc = state.builtins.getDoc(v)) {
if (auto doc = evaluator.builtins.getDoc(v)) {
std::string markdown;
if (!doc->args.empty() && doc->name) {
@@ -797,8 +819,8 @@ ProcessLineResult NixRepl::processLine(std::string line)
logger->cout(trim(renderMarkdownToTerminal(markdown)));
} else if (v.isLambda()) {
auto pos = state.positions[v.lambda.fun->pos];
if (auto path = std::get_if<SourcePath>(&pos.origin)) {
auto pos = evaluator.positions[v.lambda.fun->pos];
if (auto path = std::get_if<CheckedSourcePath>(&pos.origin)) {
// Path and position have now been obtained, feed to nix-doc library to get data.
auto docComment = lambdaDocsForPos(*path, pos);
if (!docComment) {
@@ -840,9 +862,9 @@ ProcessLineResult NixRepl::processLine(std::string line)
isVarName(name = removeWhitespace(line.substr(0, p))))
{
Expr & e = parseString(line.substr(p + 1));
Value & v(*state.mem.allocValue());
Value & v(*evaluator.mem.allocValue());
v.mkThunk(env, e);
addVarToScope(state.symbols.create(name), v);
addVarToScope(evaluator.symbols.create(name), v);
} else {
Value v;
evalString(line, v);
@@ -859,7 +881,7 @@ void NixRepl::loadFile(const Path & path)
loadedFiles.remove(path);
loadedFiles.push_back(path);
Value v, v2;
state.evalFile(lookupFileArg(state, path), v);
state.evalFile(lookupFileArg(evaluator, path), v);
state.autoCallFunction(*autoArgs, v, v2);
addAttrsToScope(v2);
}
@@ -889,14 +911,14 @@ void NixRepl::loadFlake(const std::string & flakeRefS)
void NixRepl::initEnv()
{
env = &state.mem.allocEnv(envSize);
env->up = &state.builtins.env;
env = &evaluator.mem.allocEnv(envSize);
env->up = &evaluator.builtins.env;
displ = 0;
staticEnv->vars.clear();
varNames.clear();
for (auto & i : state.builtins.staticEnv->vars)
varNames.emplace(state.symbols[i.first]);
for (auto & i : evaluator.builtins.staticEnv->vars)
varNames.emplace(evaluator.symbols[i.first]);
}
@@ -935,7 +957,7 @@ void NixRepl::loadReplOverlays()
notice("Loading '%1%'...", Magenta("repl-overlays"));
auto replInitFilesFunction = getReplOverlaysEvalFunction();
Value &newAttrs(*state.mem.allocValue());
Value &newAttrs(*evaluator.mem.allocValue());
SmallValueVector<3> args = {replInitInfo(), bindingsToAttrs(), replOverlays()};
state.callFunction(
*replInitFilesFunction,
@@ -960,14 +982,14 @@ Value * NixRepl::getReplOverlaysEvalFunction()
}
auto evalReplInitFilesPath = CanonPath::root + "repl-overlays.nix";
*replOverlaysEvalFunction = state.mem.allocValue();
*replOverlaysEvalFunction = evaluator.mem.allocValue();
auto code =
#include "repl-overlays.nix.gen.hh"
;
auto & expr = state.parseExprFromString(
auto & expr = evaluator.parseExprFromString(
code,
SourcePath(evalReplInitFilesPath),
state.builtins.staticEnv
evaluator.builtins.staticEnv
);
state.eval(expr, **replOverlaysEvalFunction);
@@ -977,8 +999,8 @@ Value * NixRepl::getReplOverlaysEvalFunction()
Value * NixRepl::replOverlays()
{
Value * replInits(state.mem.allocValue());
*replInits = state.mem.newList(evalSettings.replOverlays.get().size());
Value * replInits(evaluator.mem.allocValue());
*replInits = evaluator.mem.newList(evalSettings.replOverlays.get().size());
Value ** replInitElems = replInits->listElems();
size_t i = 0;
@@ -988,7 +1010,7 @@ Value * NixRepl::replOverlays()
auto replInit = evalFile(sourcePath);
if (!replInit->isLambda()) {
state.error<TypeError>(
evaluator.errors.make<TypeError>(
"Expected `repl-overlays` to be a lambda but found %1%: %2%",
showType(*replInit),
ValuePrinter(state, *replInit, errorPrintOptions)
@@ -999,7 +1021,7 @@ Value * NixRepl::replOverlays()
if (replInit->lambda.fun->hasFormals()
&& !replInit->lambda.fun->formals->ellipsis) {
state.error<TypeError>(
evaluator.errors.make<TypeError>(
"Expected first argument of %1% to have %2% to allow future versions of Lix to add additional attributes to the argument",
"repl-overlays",
"..."
@@ -1018,13 +1040,13 @@ Value * NixRepl::replOverlays()
Value * NixRepl::replInitInfo()
{
auto builder = state.buildBindings(2);
auto builder = evaluator.buildBindings(2);
Value * currentSystem(state.mem.allocValue());
Value * currentSystem(evaluator.mem.allocValue());
currentSystem->mkString(evalSettings.getCurrentSystem());
builder.insert(state.symbols.create("currentSystem"), currentSystem);
builder.insert(evaluator.symbols.create("currentSystem"), currentSystem);
Value * info(state.mem.allocValue());
Value * info(evaluator.mem.allocValue());
info->mkAttrs(builder.finish());
return info;
}
@@ -1039,7 +1061,7 @@ void NixRepl::addAttrsToScope(Value & attrs)
for (auto & i : *attrs.attrs) {
staticEnv->vars.emplace_back(i.name, displ);
env->values[displ++] = i.value;
varNames.emplace(state.symbols[i.name]);
varNames.emplace(evaluator.symbols[i.name]);
}
staticEnv->sort();
staticEnv->deduplicate();
@@ -1056,17 +1078,17 @@ void NixRepl::addVarToScope(const Symbol name, Value & v)
staticEnv->vars.emplace_back(name, displ);
staticEnv->sort();
env->values[displ++] = &v;
varNames.emplace(state.symbols[name]);
varNames.emplace(evaluator.symbols[name]);
}
Value * NixRepl::bindingsToAttrs()
{
auto builder = state.buildBindings(staticEnv->vars.size());
auto builder = evaluator.buildBindings(staticEnv->vars.size());
for (auto & [symbol, displacement] : staticEnv->vars) {
builder.insert(symbol, env->values[displacement]);
}
Value * attrs(state.mem.allocValue());
Value * attrs(evaluator.mem.allocValue());
attrs->mkAttrs(builder.finish());
return attrs;
}
@@ -1074,7 +1096,7 @@ Value * NixRepl::bindingsToAttrs()
Expr & NixRepl::parseString(std::string s)
{
return state.parseExprFromString(std::move(s), CanonPath::fromCwd(), staticEnv);
return evaluator.parseExprFromString(std::move(s), CanonPath::fromCwd(), staticEnv);
}
@@ -1087,8 +1109,8 @@ void NixRepl::evalString(std::string s, Value & v)
Value * NixRepl::evalFile(SourcePath & path)
{
auto & expr = state.parseExprFromFile(path, staticEnv);
Value * result(state.mem.allocValue());
auto & expr = evaluator.parseExprFromFile(evaluator.paths.checkSourcePath(path), staticEnv);
Value * result(evaluator.mem.allocValue());
expr.eval(state, *env, *result);
state.forceValue(*result, result->determinePos(noPos));
return result;
@@ -1108,7 +1130,7 @@ ReplExitStatus AbstractNixRepl::run(
repl.autoArgs = autoArgs;
repl.initEnv();
for (auto & [name, value] : extraEnv) {
repl.addVarToScope(repl.state.symbols.create(name), *value);
repl.addVarToScope(repl.evaluator.symbols.create(name), *value);
}
return repl.mainLoop();
}
+91 -34
View File
@@ -1,60 +1,90 @@
#include "lix/libexpr/attr-path.hh"
#include "lix/libexpr/eval-inline.hh"
#include "print-options.hh"
#include <algorithm>
#include <sstream>
namespace nix {
static Strings parseAttrPath(std::string_view s)
std::vector<std::string> parseAttrPath(std::string_view const s)
{
Strings res;
std::vector<std::string> res;
std::string cur;
bool haveData = false;
auto i = s.begin();
while (i != s.end()) {
if (*i == '.') {
res.push_back(cur);
haveData = false;
cur.clear();
} else if (*i == '"') {
// If there is a quote there *will* be a named term even if it is empty.
++i;
haveData = true;
while (1) {
if (i == s.end())
throw ParseError("missing closing quote in selection path '%1%'", s);
if (*i == '"') break;
cur.push_back(*i++);
}
} else
} else {
cur.push_back(*i);
haveData = true;
}
++i;
}
if (!cur.empty()) res.push_back(cur);
if (haveData) res.push_back(cur);
return res;
}
std::vector<Symbol> parseAttrPath(EvalState & state, std::string_view s)
std::string unparseAttrPath(std::vector<std::string> const & attrPath)
{
std::vector<Symbol> res;
for (auto & a : parseAttrPath(s))
res.push_back(state.symbols.create(a));
return res;
// FIXME(jade): can probably be rewritten with ranges once libc++ has a
// fully featured implementation
// https://github.com/llvm/llvm-project/pull/65536
auto ret = std::ostringstream{};
bool first = true;
for (auto const & part : attrPath) {
if (!first) {
ret << ".";
}
first = false;
bool mustQuote = std::ranges::any_of(part, [](char c) -> bool {
return c == '"' || c == '.' || c == ' ';
});
if (mustQuote || part.empty()) {
ret << '"' << part << '"';
} else {
ret << part;
}
}
return ret.str();
}
std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::string & attrPath,
Bindings & autoArgs, Value & vIn)
{
Strings tokens = parseAttrPath(attrPath);
auto tokens = parseAttrPath(attrPath);
Value * v = &vIn;
PosIdx pos = noPos;
for (auto & attr : tokens) {
for (auto [attrPathIdx, attr] : enumerate(tokens)) {
/* Is i an index (integer) or a normal attribute name? */
auto attrIndex = string2Int<unsigned int>(attr);
/* Evaluate the expression. */
Value * vNew = state.mem.allocValue();
Value * vNew = state.ctx.mem.allocValue();
state.autoCallFunction(autoArgs, *v, *vNew);
v = vNew;
state.forceValue(*v, noPos);
@@ -63,37 +93,65 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
according to what is specified in the attrPath. */
if (!attrIndex) {
if (v->type() != nAttrs)
state.error<TypeError>(
"the expression selected by the selection path '%1%' should be a set but is %2%",
attrPath,
showType(*v)).debugThrow();
if (attr.empty())
throw Error("empty attribute name in selection path '%1%'", attrPath);
Bindings::iterator a = v->attrs->find(state.symbols.create(attr));
if (v->type() != nAttrs) {
auto pathPart =
std::vector<std::string>(tokens.begin(), tokens.begin() + attrPathIdx);
state.ctx.errors
.make<TypeError>(
"the value being indexed in the selection path '%1%' at '%2%' should be a "
"set but is %3%: %4%",
attrPath,
unparseAttrPath(pathPart),
showType(*v),
ValuePrinter(state, *v, errorPrintOptions)
)
.debugThrow();
}
Bindings::iterator a = v->attrs->find(state.ctx.symbols.create(attr));
if (a == v->attrs->end()) {
std::set<std::string> attrNames;
for (auto & attr : *v->attrs)
attrNames.insert(state.symbols[attr.name]);
attrNames.insert(state.ctx.symbols[attr.name]);
auto suggestions = Suggestions::bestMatches(attrNames, attr);
throw AttrPathNotFound(suggestions, "attribute '%1%' in selection path '%2%' not found", attr, attrPath);
auto pathPart =
std::vector<std::string>(tokens.begin(), tokens.begin() + attrPathIdx);
throw AttrPathNotFound(
suggestions,
"attribute '%1%' in selection path '%2%' not found inside path '%3%', whose "
"contents are: %4%",
attr,
attrPath,
unparseAttrPath(pathPart),
ValuePrinter(state, *v, errorPrintOptions)
);
}
v = &*a->value;
pos = a->pos;
}
else {
if (!v->isList())
state.error<TypeError>(
"the expression selected by the selection path '%1%' should be a list but is %2%",
} else {
if (!v->isList()) {
state.ctx.errors
.make<TypeError>(
"the expression selected by the selection path '%1%' should be a list but "
"is %2%: %3%",
attrPath,
showType(*v),
ValuePrinter(state, *v, errorPrintOptions)
)
.debugThrow();
}
if (*attrIndex >= v->listSize()) {
throw AttrPathNotFound(
"list index %1% in selection path '%2%' is out of range for list %3%",
*attrIndex,
attrPath,
showType(*v)).debugThrow();
if (*attrIndex >= v->listSize())
throw AttrPathNotFound("list index %1% in selection path '%2%' is out of range", *attrIndex, attrPath);
ValuePrinter(state, *v, errorPrintOptions)
);
}
v = v->listElems()[*attrIndex];
pos = noPos;
@@ -109,7 +167,7 @@ std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v
{
Value * v2;
try {
auto dummyArgs = state.mem.allocBindings(0);
auto dummyArgs = state.ctx.mem.allocBindings(0);
v2 = findAlongAttrPath(state, "meta.position", *dummyArgs, v).first;
} catch (Error &) {
throw NoPositionInfo("package '%s' has no source location information", what);
@@ -120,7 +178,7 @@ std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v
NixStringContext context;
auto path = state.coerceToPath(noPos, *v2, context, "while evaluating the 'meta.position' attribute of a derivation");
auto fn = path.path.abs();
auto fn = path.canonical().abs();
auto fail = [fn]() {
throw ParseError("cannot parse 'meta.position' attribute '%s'", fn);
@@ -129,7 +187,6 @@ std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v
try {
auto colon = fn.rfind(':');
if (colon == std::string::npos) fail();
std::string filename(fn, 0, colon);
auto lineno = std::stoi(std::string(fn, colon + 1, std::string::npos));
return {CanonPath(fn.substr(0, colon)), lineno};
} catch (std::invalid_argument & e) {
+13 -1
View File
@@ -22,6 +22,18 @@ std::pair<Value *, PosIdx> findAlongAttrPath(
*/
std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what);
std::vector<Symbol> parseAttrPath(EvalState & state, std::string_view s);
/**
* Parses an attr path (as used in nix-build -A foo.bar.baz) into a list of tokens.
*
* Such an attr path is a dot-separated sequence of attribute names, which are possibly quoted.
* No escaping is performed; attribute names containing double quotes are unrepresentable.
*/
std::vector<std::string> parseAttrPath(std::string_view const s);
/**
* Converts an attr path from a list of strings into a string once more.
* The result returned is an attr path and is *not necessarily valid nix syntax*.
*/
std::string unparseAttrPath(std::vector<std::string> const & attrPath);
}
+2 -2
View File
@@ -17,11 +17,11 @@ Bindings * EvalMemory::allocBindings(size_t capacity)
{
if (capacity == 0)
return &Bindings::EMPTY;
if (capacity > std::numeric_limits<Bindings::size_t>::max())
if (capacity > std::numeric_limits<Bindings::Size>::max())
throw Error("attribute set of size %d is too big", capacity);
stats.nrAttrsets++;
stats.nrAttrsInAttrsets += capacity;
return new (gcAllocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) Bindings((Bindings::size_t) capacity);
return new (gcAllocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) Bindings((Bindings::Size) capacity);
}
+7 -7
View File
@@ -48,20 +48,20 @@ static_assert(sizeof(Attr) == 2 * sizeof(uint32_t) + sizeof(Value *),
class Bindings
{
public:
typedef uint32_t size_t;
using Size = uint32_t;
PosIdx pos;
static Bindings EMPTY;
private:
size_t size_, capacity_;
Size size_, capacity_;
Attr attrs[0];
Bindings(size_t capacity) : size_(0), capacity_(capacity) { }
Bindings(Size capacity) : size_(0), capacity_(capacity) { }
Bindings(const Bindings & bindings) = delete;
public:
size_t size() const { return size_; }
Size size() const { return size_; }
bool empty() const { return !size_; }
@@ -92,14 +92,14 @@ public:
iterator begin() { return &attrs[0]; }
iterator end() { return &attrs[size_]; }
Attr & operator[](size_t pos)
Attr & operator[](Size pos)
{
return attrs[pos];
}
void sort();
size_t capacity() { return capacity_; }
Size capacity() { return capacity_; }
/**
* Returns the attributes in lexicographically sorted order.
@@ -108,7 +108,7 @@ public:
{
std::vector<const Attr *> res;
res.reserve(size_);
for (size_t n = 0; n < size_; n++)
for (Size n = 0; n < size_; n++)
res.emplace_back(&attrs[n]);
std::sort(res.begin(), res.end(), [&](const Attr * a, const Attr * b) {
std::string_view sa = symbols[a->name], sb = symbols[b->name];
+117 -148
View File
@@ -21,8 +21,6 @@ struct AttrDb
{
std::atomic_bool failed{false};
const Store & cfg;
struct State
{
SQLite db;
@@ -35,15 +33,8 @@ struct AttrDb
std::unique_ptr<Sync<State>> _state;
SymbolTable & symbols;
AttrDb(
const Store & cfg,
const Hash & fingerprint,
SymbolTable & symbols)
: cfg(cfg)
, _state(std::make_unique<Sync<State>>())
, symbols(symbols)
AttrDb(const Hash & fingerprint)
: _state(std::make_unique<Sync<State>>())
{
auto state(_state->lock());
@@ -98,7 +89,7 @@ struct AttrDb
AttrId setAttrs(
AttrKey key,
const std::vector<Symbol> & attrs)
const fullattr_t & attrs)
{
return doSQLite([&]()
{
@@ -106,17 +97,17 @@ struct AttrDb
state->insertAttribute.use()
(key.first)
(symbols[key.second])
(key.second)
(AttrType::FullAttrs)
(0, false).exec();
AttrId rowId = state->db.getLastInsertedRowId();
assert(rowId);
for (auto & attr : attrs)
for (auto & attr : attrs.p)
state->insertAttribute.use()
(rowId)
(symbols[attr])
(attr)
(AttrType::Placeholder)
(0, false).exec();
@@ -141,14 +132,14 @@ struct AttrDb
}
state->insertAttributeWithContext.use()
(key.first)
(symbols[key.second])
(key.second)
(AttrType::String)
(s)
(ctx).exec();
} else {
state->insertAttribute.use()
(key.first)
(symbols[key.second])
(key.second)
(AttrType::String)
(s).exec();
}
@@ -167,7 +158,7 @@ struct AttrDb
state->insertAttribute.use()
(key.first)
(symbols[key.second])
(key.second)
(AttrType::Bool)
(b ? 1 : 0).exec();
@@ -185,7 +176,7 @@ struct AttrDb
state->insertAttribute.use()
(key.first)
(symbols[key.second])
(key.second)
(AttrType::Int)
(n).exec();
@@ -203,7 +194,7 @@ struct AttrDb
state->insertAttribute.use()
(key.first)
(symbols[key.second])
(key.second)
(AttrType::ListOfStrings)
(concatStringsSep("\t", l)).exec();
@@ -219,7 +210,7 @@ struct AttrDb
state->insertAttribute.use()
(key.first)
(symbols[key.second])
(key.second)
(AttrType::Placeholder)
(0, false).exec();
@@ -235,7 +226,7 @@ struct AttrDb
state->insertAttribute.use()
(key.first)
(symbols[key.second])
(key.second)
(AttrType::Missing)
(0, false).exec();
@@ -251,7 +242,7 @@ struct AttrDb
state->insertAttribute.use()
(key.first)
(symbols[key.second])
(key.second)
(AttrType::Misc)
(0, false).exec();
@@ -267,7 +258,7 @@ struct AttrDb
state->insertAttribute.use()
(key.first)
(symbols[key.second])
(key.second)
(AttrType::Failed)
(0, false).exec();
@@ -279,7 +270,7 @@ struct AttrDb
{
auto state(_state->lock());
auto queryAttribute(state->queryAttribute.use()(key.first)(symbols[key.second]));
auto queryAttribute(state->queryAttribute.use()(key.first)(key.second));
if (!queryAttribute.next()) return {};
auto rowId = (AttrId) queryAttribute.getInt(0);
@@ -290,10 +281,10 @@ struct AttrDb
return {{rowId, placeholder_t()}};
case AttrType::FullAttrs: {
// FIXME: expensive, should separate this out.
std::vector<Symbol> attrs;
fullattr_t attrs;
auto queryAttributes(state->queryAttributes.use()(rowId));
while (queryAttributes.next())
attrs.emplace_back(symbols.create(queryAttributes.getStr(0)));
attrs.p.emplace_back(queryAttributes.getStr(0));
return {{rowId, attrs}};
}
case AttrType::String: {
@@ -321,44 +312,39 @@ struct AttrDb
}
};
static std::shared_ptr<AttrDb> makeAttrDb(
const Store & cfg,
const Hash & fingerprint,
SymbolTable & symbols)
static std::shared_ptr<AttrDb> makeAttrDb(const Hash & fingerprint)
{
try {
return std::make_shared<AttrDb>(cfg, fingerprint, symbols);
return std::make_shared<AttrDb>(fingerprint);
} catch (SQLiteError &) {
ignoreExceptionExceptInterrupt();
return nullptr;
}
}
ref<EvalCache> CachingEvalState::getCacheFor(Hash hash, RootLoader rootLoader)
ref<EvalCache> CachingEvaluator::getCacheFor(Hash hash, RootLoader rootLoader)
{
if (auto it = caches.find(hash); it != caches.end()) {
return it->second;
}
auto cache = make_ref<EvalCache>(hash, *this, rootLoader);
auto cache = make_ref<EvalCache>(hash, rootLoader);
caches.emplace(hash, cache);
return cache;
}
EvalCache::EvalCache(
std::optional<std::reference_wrapper<const Hash>> useCache,
EvalState & state,
RootLoader rootLoader)
: db(useCache ? makeAttrDb(*state.store, *useCache, state.symbols) : nullptr)
, state(state)
: db(useCache ? makeAttrDb(*useCache) : nullptr)
, rootLoader(rootLoader)
{
}
Value * EvalCache::getRootValue()
Value * EvalCache::getRootValue(EvalState & state)
{
if (!value) {
debug("getting root value");
value = allocRootValue(rootLoader());
value = allocRootValue(rootLoader(state));
}
return *value;
}
@@ -382,7 +368,7 @@ AttrCursor::AttrCursor(
AttrKey AttrCursor::getKey()
{
if (!parent)
return {0, root->state.s.epsilon};
return {0, ""};
if (!parent->first->cachedValue) {
parent->first->cachedValue = root->db->getAttr(parent->first->getKey());
assert(parent->first->cachedValue);
@@ -390,59 +376,59 @@ AttrKey AttrCursor::getKey()
return {parent->first->cachedValue->first, parent->second};
}
Value & AttrCursor::getValue()
Value & AttrCursor::getValue(EvalState & state)
{
if (!_value) {
if (parent) {
auto & vParent = parent->first->getValue();
root->state.forceAttrs(vParent, noPos, "while searching for an attribute");
auto attr = vParent.attrs->get(parent->second);
auto & vParent = parent->first->getValue(state);
state.forceAttrs(vParent, noPos, "while searching for an attribute");
auto attr = vParent.attrs->get(state.ctx.symbols.create(parent->second));
if (!attr)
throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr());
throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr(state));
_value = allocRootValue(attr->value);
} else
_value = allocRootValue(root->getRootValue());
_value = allocRootValue(root->getRootValue(state));
}
return **_value;
}
std::vector<Symbol> AttrCursor::getAttrPath() const
std::vector<std::string> AttrCursor::getAttrPath(EvalState & state) const
{
if (parent) {
auto attrPath = parent->first->getAttrPath();
auto attrPath = parent->first->getAttrPath(state);
attrPath.push_back(parent->second);
return attrPath;
} else
return {};
}
std::vector<Symbol> AttrCursor::getAttrPath(Symbol name) const
std::vector<std::string> AttrCursor::getAttrPath(EvalState & state, std::string_view name) const
{
auto attrPath = getAttrPath();
attrPath.push_back(name);
auto attrPath = getAttrPath(state);
attrPath.emplace_back(name);
return attrPath;
}
std::string AttrCursor::getAttrPathStr() const
std::string AttrCursor::getAttrPathStr(EvalState & state) const
{
return concatStringsSep(".", root->state.symbols.resolve(getAttrPath()));
return concatStringsSep(".", getAttrPath(state));
}
std::string AttrCursor::getAttrPathStr(Symbol name) const
std::string AttrCursor::getAttrPathStr(EvalState & state, std::string_view name) const
{
return concatStringsSep(".", root->state.symbols.resolve(getAttrPath(name)));
return concatStringsSep(".", getAttrPath(state, name));
}
Value & AttrCursor::forceValue()
Value & AttrCursor::forceValue(EvalState & state)
{
debug("evaluating uncached attribute '%s'", getAttrPathStr());
debug("evaluating uncached attribute '%s'", getAttrPathStr(state));
auto & v = getValue();
auto & v = getValue(state);
try {
root->state.forceValue(v, noPos);
state.forceValue(v, noPos);
} catch (EvalError &) {
debug("setting '%s' to failed", getAttrPathStr());
debug("setting '%s' to failed", getAttrPathStr(state));
if (root->db)
cachedValue = {root->db->setFailed(getKey()), failed_t()};
throw;
@@ -453,8 +439,8 @@ Value & AttrCursor::forceValue()
cachedValue = {root->db->setString(getKey(), v.string.s, v.string.context),
string_t{v.string.s, {}}};
else if (v.type() == nPath) {
auto path = v.path().path;
cachedValue = {root->db->setString(getKey(), path.abs()), string_t{path.abs(), {}}};
auto path = v.path().canonical().abs();
cachedValue = {root->db->setString(getKey(), path), string_t{path, {}}};
}
else if (v.type() == nBool)
cachedValue = {root->db->setBool(getKey(), v.boolean), v.boolean};
@@ -469,27 +455,23 @@ Value & AttrCursor::forceValue()
return v;
}
Suggestions AttrCursor::getSuggestionsForAttr(Symbol name)
Suggestions AttrCursor::getSuggestionsForAttr(EvalState & state, const std::string & name)
{
auto attrNames = getAttrs();
std::set<std::string> strAttrNames;
for (auto & name : attrNames)
strAttrNames.insert(root->state.symbols[name]);
return Suggestions::bestMatches(strAttrNames, root->state.symbols[name]);
auto attrNames = getAttrs(state);
return Suggestions::bestMatches({attrNames.begin(), attrNames.end()}, name);
}
std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name)
std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(EvalState & state, const std::string & name)
{
if (root->db) {
if (!cachedValue)
cachedValue = root->db->getAttr(getKey());
if (cachedValue) {
if (auto attrs = std::get_if<std::vector<Symbol>>(&cachedValue->second)) {
for (auto & attr : *attrs)
if (auto attrs = std::get_if<fullattr_t>(&cachedValue->second)) {
for (auto & attr : attrs->p)
if (attr == name)
return std::make_shared<AttrCursor>(root, std::make_pair(shared_from_this(), attr));
return std::make_shared<AttrCursor>(root, std::make_pair(shared_from_this(), name));
return nullptr;
} else if (std::get_if<placeholder_t>(&cachedValue->second)) {
auto attr = root->db->getAttr({cachedValue->first, name});
@@ -497,7 +479,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name)
if (std::get_if<missing_t>(&attr->second))
return nullptr;
else if (std::get_if<failed_t>(&attr->second)) {
debug("reevaluating failed cached attribute '%s'", getAttrPathStr(name));
debug("reevaluating failed cached attribute '%s'", getAttrPathStr(state, name));
} else
return std::make_shared<AttrCursor>(root,
std::make_pair(shared_from_this(), name), nullptr, std::move(attr));
@@ -506,17 +488,17 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name)
// evaluate to see whether 'name' exists
} else
return nullptr;
//error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
//errors.make<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
}
}
auto & v = forceValue();
auto & v = forceValue(state);
if (v.type() != nAttrs)
return nullptr;
//error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
//errors.make<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
auto attr = v.attrs->get(name);
auto attr = v.attrs->get(state.ctx.symbols.create(name));
if (!attr) {
if (root->db) {
@@ -538,31 +520,21 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name)
root, std::make_pair(shared_from_this(), name), attr->value, std::move(cachedValue2));
}
std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(std::string_view name)
ref<AttrCursor> AttrCursor::getAttr(EvalState & state, const std::string & name)
{
return maybeGetAttr(root->state.symbols.create(name));
}
ref<AttrCursor> AttrCursor::getAttr(Symbol name)
{
auto p = maybeGetAttr(name);
auto p = maybeGetAttr(state, name);
if (!p)
throw Error("attribute '%s' does not exist", getAttrPathStr(name));
throw Error("attribute '%s' does not exist", getAttrPathStr(state, name));
return ref(p);
}
ref<AttrCursor> AttrCursor::getAttr(std::string_view name)
{
return getAttr(root->state.symbols.create(name));
}
OrSuggestions<ref<AttrCursor>> AttrCursor::findAlongAttrPath(const std::vector<Symbol> & attrPath)
OrSuggestions<ref<AttrCursor>> AttrCursor::findAlongAttrPath(EvalState & state, const std::vector<std::string> & attrPath)
{
auto res = shared_from_this();
for (auto & attr : attrPath) {
auto child = res->maybeGetAttr(attr);
auto child = res->maybeGetAttr(state, attr);
if (!child) {
auto suggestions = res->getSuggestionsForAttr(attr);
auto suggestions = res->getSuggestionsForAttr(state, attr);
return OrSuggestions<ref<AttrCursor>>::failed(suggestions);
}
res = child;
@@ -570,30 +542,30 @@ OrSuggestions<ref<AttrCursor>> AttrCursor::findAlongAttrPath(const std::vector<S
return ref(res);
}
std::string AttrCursor::getString()
std::string AttrCursor::getString(EvalState & state)
{
if (root->db) {
if (!cachedValue)
cachedValue = root->db->getAttr(getKey());
if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
if (auto s = std::get_if<string_t>(&cachedValue->second)) {
debug("using cached string attribute '%s'", getAttrPathStr());
debug("using cached string attribute '%s'", getAttrPathStr(state));
return s->first;
} else
root->state.error<TypeError>("'%s' is not a string", getAttrPathStr()).debugThrow();
state.ctx.errors.make<TypeError>("'%s' is not a string", getAttrPathStr(state)).debugThrow();
}
}
auto & v = forceValue();
auto & v = forceValue(state);
if (v.type() != nString && v.type() != nPath) {
root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr(), v.type()).debugThrow();
state.ctx.errors.make<TypeError>("'%s' is not a string but %s", getAttrPathStr(state), v.type()).debugThrow();
}
return v.type() == nString ? v.string.s : v.path().to_string();
}
string_t AttrCursor::getStringWithContext()
string_t AttrCursor::getStringWithContext(EvalState & state)
{
if (root->db) {
if (!cachedValue)
@@ -613,21 +585,21 @@ string_t AttrCursor::getStringWithContext()
return o.path;
},
}, c.raw);
if (!root->state.store->isValidPath(path)) {
if (!state.ctx.store->isValidPath(path)) {
valid = false;
break;
}
}
if (valid) {
debug("using cached string attribute '%s'", getAttrPathStr());
debug("using cached string attribute '%s'", getAttrPathStr(state));
return *s;
}
} else
root->state.error<TypeError>("'%s' is not a string", getAttrPathStr()).debugThrow();
state.ctx.errors.make<TypeError>("'%s' is not a string", getAttrPathStr(state)).debugThrow();
}
}
auto & v = forceValue();
auto & v = forceValue(state);
if (v.type() == nString) {
NixStringContext context;
@@ -636,80 +608,80 @@ string_t AttrCursor::getStringWithContext()
} else if (v.type() == nPath) {
return {v.path().to_string(), {}};
} else {
root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr(), v.type()).debugThrow();
state.ctx.errors.make<TypeError>("'%s' is not a string but %s", getAttrPathStr(state), v.type()).debugThrow();
}
}
bool AttrCursor::getBool()
bool AttrCursor::getBool(EvalState & state)
{
if (root->db) {
if (!cachedValue)
cachedValue = root->db->getAttr(getKey());
if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
if (auto b = std::get_if<bool>(&cachedValue->second)) {
debug("using cached Boolean attribute '%s'", getAttrPathStr());
debug("using cached Boolean attribute '%s'", getAttrPathStr(state));
return *b;
} else
root->state.error<TypeError>("'%s' is not a Boolean", getAttrPathStr()).debugThrow();
state.ctx.errors.make<TypeError>("'%s' is not a Boolean", getAttrPathStr(state)).debugThrow();
}
}
auto & v = forceValue();
auto & v = forceValue(state);
if (v.type() != nBool)
root->state.error<TypeError>("'%s' is not a Boolean", getAttrPathStr()).debugThrow();
state.ctx.errors.make<TypeError>("'%s' is not a Boolean", getAttrPathStr(state)).debugThrow();
return v.boolean;
}
NixInt AttrCursor::getInt()
NixInt AttrCursor::getInt(EvalState & state)
{
if (root->db) {
if (!cachedValue)
cachedValue = root->db->getAttr(getKey());
if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
if (auto i = std::get_if<int_t>(&cachedValue->second)) {
debug("using cached integer attribute '%s'", getAttrPathStr());
debug("using cached integer attribute '%s'", getAttrPathStr(state));
return i->x;
} else
root->state.error<TypeError>("'%s' is not an integer", getAttrPathStr()).debugThrow();
state.ctx.errors.make<TypeError>("'%s' is not an integer", getAttrPathStr(state)).debugThrow();
}
}
auto & v = forceValue();
auto & v = forceValue(state);
if (v.type() != nInt)
root->state.error<TypeError>("'%s' is not an integer", getAttrPathStr()).debugThrow();
state.ctx.errors.make<TypeError>("'%s' is not an integer", getAttrPathStr(state)).debugThrow();
return v.integer;
}
std::vector<std::string> AttrCursor::getListOfStrings()
std::vector<std::string> AttrCursor::getListOfStrings(EvalState & state)
{
if (root->db) {
if (!cachedValue)
cachedValue = root->db->getAttr(getKey());
if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
if (auto l = std::get_if<std::vector<std::string>>(&cachedValue->second)) {
debug("using cached list of strings attribute '%s'", getAttrPathStr());
debug("using cached list of strings attribute '%s'", getAttrPathStr(state));
return *l;
} else
root->state.error<TypeError>("'%s' is not a list of strings", getAttrPathStr()).debugThrow();
state.ctx.errors.make<TypeError>("'%s' is not a list of strings", getAttrPathStr(state)).debugThrow();
}
}
debug("evaluating uncached attribute '%s'", getAttrPathStr());
debug("evaluating uncached attribute '%s'", getAttrPathStr(state));
auto & v = getValue();
root->state.forceValue(v, noPos);
auto & v = getValue(state);
state.forceValue(v, noPos);
if (v.type() != nList)
root->state.error<TypeError>("'%s' is not a list", getAttrPathStr()).debugThrow();
state.ctx.errors.make<TypeError>("'%s' is not a list", getAttrPathStr(state)).debugThrow();
std::vector<std::string> res;
for (auto & elem : v.listItems())
res.push_back(std::string(root->state.forceStringNoCtx(*elem, noPos, "while evaluating an attribute for caching")));
res.push_back(std::string(state.forceStringNoCtx(*elem, noPos, "while evaluating an attribute for caching")));
if (root->db)
cachedValue = {root->db->setListOfStrings(getKey(), res), res};
@@ -717,56 +689,53 @@ std::vector<std::string> AttrCursor::getListOfStrings()
return res;
}
std::vector<Symbol> AttrCursor::getAttrs()
std::vector<std::string> AttrCursor::getAttrs(EvalState & state)
{
if (root->db) {
if (!cachedValue)
cachedValue = root->db->getAttr(getKey());
if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
if (auto attrs = std::get_if<std::vector<Symbol>>(&cachedValue->second)) {
debug("using cached attrset attribute '%s'", getAttrPathStr());
return *attrs;
if (auto attrs = std::get_if<fullattr_t>(&cachedValue->second)) {
debug("using cached attrset attribute '%s'", getAttrPathStr(state));
return attrs->p;
} else
root->state.error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
state.ctx.errors.make<TypeError>("'%s' is not an attribute set", getAttrPathStr(state)).debugThrow();
}
}
auto & v = forceValue();
auto & v = forceValue(state);
if (v.type() != nAttrs)
root->state.error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
state.ctx.errors.make<TypeError>("'%s' is not an attribute set", getAttrPathStr(state)).debugThrow();
std::vector<Symbol> attrs;
for (auto & attr : *getValue().attrs)
attrs.push_back(attr.name);
std::sort(attrs.begin(), attrs.end(), [&](Symbol a, Symbol b) {
std::string_view sa = root->state.symbols[a], sb = root->state.symbols[b];
return sa < sb;
});
fullattr_t attrs;
for (auto & attr : *getValue(state).attrs)
attrs.p.push_back(state.ctx.symbols[attr.name]);
std::sort(attrs.p.begin(), attrs.p.end());
if (root->db)
cachedValue = {root->db->setAttrs(getKey(), attrs), attrs};
return attrs;
return attrs.p;
}
bool AttrCursor::isDerivation()
bool AttrCursor::isDerivation(EvalState & state)
{
auto aType = maybeGetAttr("type");
return aType && aType->getString() == "derivation";
auto aType = maybeGetAttr(state, "type");
return aType && aType->getString(state) == "derivation";
}
StorePath AttrCursor::forceDerivation()
StorePath AttrCursor::forceDerivation(EvalState & state)
{
auto aDrvPath = getAttr(root->state.s.drvPath);
auto drvPath = root->state.store->parseStorePath(aDrvPath->getString());
if (!root->state.store->isValidPath(drvPath) && !settings.readOnlyMode) {
auto aDrvPath = getAttr(state, "drvPath");
auto drvPath = state.ctx.store->parseStorePath(aDrvPath->getString(state));
if (!state.ctx.store->isValidPath(drvPath) && !settings.readOnlyMode) {
/* The eval cache contains 'drvPath', but the actual path has
been garbage-collected. So force it to be regenerated. */
aDrvPath->forceValue();
if (!root->state.store->isValidPath(drvPath))
aDrvPath->forceValue(state);
if (!state.ctx.store->isValidPath(drvPath))
throw Error("don't know how to recreate store derivation '%s'!",
root->state.store->printStorePath(drvPath));
state.ctx.store->printStorePath(drvPath));
}
return drvPath;
}
+26 -31
View File
@@ -13,7 +13,7 @@ namespace nix::eval_cache {
struct AttrDb;
class AttrCursor;
typedef std::function<Value *()> RootLoader;
typedef std::function<Value *(EvalState &)> RootLoader;
/**
* EvalState with caching support. Historically this was part of EvalState,
@@ -22,7 +22,7 @@ typedef std::function<Value *()> RootLoader;
* the rather strong connection between EvalState and these caches. At some
* future time the cache interface should be changed to hide its EvalState.
*/
class CachingEvalState : public EvalState
class CachingEvaluator : public Evaluator
{
/**
* A cache for evaluation caches, so as to reuse the same root value if possible
@@ -30,7 +30,7 @@ class CachingEvalState : public EvalState
std::map<Hash, ref<EvalCache>> caches;
public:
using EvalState::EvalState;
using Evaluator::Evaluator;
ref<EvalCache> getCacheFor(Hash hash, RootLoader rootLoader);
};
@@ -40,17 +40,15 @@ class EvalCache : public std::enable_shared_from_this<EvalCache>
friend class AttrCursor;
std::shared_ptr<AttrDb> db;
EvalState & state;
RootLoader rootLoader;
RootValue value;
Value * getRootValue();
Value * getRootValue(EvalState & state);
public:
EvalCache(
std::optional<std::reference_wrapper<const Hash>> useCache,
EvalState & state,
RootLoader rootLoader);
ref<AttrCursor> getRoot();
@@ -69,16 +67,17 @@ enum AttrType {
};
struct placeholder_t {};
struct fullattr_t { std::vector<std::string> p; };
struct missing_t {};
struct misc_t {};
struct failed_t {};
struct int_t { NixInt x; };
typedef uint64_t AttrId;
typedef std::pair<AttrId, Symbol> AttrKey;
typedef std::pair<AttrId, std::string> AttrKey;
typedef std::pair<std::string, NixStringContext> string_t;
typedef std::variant<
std::vector<Symbol>,
fullattr_t,
string_t,
placeholder_t,
missing_t,
@@ -94,14 +93,14 @@ class AttrCursor : public std::enable_shared_from_this<AttrCursor>
friend class EvalCache;
ref<EvalCache> root;
typedef std::optional<std::pair<std::shared_ptr<AttrCursor>, Symbol>> Parent;
typedef std::optional<std::pair<std::shared_ptr<AttrCursor>, std::string>> Parent;
Parent parent;
RootValue _value;
std::optional<std::pair<AttrId, AttrValue>> cachedValue;
AttrKey getKey();
Value & getValue();
Value & getValue(EvalState & state);
public:
@@ -111,50 +110,46 @@ public:
Value * value = nullptr,
std::optional<std::pair<AttrId, AttrValue>> && cachedValue = {});
std::vector<Symbol> getAttrPath() const;
std::vector<std::string> getAttrPath(EvalState & state) const;
std::vector<Symbol> getAttrPath(Symbol name) const;
std::vector<std::string> getAttrPath(EvalState & state, std::string_view name) const;
std::string getAttrPathStr() const;
std::string getAttrPathStr(EvalState & state) const;
std::string getAttrPathStr(Symbol name) const;
std::string getAttrPathStr(EvalState & state, std::string_view name) const;
Suggestions getSuggestionsForAttr(Symbol name);
Suggestions getSuggestionsForAttr(EvalState & state, const std::string & name);
std::shared_ptr<AttrCursor> maybeGetAttr(Symbol name);
std::shared_ptr<AttrCursor> maybeGetAttr(EvalState & state, const std::string & name);
std::shared_ptr<AttrCursor> maybeGetAttr(std::string_view name);
ref<AttrCursor> getAttr(Symbol name);
ref<AttrCursor> getAttr(std::string_view name);
ref<AttrCursor> getAttr(EvalState & state, const std::string & name);
/**
* Get an attribute along a chain of attrsets. Note that this does
* not auto-call functors or functions.
*/
OrSuggestions<ref<AttrCursor>> findAlongAttrPath(const std::vector<Symbol> & attrPath);
OrSuggestions<ref<AttrCursor>> findAlongAttrPath(EvalState & state, const std::vector<std::string> & attrPath);
std::string getString();
std::string getString(EvalState & state);
string_t getStringWithContext();
string_t getStringWithContext(EvalState & state);
bool getBool();
bool getBool(EvalState & state);
NixInt getInt();
NixInt getInt(EvalState & state);
std::vector<std::string> getListOfStrings();
std::vector<std::string> getListOfStrings(EvalState & state);
std::vector<Symbol> getAttrs();
std::vector<std::string> getAttrs(EvalState & state);
bool isDerivation();
bool isDerivation(EvalState & state);
Value & forceValue();
Value & forceValue(EvalState & state);
/**
* Force creation of the .drv file in the Nix store.
*/
StorePath forceDerivation();
StorePath forceDerivation(EvalState & state);
};
}
+32 -39
View File
@@ -5,91 +5,84 @@
namespace nix {
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::withExitStatus(unsigned int exitStatus)
EvalErrorBuilder<T> EvalErrorBuilder<T>::withExitStatus(unsigned int exitStatus) &&
{
error.withExitStatus(exitStatus);
return *this;
error->withExitStatus(exitStatus);
return std::move(*this);
}
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::atPos(PosIdx pos)
EvalErrorBuilder<T> EvalErrorBuilder<T>::atPos(PosIdx pos) &&
{
error.err.pos = state.positions[pos];
return *this;
error->err.pos = positions[pos];
return std::move(*this);
}
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::atPos(Value & value, PosIdx fallback)
EvalErrorBuilder<T> EvalErrorBuilder<T>::atPos(Value & value, PosIdx fallback) &&
{
return atPos(value.determinePos(fallback));
return std::move(*this).atPos(value.determinePos(fallback));
}
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::withTrace(PosIdx pos, const std::string_view text)
EvalErrorBuilder<T> EvalErrorBuilder<T>::withTrace(PosIdx pos, const std::string_view text) &&
{
error.err.traces.push_front(
Trace{.pos = state.positions[pos], .hint = HintFmt(std::string(text))});
return *this;
error->err.traces.push_front(
Trace{.pos = positions[pos], .hint = HintFmt(std::string(text))});
return std::move(*this);
}
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::withSuggestions(Suggestions & s)
EvalErrorBuilder<T> EvalErrorBuilder<T>::withSuggestions(Suggestions & s) &&
{
error.err.suggestions = s;
return *this;
error->err.suggestions = s;
return std::move(*this);
}
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::withFrame(const Env & env, const Expr & expr)
EvalErrorBuilder<T> EvalErrorBuilder<T>::withFrame(const Env & env, const Expr & expr) &&
{
if (state.debug) {
error.frame = state.debug->addTrace(DebugTrace{
.pos = state.positions[expr.getPos()],
if (debug) {
error->frame = debug->addTrace(DebugTrace{
.pos = positions[expr.getPos()],
.expr = expr,
.env = env,
.hint = HintFmt("Fake frame for debugging purposes"),
.isError = true
}).entry;
}
return *this;
return std::move(*this);
}
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::addTrace(PosIdx pos, HintFmt hint)
EvalErrorBuilder<T> EvalErrorBuilder<T>::addTrace(PosIdx pos, HintFmt hint) &&
{
error.addTrace(state.positions[pos], hint);
return *this;
error->addTrace(positions[pos], hint);
return std::move(*this);
}
template<class T>
template<typename... Args>
EvalErrorBuilder<T> &
EvalErrorBuilder<T>::addTrace(PosIdx pos, std::string_view formatString, const Args &... formatArgs)
EvalErrorBuilder<T>
EvalErrorBuilder<T>::addTrace(PosIdx pos, std::string_view formatString, const Args &... formatArgs) &&
{
addTrace(state.positions[pos], HintFmt(std::string(formatString), formatArgs...));
return *this;
addTrace(positions[pos], HintFmt(std::string(formatString), formatArgs...));
return std::move(*this);
}
template<class T>
void EvalErrorBuilder<T>::debugThrow()
void EvalErrorBuilder<T>::debugThrow() &&
{
if (state.debug) {
if (auto last = state.debug->traces().next()) {
if (debug) {
if (auto last = debug->traces().next()) {
const Env * env = &(*last)->env;
const Expr * expr = &(*last)->expr;
state.debug->runDebugRepl(state, &error, *env, *expr);
debug->onEvalError(error.get(), *env, *expr);
}
}
// `EvalState` is the only class that can construct an `EvalErrorBuilder`,
// and it does so in dynamic storage. This is the final method called on
// any such instance and must delete itself before throwing the underlying
// error.
auto error = std::move(this->error);
delete this;
throw error;
throw *error;
}
template class EvalErrorBuilder<EvalError>;
+23 -25
View File
@@ -1,12 +1,15 @@
#pragma once
///@file
#include "lix/libutil/box_ptr.hh"
#include "lix/libutil/error.hh"
#include "lix/libutil/types.hh"
#include "lix/libexpr/pos-idx.hh"
#include "lix/libexpr/pos-table.hh"
namespace nix {
struct DebugState;
struct DebugTrace;
struct Env;
struct Expr;
@@ -51,49 +54,44 @@ public:
}
};
/**
* `EvalErrorBuilder`s may only be constructed by `EvalState`. The `debugThrow`
* method must be the final method in any such `EvalErrorBuilder` usage, and it
* handles deleting the object.
*/
template<class T>
class EvalErrorBuilder final
class [[nodiscard]] EvalErrorBuilder final
{
friend class EvalState;
EvalState & state;
const PosTable & positions;
DebugState * debug;
box_ptr<T> error;
public:
template<typename... Args>
explicit EvalErrorBuilder(EvalState & state, const Args &... args)
: state(state), error(T(args...))
explicit EvalErrorBuilder(const PosTable & positions, DebugState * debug, const Args &... args)
: positions(positions)
, debug{debug}
, error(make_box_ptr<T>(args...))
{
}
public:
T error;
[[gnu::noinline]] EvalErrorBuilder<T> withExitStatus(unsigned int exitStatus) &&;
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & withExitStatus(unsigned int exitStatus);
[[gnu::noinline]] EvalErrorBuilder<T> atPos(PosIdx pos) &&;
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & atPos(PosIdx pos);
[[gnu::noinline]] EvalErrorBuilder<T> atPos(Value & value, PosIdx fallback = noPos) &&;
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & atPos(Value & value, PosIdx fallback = noPos);
[[gnu::noinline]] EvalErrorBuilder<T> withTrace(PosIdx pos, const std::string_view text) &&;
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & withTrace(PosIdx pos, const std::string_view text);
[[gnu::noinline]] EvalErrorBuilder<T> withSuggestions(Suggestions & s) &&;
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & withSuggestions(Suggestions & s);
[[gnu::noinline]] EvalErrorBuilder<T> withFrame(const Env & e, const Expr & ex) &&;
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & withFrame(const Env & e, const Expr & ex);
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & addTrace(PosIdx pos, HintFmt hint);
[[gnu::noinline]] EvalErrorBuilder<T> addTrace(PosIdx pos, HintFmt hint) &&;
template<typename... Args>
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> &
addTrace(PosIdx pos, std::string_view formatString, const Args &... formatArgs);
[[gnu::noinline]] EvalErrorBuilder<T>
addTrace(PosIdx pos, std::string_view formatString, const Args &... formatArgs) &&;
/**
* Delete the `EvalErrorBuilder` and throw the underlying exception.
* Throw the underlying exception.
*/
[[gnu::noinline, gnu::noreturn]] void debugThrow();
[[gnu::noinline, gnu::noreturn]] void debugThrow() &&;
};
}
+2 -2
View File
@@ -91,7 +91,7 @@ inline void EvalState::forceAttrs(Value & v, const PosIdx pos, std::string_view
{
forceValue(v, pos);
if (v.type() != nAttrs) {
error<TypeError>(
ctx.errors.make<TypeError>(
"expected a set but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
@@ -105,7 +105,7 @@ inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view e
{
forceValue(v, pos);
if (!v.isList()) {
error<TypeError>(
ctx.errors.make<TypeError>(
"expected a list but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
+381 -319
View File
File diff suppressed because it is too large Load Diff
+254 -180
View File
@@ -4,6 +4,7 @@
#include "lix/libexpr/attr-set.hh"
#include "lix/libexpr/eval-error.hh"
#include "lix/libexpr/gc-alloc.hh"
#include "lix/libutil/box_ptr.hh"
#include "lix/libutil/generator.hh"
#include "lix/libutil/types.hh"
#include "lix/libexpr/value.hh"
@@ -130,8 +131,6 @@ void initLibExpr();
struct RegexCache;
std::shared_ptr<RegexCache> makeRegexCache();
struct DebugTrace {
std::shared_ptr<Pos> pos;
const Expr & expr;
@@ -145,23 +144,29 @@ struct DebugState
{
private:
std::weak_ptr<const DebugTrace> latestTrace;
const PosTable & positions;
const SymbolTable & symbols;
public:
std::function<ReplExitStatus(EvalState & es, ValMap const & extraEnv)> repl;
std::function<ReplExitStatus(ValMap const & extraEnv)> errorCallback;
bool stop = false;
bool inDebugger = false;
std::map<const Expr *, const std::shared_ptr<const StaticEnv>> exprEnvs;
int trylevel = 0;
explicit DebugState(std::function<ReplExitStatus(EvalState & es, ValMap const & extraEnv)> repl)
: repl(repl)
explicit DebugState(
const PosTable & positions,
const SymbolTable & symbols,
std::function<ReplExitStatus(ValMap const & extraEnv)> errorCallback
)
: positions(positions)
, symbols(symbols)
, errorCallback(errorCallback)
{
assert(repl);
assert(errorCallback);
}
void runDebugRepl(
EvalState & evalState, const EvalError * error, const Env & env, const Expr & expr
);
void onEvalError(const EvalError * error, const Env & env, const Expr & expr);
const std::shared_ptr<const StaticEnv> staticEnvFor(const Expr & expr) const
{
@@ -206,7 +211,7 @@ struct StaticSymbols
ignoreNulls, file, line, column, functor, toString, right, wrong, structuredAttrs,
allowedReferences, allowedRequisites, disallowedReferences, disallowedRequisites, maxSize,
maxClosureSize, builder, args, contentAddressed, impure, outputHash, outputHashAlgo,
outputHashMode, recurseForDerivations, description, self, epsilon, startSet, operator_, key,
outputHashMode, recurseForDerivations, description, self, startSet, operator_, key,
path, prefix, outputSpecified;
const Expr::AstSymbols exprSymbols;
@@ -326,89 +331,84 @@ public:
std::optional<Doc> getDoc(Value & v);
};
struct CachedEvalFile;
class EvalState
struct EvalRuntimeCaches
{
friend class EvalBuiltins;
public:
SymbolTable symbols;
PosTable positions;
const StaticSymbols s;
EvalMemory mem;
private:
SearchPath searchPath;
public:
EvalBuiltins builtins;
RootValue vCallFlake;
RootValue vImportedDrvToDerivation;
/**
* If set, force copying files to the Nix store even if they
* already exist there.
* Cache used by prim_match() and other regex functions.
*/
RepairFlag repair;
std::shared_ptr<RegexCache> regexes;
/**
* A cache from path names to values for evalFile().
*/
std::map<SourcePath, std::shared_ptr<CachedEvalFile>> fileEval;
};
struct EvalErrorContext
{
const PosTable & positions;
DebugState * debug;
template<class T, typename... Args>
[[gnu::noinline]]
EvalErrorBuilder<T> make(const Args & ... args) {
return EvalErrorBuilder<T>(positions, debug, args...);
}
};
class EvalPaths
{
ref<Store> store;
/**
* Store used to build stuff.
*/
ref<Store> buildStore;
SearchPath searchPath_;
EvalErrorContext & errors;
public:
EvalPaths(const ref<Store> & store, const ref<Store> buildStore, SearchPath searchPath, EvalErrorContext & errors);
const SearchPath & searchPath() const { return searchPath_; }
private:
struct AllowedPath
{
struct ComponentLess : std::less<>
{
// we'll only use this for string-likes, it's fine. trust me sis.
using is_transparent = void;
};
std::map<std::string, AllowedPath, ComponentLess> children;
bool allowAllChildren = false;
};
/**
* The allowed filesystem paths in restricted or pure evaluation
* mode.
*/
std::optional<PathSet> allowedPaths;
std::optional<AllowedPath> allowedPaths;
/**
* Store used to materialise .drv files.
*/
const ref<Store> store;
/**
* Store used to build stuff.
*/
const ref<Store> buildStore;
RootValue vCallFlake = nullptr;
RootValue vImportedDrvToDerivation = nullptr;
std::unique_ptr<DebugState> debug;
template<class T, typename... Args>
[[nodiscard, gnu::noinline]]
EvalErrorBuilder<T> & error(const Args & ... args) {
// `EvalErrorBuilder::debugThrow` performs the corresponding `delete`.
return *new EvalErrorBuilder<T>(*this, args...);
}
private:
/* Cache for calls to addToStore(); maps source paths to the store
paths. */
std::map<SourcePath, StorePath> srcToStore;
/**
* A cache from path names to values.
*/
using FileEvalCache = GcMap<SourcePath, Value>;
FileEvalCache fileEvalCache;
std::map<std::string, std::optional<std::string>> searchPathResolved;
/**
* Cache used by checkSourcePath().
*/
std::unordered_map<Path, SourcePath> resolvedPaths;
/**
* Cache used by prim_match().
*/
std::shared_ptr<RegexCache> regexCache;
std::unordered_map<Path, CheckedSourcePath> resolvedPaths;
public:
EvalState(
const SearchPath & _searchPath,
ref<Store> store,
std::shared_ptr<Store> buildStore = nullptr);
~EvalState();
/**
* Allow access to a path.
*/
@@ -429,7 +429,12 @@ public:
* Check whether access to a path is allowed and throw an error if
* not. Otherwise return the canonicalised path.
*/
SourcePath checkSourcePath(const SourcePath & path);
CheckedSourcePath checkSourcePath(const SourcePath & path);
/**
* If `path` refers to a directory, then append "/default.nix".
*/
CheckedSourcePath resolveExprPath(SourcePath path);
void checkURI(const std::string & uri);
@@ -444,11 +449,108 @@ public:
*/
Path toRealPath(const Path & path, const NixStringContext & context);
/**
* Look up a file in the search path.
*/
SourcePath findFile(const std::string_view path);
SourcePath findFile(const SearchPath & searchPath, const std::string_view path, const PosIdx pos = noPos);
/**
* Try to resolve a search path value (not the optinal key part)
*
* If the specified search path element is a URI, download it.
*
* If it is not found, return `std::nullopt`
*/
std::optional<std::string> resolveSearchPathPath(const SearchPath::Path & path);
StorePath copyPathToStore(
NixStringContext & context, const SourcePath & path, RepairFlag repair = NoRepair
);
/**
* Create a string representing a store path.
*
* The string is the printed store path with a context containing a
* single `NixStringContextElem::Opaque` element of that store path.
*/
void mkStorePathString(const StorePath & storePath, Value & v);
/**
* Realise the given context, and return a mapping from the placeholders
* used to construct the associated value to their final store path
*/
[[nodiscard]] StringMap realiseContext(const NixStringContext & context);
};
struct EvalStatistics
{
unsigned long nrLookups = 0;
unsigned long nrAvoided = 0;
unsigned long nrOpUpdates = 0;
unsigned long nrOpUpdateValuesCopied = 0;
unsigned long nrListConcats = 0;
unsigned long nrPrimOpCalls = 0;
unsigned long nrFunctionCalls = 0;
unsigned long nrThunks = 0;
bool countCalls = false;
std::map<std::string, size_t> primOpCalls;
std::map<ExprLambda *, size_t> functionCalls;
std::map<PosIdx, size_t> attrSelects;
void addCall(ExprLambda & fun);
};
class Evaluator
{
friend class EvalBuiltins;
friend class EvalState;
EvalState * activeEval = nullptr;
public:
SymbolTable symbols;
PosTable positions;
const StaticSymbols s;
EvalMemory mem;
EvalRuntimeCaches caches;
EvalPaths paths;
EvalBuiltins builtins;
EvalStatistics stats;
/**
* If set, force copying files to the Nix store even if they
* already exist there.
*/
RepairFlag repair;
/**
* Store used to materialise .drv files.
*/
const ref<Store> store;
std::unique_ptr<DebugState> debug;
EvalErrorContext errors;
Evaluator(
const SearchPath & _searchPath,
ref<Store> store,
std::shared_ptr<Store> buildStore = nullptr,
std::function<ReplExitStatus(EvalState & es, ValMap const & extraEnv)> debugRepl = nullptr
);
Evaluator(const Evaluator &) = delete;
Evaluator(Evaluator &&) = delete;
Evaluator & operator=(const Evaluator &) = delete;
Evaluator & operator=(Evaluator &&) = delete;
/**
* Parse a Nix expression from the specified file.
*/
Expr & parseExprFromFile(const SourcePath & path);
Expr & parseExprFromFile(const SourcePath & path, std::shared_ptr<StaticEnv> & staticEnv);
Expr & parseExprFromFile(const CheckedSourcePath & path);
Expr & parseExprFromFile(const CheckedSourcePath & path, std::shared_ptr<StaticEnv> & staticEnv);
/**
* Parse a Nix expression from the specified string.
@@ -467,6 +569,84 @@ public:
Expr & parseStdin();
/**
* Creates a thunk that will evaluate the given expression when forced.
*/
void evalLazily(Expr & e, Value & v);
private:
Expr * parse(
char * text,
size_t length,
Pos::Origin origin,
const SourcePath & basePath,
std::shared_ptr<StaticEnv> & staticEnv,
const FeatureSettings & xpSettings = featureSettings);
public:
BindingsBuilder buildBindings(size_t capacity)
{
return mem.buildBindings(symbols, capacity);
}
/**
* Print statistics, if enabled.
*
* Performs a full memory GC before printing the statistics, so that the
* GC statistics are more accurate.
*/
void maybePrintStats();
/**
* Print statistics, unconditionally, cheaply, without performing a GC first.
*/
void printStatistics();
/**
* Perform a full memory garbage collection - not incremental.
*
* @return true if Nix was built with GC and a GC was performed, false if not.
* The return value is currently not thread safe - just the return value.
*/
bool fullGC();
/**
* Create an `EvalState` in prepation to evaluate some amount of Nix code.
*
* While preparation of evaluation can be done with Evaluator itself only,
* actually evaluating things requires an EvalState. This function creates
* an EvalState and returns it. At most one EvalState per Evaluator may be
* live at any given point, and references to this EvalState must not live
* anywhere except in the returned box, local variables, or arguments. Any
* reference held in an object type is illegal, be it in a lambda capture,
* a pointer member of an object, a hidden member such as arguments passed
* to functions by `std::thread` or `std::async`—all references held where
* they could be copied are moved from are disallowed. EvalState is thus a
* witness type that a given thread may evaluate nix code and must *never*
* be run inside `kj::Promise` context. This is due to a kj limitation, in
* which it is not possible to block on a promise while already running in
* a promise without doing this blocking on a different event loop/thread.
*/
box_ptr<EvalState> begin();
};
class EvalState
{
friend class Evaluator;
explicit EvalState(Evaluator & ctx);
public:
Evaluator & ctx;
EvalState(const EvalState &) = delete;
EvalState(EvalState &&) = delete;
EvalState & operator=(const EvalState &) = delete;
EvalState & operator=(EvalState &&) = delete;
~EvalState();
/**
* Evaluate an expression read from the given file to normal form.
*/
@@ -474,21 +654,6 @@ public:
void resetFileCache();
/**
* Look up a file in the search path.
*/
SourcePath findFile(const std::string_view path);
SourcePath findFile(const SearchPath & searchPath, const std::string_view path, const PosIdx pos = noPos);
/**
* Try to resolve a search path value (not the optinal key part)
*
* If the specified search path element is a URI, download it.
*
* If it is not found, return `std::nullopt`
*/
std::optional<std::string> resolveSearchPathPath(const SearchPath::Path & path);
/**
* Evaluate an expression to normal form
*
@@ -560,8 +725,6 @@ public:
bool coerceMore = false, bool copyToStore = true,
bool canonicalizePath = true);
StorePath copyPathToStore(NixStringContext & context, const SourcePath & path);
/**
* Path coercion.
*
@@ -603,17 +766,9 @@ private:
inline Value * lookupVar(Env * env, const ExprVar & var, bool noEval);
friend struct ExprVar;
friend struct ExprAttrs;
friend struct ExprSet;
friend struct ExprLet;
Expr * parse(
char * text,
size_t length,
Pos::Origin origin,
const SourcePath & basePath,
std::shared_ptr<StaticEnv> & staticEnv,
const FeatureSettings & xpSettings = featureSettings);
/**
* Current Nix call stack depth, used with `max-call-depth` setting to throw stack overflow hopefully before we run out of system stack.
*/
@@ -644,22 +799,8 @@ public:
*/
void autoCallFunction(Bindings & args, Value & fun, Value & res);
BindingsBuilder buildBindings(size_t capacity)
{
return mem.buildBindings(symbols, capacity);
}
void mkThunk_(Value & v, Expr & expr);
void mkPos(Value & v, PosIdx pos);
/**
* Create a string representing a store path.
*
* The string is the printed store path with a context containing a
* single `NixStringContextElem::Opaque` element of that store path.
*/
void mkStorePathString(const StorePath & storePath, Value & v);
/**
* Create a string representing a `SingleDerivedPath::Built`.
*
@@ -697,33 +838,6 @@ public:
void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx);
/**
* Print statistics, if enabled.
*
* Performs a full memory GC before printing the statistics, so that the
* GC statistics are more accurate.
*/
void maybePrintStats();
/**
* Print statistics, unconditionally, cheaply, without performing a GC first.
*/
void printStatistics();
/**
* Perform a full memory garbage collection - not incremental.
*
* @return true if Nix was built with GC and a GC was performed, false if not.
* The return value is currently not thread safe - just the return value.
*/
bool fullGC();
/**
* Realise the given context, and return a mapping from the placeholders
* used to construct the associated value to their final store path
*/
[[nodiscard]] StringMap realiseContext(const NixStringContext & context);
private:
/**
@@ -741,41 +855,6 @@ private:
*/
std::string mkSingleDerivedPathStringRaw(
const SingleDerivedPath & p);
unsigned long nrLookups = 0;
unsigned long nrAvoided = 0;
unsigned long nrOpUpdates = 0;
unsigned long nrOpUpdateValuesCopied = 0;
unsigned long nrListConcats = 0;
unsigned long nrPrimOpCalls = 0;
unsigned long nrFunctionCalls = 0;
bool countCalls;
using PrimOpCalls = std::map<std::string, size_t>;
PrimOpCalls primOpCalls;
using FunctionCalls = std::map<ExprLambda *, size_t>;
FunctionCalls functionCalls;
void incrFunctionCall(ExprLambda * fun);
using AttrSelects = std::map<PosIdx, size_t>;
AttrSelects attrSelects;
friend struct ExprOpUpdate;
friend struct ExprOpConcatLists;
friend struct ExprVar;
friend struct ExprString;
friend struct ExprInt;
friend struct ExprFloat;
friend struct ExprPath;
friend struct ExprSelect;
friend void prim_getAttr(EvalState & state, const PosIdx pos, Value * * args, Value & v);
friend void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v);
friend void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v);
friend struct Value;
};
/**
@@ -787,11 +866,6 @@ private:
std::string_view showType(ValueType type, bool withArticle = true);
std::string showType(const Value & v);
/**
* If `path` refers to a directory, then append "/default.nix".
*/
SourcePath resolveExprPath(SourcePath path);
static constexpr std::string_view corepkgsPrefix{"/__corepkgs__/"};
+65 -65
View File
@@ -37,7 +37,7 @@ static std::optional<FetchedFlake> lookupInFlakeCache(
}
static std::tuple<fetchers::Tree, FlakeRef, FlakeRef> fetchOrSubstituteTree(
EvalState & state,
Evaluator & state,
const FlakeRef & originalRef,
bool allowLookup,
FlakeCache & flakeCache)
@@ -68,7 +68,7 @@ static std::tuple<fetchers::Tree, FlakeRef, FlakeRef> fetchOrSubstituteTree(
debug("got tree '%s' from '%s'",
state.store->printStorePath(tree.storePath), lockedRef);
state.allowPath(tree.storePath);
state.paths.allowPath(tree.storePath);
assert(!originalRef.input.getNarHash() || tree.storePath == originalRef.input.computeStorePath(*state.store));
@@ -88,7 +88,7 @@ static void expectType(EvalState & state, ValueType type,
forceTrivialValue(state, value, pos);
if (value.type() != type)
throw Error("expected %s but got %s at %s",
showType(type), showType(value.type()), state.positions[pos]);
showType(type), showType(value.type()), state.ctx.positions[pos]);
}
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
@@ -103,10 +103,10 @@ static FlakeInput parseFlakeInput(EvalState & state,
FlakeInput input;
auto sInputs = state.symbols.create("inputs");
auto sUrl = state.symbols.create("url");
auto sFlake = state.symbols.create("flake");
auto sFollows = state.symbols.create("follows");
auto sInputs = state.ctx.symbols.create("inputs");
auto sUrl = state.ctx.symbols.create("url");
auto sFlake = state.ctx.symbols.create("flake");
auto sFollows = state.ctx.symbols.create("follows");
fetchers::Attrs attrs;
std::optional<std::string> url;
@@ -133,31 +133,31 @@ static FlakeInput parseFlakeInput(EvalState & state,
#pragma GCC diagnostic ignored "-Wswitch-enum"
switch (attr.value->type()) {
case nString:
attrs.emplace(state.symbols[attr.name], attr.value->string.s);
attrs.emplace(state.ctx.symbols[attr.name], attr.value->string.s);
break;
case nBool:
attrs.emplace(state.symbols[attr.name], Explicit<bool> { attr.value->boolean });
attrs.emplace(state.ctx.symbols[attr.name], Explicit<bool> { attr.value->boolean });
break;
case nInt: {
auto intValue = attr.value->integer.value;
if (intValue < 0) {
state.error<EvalError>("negative value given for flake input attribute %1%: %2%", state.symbols[attr.name], intValue).debugThrow();
state.ctx.errors.make<EvalError>("negative value given for flake input attribute %1%: %2%", state.ctx.symbols[attr.name], intValue).debugThrow();
}
uint64_t asUnsigned = intValue;
attrs.emplace(state.symbols[attr.name], asUnsigned);
attrs.emplace(state.ctx.symbols[attr.name], asUnsigned);
break;
}
default:
state.error<TypeError>("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
state.symbols[attr.name], showType(*attr.value)).debugThrow();
state.ctx.errors.make<TypeError>("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
state.ctx.symbols[attr.name], showType(*attr.value)).debugThrow();
}
#pragma GCC diagnostic pop
}
} catch (Error & e) {
e.addTrace(
state.positions[attr.pos],
HintFmt("while evaluating flake attribute '%s'", state.symbols[attr.name]));
state.ctx.positions[attr.pos],
HintFmt("while evaluating flake attribute '%s'", state.ctx.symbols[attr.name]));
throw;
}
}
@@ -166,13 +166,13 @@ static FlakeInput parseFlakeInput(EvalState & state,
try {
input.ref = FlakeRef::fromAttrs(attrs);
} catch (Error & e) {
e.addTrace(state.positions[pos], HintFmt("while evaluating flake input"));
e.addTrace(state.ctx.positions[pos], HintFmt("while evaluating flake input"));
throw;
}
else {
attrs.erase("url");
if (!attrs.empty())
throw Error("unexpected flake input attribute '%s', at %s", attrs.begin()->first, state.positions[pos]);
throw Error("unexpected flake input attribute '%s', at %s", attrs.begin()->first, state.ctx.positions[pos]);
if (url)
input.ref = parseFlakeRef(*url, baseDir, true, input.isFlake);
}
@@ -196,9 +196,9 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs(
expectType(state, nAttrs, *value, pos);
for (nix::Attr & inputAttr : *(*value).attrs) {
inputs.emplace(state.symbols[inputAttr.name],
inputs.emplace(state.ctx.symbols[inputAttr.name],
parseFlakeInput(state,
state.symbols[inputAttr.name],
state.ctx.symbols[inputAttr.name],
inputAttr.value,
inputAttr.pos,
baseDir,
@@ -217,7 +217,7 @@ static Flake getFlake(
InputPath lockRootPath)
{
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
state, originalRef, allowLookup, flakeCache);
state.ctx, originalRef, allowLookup, flakeCache);
// We need to guard against symlink attacks, but before we start doing
// filesystem operations we should make sure there's a flake.nix in the
@@ -232,7 +232,7 @@ static Flake getFlake(
auto flakeFile = canonPath(flakeDir + "/flake.nix", true);
if (!isInDir(flakeFile, sourceInfo.actualPath))
throw Error("'flake.nix' file of flake '%s' escapes from '%s'",
lockedRef, state.store->printStorePath(sourceInfo.storePath));
lockedRef, state.ctx.store->printStorePath(sourceInfo.storePath));
Flake flake {
.originalRef = originalRef,
@@ -242,35 +242,35 @@ static Flake getFlake(
};
// FIXME: symlink attack
auto resolvedFlakeFile = resolveExprPath(state.checkSourcePath(CanonPath(flakeFile)));
Expr & flakeExpr = state.parseExprFromFile(state.checkSourcePath(resolvedFlakeFile));
auto resolvedFlakeFile = state.ctx.paths.resolveExprPath(CanonPath(flakeFile));
Expr & flakeExpr = state.ctx.parseExprFromFile(resolvedFlakeFile);
// Enforce that 'flake.nix' is a direct attrset, not a computation.
if (!(dynamic_cast<ExprAttrs *>(&flakeExpr))) {
state.error<EvalError>("file '%s' must be an attribute set", resolvedFlakeFile).debugThrow();
state.ctx.errors.make<EvalError>("file '%s' must be an attribute set", resolvedFlakeFile).debugThrow();
}
Value vInfo;
state.eval(flakeExpr, vInfo);
if (auto description = vInfo.attrs->get(state.s.description)) {
if (auto description = vInfo.attrs->get(state.ctx.s.description)) {
expectType(state, nString, *description->value, description->pos);
flake.description = description->value->string.s;
}
auto sInputs = state.symbols.create("inputs");
auto sInputs = state.ctx.symbols.create("inputs");
if (auto inputs = vInfo.attrs->get(sInputs))
flake.inputs = parseFlakeInputs(state, inputs->value, inputs->pos, flakeDir, lockRootPath, 0);
if (auto outputs = vInfo.attrs->get(state.s.outputs)) {
if (auto outputs = vInfo.attrs->get(state.ctx.s.outputs)) {
expectType(state, nFunction, *outputs->value, outputs->pos);
if (outputs->value->isLambda() && outputs->value->lambda.fun->hasFormals()) {
for (auto & formal : outputs->value->lambda.fun->formals->formals) {
if (formal.name != state.s.self)
flake.inputs.emplace(state.symbols[formal.name], FlakeInput {
.ref = parseFlakeRef(state.symbols[formal.name])
if (formal.name != state.ctx.s.self)
flake.inputs.emplace(state.ctx.symbols[formal.name], FlakeInput {
.ref = parseFlakeRef(state.ctx.symbols[formal.name])
});
}
}
@@ -278,7 +278,7 @@ static Flake getFlake(
} else
throw Error("flake '%s' lacks attribute 'outputs'", lockedRef);
auto sNixConfig = state.symbols.create("nixConfig");
auto sNixConfig = state.ctx.symbols.create("nixConfig");
if (auto nixConfig = vInfo.attrs->get(sNixConfig)) {
expectType(state, nAttrs, *nixConfig->value, nixConfig->pos);
@@ -287,45 +287,45 @@ static Flake getFlake(
forceTrivialValue(state, *setting.value, setting.pos);
if (setting.value->type() == nString)
flake.config.settings.emplace(
state.symbols[setting.name],
state.ctx.symbols[setting.name],
std::string(state.forceStringNoCtx(*setting.value, setting.pos, "")));
else if (setting.value->type() == nPath) {
NixStringContext emptyContext = {};
flake.config.settings.emplace(
state.symbols[setting.name],
state.ctx.symbols[setting.name],
state.coerceToString(setting.pos, *setting.value, emptyContext, "", false, true, true) .toOwned());
}
else if (setting.value->type() == nInt)
flake.config.settings.emplace(
state.symbols[setting.name],
state.ctx.symbols[setting.name],
state.forceInt(*setting.value, setting.pos, "").value);
else if (setting.value->type() == nBool)
flake.config.settings.emplace(
state.symbols[setting.name],
state.ctx.symbols[setting.name],
Explicit<bool> { state.forceBool(*setting.value, setting.pos, "") });
else if (setting.value->type() == nList) {
std::vector<std::string> ss;
for (auto elem : setting.value->listItems()) {
if (elem->type() != nString)
state.error<TypeError>("list element in flake configuration setting '%s' is %s while a string is expected",
state.symbols[setting.name], showType(*setting.value)).debugThrow();
state.ctx.errors.make<TypeError>("list element in flake configuration setting '%s' is %s while a string is expected",
state.ctx.symbols[setting.name], showType(*setting.value)).debugThrow();
ss.emplace_back(state.forceStringNoCtx(*elem, setting.pos, ""));
}
flake.config.settings.emplace(state.symbols[setting.name], ss);
flake.config.settings.emplace(state.ctx.symbols[setting.name], ss);
}
else
state.error<TypeError>("flake configuration setting '%s' is %s",
state.symbols[setting.name], showType(*setting.value)).debugThrow();
state.ctx.errors.make<TypeError>("flake configuration setting '%s' is %s",
state.ctx.symbols[setting.name], showType(*setting.value)).debugThrow();
}
}
for (auto & attr : *vInfo.attrs) {
if (attr.name != state.s.description &&
if (attr.name != state.ctx.s.description &&
attr.name != sInputs &&
attr.name != state.s.outputs &&
attr.name != state.ctx.s.outputs &&
attr.name != sNixConfig)
throw Error("flake '%s' has an unsupported attribute '%s', at %s",
lockedRef, state.symbols[attr.name], state.positions[attr.pos]);
lockedRef, state.ctx.symbols[attr.name], state.ctx.positions[attr.pos]);
}
return flake;
@@ -385,7 +385,7 @@ LockedFlake lockFlake(
if (lockFlags.applyNixConfig) {
flake.config.apply();
state.store->setOptions();
state.ctx.store->setOptions();
}
try {
@@ -634,7 +634,7 @@ LockedFlake lockFlake(
else {
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
state, *input.ref, useRegistries, flakeCache);
state.ctx, *input.ref, useRegistries, flakeCache);
auto childNode = make_ref<LockedNode>(lockedRef, ref, false);
@@ -773,16 +773,16 @@ void callFlake(EvalState & state,
const LockedFlake & lockedFlake,
Value & vRes)
{
auto vLocks = state.mem.allocValue();
auto vRootSrc = state.mem.allocValue();
auto vRootSubdir = state.mem.allocValue();
auto vTmp1 = state.mem.allocValue();
auto vTmp2 = state.mem.allocValue();
auto vLocks = state.ctx.mem.allocValue();
auto vRootSrc = state.ctx.mem.allocValue();
auto vRootSubdir = state.ctx.mem.allocValue();
auto vTmp1 = state.ctx.mem.allocValue();
auto vTmp2 = state.ctx.mem.allocValue();
vLocks->mkString(lockedFlake.lockFile.to_string());
emitTreeAttrs(
state,
state.ctx,
*lockedFlake.flake.sourceInfo,
lockedFlake.flake.lockedRef.input,
*vRootSrc,
@@ -791,14 +791,14 @@ void callFlake(EvalState & state,
vRootSubdir->mkString(lockedFlake.flake.lockedRef.subdir);
if (!state.vCallFlake) {
state.vCallFlake = allocRootValue(state.mem.allocValue());
state.eval(state.parseExprFromString(
if (!state.ctx.caches.vCallFlake) {
state.ctx.caches.vCallFlake = allocRootValue(state.ctx.mem.allocValue());
state.eval(state.ctx.parseExprFromString(
#include "call-flake.nix.gen.hh"
, CanonPath::root), **state.vCallFlake);
, CanonPath::root), **state.ctx.caches.vCallFlake);
}
state.callFunction(**state.vCallFlake, *vLocks, *vTmp1, noPos);
state.callFunction(**state.ctx.caches.vCallFlake, *vLocks, *vTmp1, noPos);
state.callFunction(*vTmp1, *vRootSrc, *vTmp2, noPos);
state.callFunction(*vTmp2, *vRootSubdir, vRes, noPos);
}
@@ -808,7 +808,7 @@ void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, Value &
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake"));
auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
if (evalSettings.pureEval && !flakeRef.input.isLocked())
throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, state.positions[pos]);
throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, state.ctx.positions[pos]);
callFlake(state,
lockFlake(state, flakeRef,
@@ -830,9 +830,9 @@ void prim_parseFlakeRef(
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos,
"while evaluating the argument passed to builtins.parseFlakeRef"));
auto attrs = parseFlakeRef(flakeRefS, {}, true).toAttrs();
auto binds = state.buildBindings(attrs.size());
auto binds = state.ctx.buildBindings(attrs.size());
for (const auto & [key, value] : attrs) {
auto s = state.symbols.create(key);
auto s = state.ctx.symbols.create(key);
auto & vv = binds.alloc(s);
std::visit(overloaded {
[&vv](const std::string & value) { vv.mkString(value); },
@@ -858,22 +858,22 @@ void prim_flakeRefToString(
auto intValue = attr.value->integer.value;
if (intValue < 0) {
state.error<EvalError>("negative value given for flake ref attr %1%: %2%", state.symbols[attr.name], intValue).atPos(pos).debugThrow();
state.ctx.errors.make<EvalError>("negative value given for flake ref attr %1%: %2%", state.ctx.symbols[attr.name], intValue).atPos(pos).debugThrow();
}
uint64_t asUnsigned = intValue;
attrs.emplace(state.symbols[attr.name], asUnsigned);
attrs.emplace(state.ctx.symbols[attr.name], asUnsigned);
} else if (t == nBool) {
attrs.emplace(state.symbols[attr.name],
attrs.emplace(state.ctx.symbols[attr.name],
Explicit<bool> { attr.value->boolean });
} else if (t == nString) {
attrs.emplace(state.symbols[attr.name],
attrs.emplace(state.ctx.symbols[attr.name],
std::string(attr.value->str()));
} else {
state.error<EvalError>(
state.ctx.errors.make<EvalError>(
"flake reference attribute sets may only contain integers, Booleans, "
"and strings, but attribute '%s' is %s",
state.symbols[attr.name],
state.ctx.symbols[attr.name],
showType(*attr.value)).debugThrow();
}
}
+2 -1
View File
@@ -1,6 +1,7 @@
#pragma once
///@file
#include "lix/libexpr/eval.hh"
#include "lix/libutil/types.hh"
#include "lix/libexpr/flake/flakeref.hh"
#include "lix/libexpr/flake/lockfile.hh"
@@ -192,7 +193,7 @@ void callFlake(
}
void emitTreeAttrs(
EvalState & state,
Evaluator & state,
const fetchers::Tree & tree,
const fetchers::Input & input,
Value & v,
+12 -2
View File
@@ -1,5 +1,7 @@
#include "lix/libexpr/flake/lockfile.hh"
#include "json-to-value.hh"
#include "lix/libstore/store-api.hh"
#include "lix/libutil/error.hh"
#include "lix/libutil/url-parts.hh"
#include <iomanip>
@@ -186,8 +188,16 @@ std::string LockFile::to_string() const
LockFile LockFile::read(const Path & path)
{
if (!pathExists(path)) return LockFile();
return LockFile(nlohmann::json::parse(readFile(path)), path);
if (!pathExists(path)) {
return LockFile();
}
try {
return LockFile(nlohmann::json::parse(readFile(path)), path);
} catch (nlohmann::json::parse_error &nlohmann_json_parse_exc) {
auto json_parse_error = JSONParseError(nlohmann_json_parse_exc.what());
json_parse_error.addTrace(nullptr, "while parsing the lock file at %s", path);
throw json_parse_error;
}
}
std::ostream & operator <<(std::ostream & stream, const LockFile & lockFile)
+72 -87
View File
@@ -12,14 +12,14 @@
namespace nix {
DrvInfo::DrvInfo(EvalState & state, std::string attrPath, Bindings * attrs)
: state(&state), attrs(attrs), attrPath(std::move(attrPath))
DrvInfo::DrvInfo(std::string attrPath, Bindings * attrs)
: attrs(attrs), attrPath(std::move(attrPath))
{
}
DrvInfo::DrvInfo(EvalState & state, ref<Store> store, const std::string & drvPathWithOutputs)
: state(&state), attrs(nullptr), attrPath("")
DrvInfo::DrvInfo(ref<Store> store, const std::string & drvPathWithOutputs)
: attrs(nullptr), attrPath("")
{
auto [drvPath, selectedOutputs] = parsePathWithOutputs(*store, drvPathWithOutputs);
@@ -46,68 +46,68 @@ DrvInfo::DrvInfo(EvalState & state, ref<Store> store, const std::string & drvPat
}
std::string DrvInfo::queryName()
std::string DrvInfo::queryName(EvalState & state)
{
if (name == "" && attrs) {
auto i = attrs->find(state->s.name);
if (i == attrs->end()) state->error<TypeError>("derivation name missing").debugThrow();
name = state->forceStringNoCtx(*i->value, noPos, "while evaluating the 'name' attribute of a derivation");
auto i = attrs->find(state.ctx.s.name);
if (i == attrs->end()) state.ctx.errors.make<TypeError>("derivation name missing").debugThrow();
name = state.forceStringNoCtx(*i->value, noPos, "while evaluating the 'name' attribute of a derivation");
}
return name;
}
std::string DrvInfo::querySystem()
std::string DrvInfo::querySystem(EvalState & state)
{
if (system == "" && attrs) {
auto i = attrs->find(state->s.system);
system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, i->pos, "while evaluating the 'system' attribute of a derivation");
auto i = attrs->find(state.ctx.s.system);
system = i == attrs->end() ? "unknown" : state.forceStringNoCtx(*i->value, i->pos, "while evaluating the 'system' attribute of a derivation");
}
return system;
}
std::optional<StorePath> DrvInfo::queryDrvPath()
std::optional<StorePath> DrvInfo::queryDrvPath(EvalState & state)
{
if (!drvPath && attrs) {
Bindings::iterator i = attrs->find(state->s.drvPath);
Bindings::iterator i = attrs->find(state.ctx.s.drvPath);
NixStringContext context;
if (i == attrs->end())
drvPath = {std::nullopt};
else
drvPath = {state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the 'drvPath' attribute of a derivation")};
drvPath = {state.coerceToStorePath(i->pos, *i->value, context, "while evaluating the 'drvPath' attribute of a derivation")};
}
return drvPath.value_or(std::nullopt);
}
StorePath DrvInfo::requireDrvPath()
StorePath DrvInfo::requireDrvPath(EvalState & state)
{
if (auto drvPath = queryDrvPath())
if (auto drvPath = queryDrvPath(state))
return *drvPath;
throw Error("derivation does not contain a 'drvPath' attribute");
}
StorePath DrvInfo::queryOutPath()
StorePath DrvInfo::queryOutPath(EvalState & state)
{
if (!outPath && attrs) {
Bindings::iterator i = attrs->find(state->s.outPath);
Bindings::iterator i = attrs->find(state.ctx.s.outPath);
NixStringContext context;
if (i != attrs->end())
outPath = state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the output path of a derivation");
outPath = state.coerceToStorePath(i->pos, *i->value, context, "while evaluating the output path of a derivation");
}
if (!outPath)
throw UnimplementedError("CA derivations are not yet supported");
return *outPath;
}
void DrvInfo::fillOutputs(bool withPaths)
void DrvInfo::fillOutputs(EvalState & state, bool withPaths)
{
auto fillDefault = [&]() {
std::optional<StorePath> outPath = std::nullopt;
if (withPaths) {
outPath.emplace(this->queryOutPath());
outPath.emplace(this->queryOutPath(state));
}
this->outputs.emplace("out", outPath);
};
@@ -118,14 +118,14 @@ void DrvInfo::fillOutputs(bool withPaths)
return;
}
Attr * outputs = this->attrs->get(this->state->s.outputs);
Attr * outputs = this->attrs->get(state.ctx.s.outputs);
if (outputs == nullptr) {
fillDefault();
return;
}
// NOTE(Qyriad): I don't think there is any codepath that can cause this to error.
this->state->forceList(
state.forceList(
*outputs->value,
outputs->pos,
"while evaluating the 'outputs' attribute of a derivation"
@@ -138,7 +138,7 @@ void DrvInfo::fillOutputs(bool withPaths)
// it is only possible by overriding a derivation attrset already created by
// one of those with `//` to introduce the failing `outputs` entry.
auto errMsg = fmt("while evaluating output %d of a derivation", idx);
std::string_view outputName = state->forceStringNoCtx(
std::string_view outputName = state.forceStringNoCtx(
*elem,
outputs->pos,
errMsg
@@ -146,7 +146,7 @@ void DrvInfo::fillOutputs(bool withPaths)
if (withPaths) {
// Find the attr with this output's name...
Attr * out = this->attrs->get(this->state->symbols.create(outputName));
Attr * out = this->attrs->get(state.ctx.symbols.create(outputName));
if (out == nullptr) {
// FIXME: throw error?
continue;
@@ -154,10 +154,10 @@ void DrvInfo::fillOutputs(bool withPaths)
// Meanwhile we couldn't figure out any circumstances
// that cause this to error.
state->forceAttrs(*out->value, outputs->pos, errMsg);
state.forceAttrs(*out->value, outputs->pos, errMsg);
// ...and evaluate its `outPath` attribute.
Attr * outPath = out->value->attrs->get(this->state->s.outPath);
Attr * outPath = out->value->attrs->get(state.ctx.s.outPath);
if (outPath == nullptr) {
continue;
// FIXME: throw error?
@@ -166,7 +166,7 @@ void DrvInfo::fillOutputs(bool withPaths)
NixStringContext context;
// And idk what could possibly cause this one to error
// that wouldn't error before here.
auto storePath = state->coerceToStorePath(
auto storePath = state.coerceToStorePath(
outPath->pos,
*outPath->value,
context,
@@ -179,14 +179,14 @@ void DrvInfo::fillOutputs(bool withPaths)
}
}
DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall)
DrvInfo::Outputs DrvInfo::queryOutputs(EvalState & state, bool withPaths, bool onlyOutputsToInstall)
{
// If we haven't already cached the outputs set, then do so now.
if (outputs.empty()) {
// FIXME: this behavior seems kind of busted, since whether or not this
// DrvInfo will have paths is forever determined by the *first* call to
// this function??
fillOutputs(withPaths);
fillOutputs(state, withPaths);
}
// Things that operate on derivations like packages, like `nix-env` and `nix build`,
@@ -201,23 +201,23 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall
// output by its attribute, e.g. `pkgs.lix.dev`, which (lol?) sets the magic
// attribute `outputSpecified = true`, and changes the `outputName` attr to the
// explicitly selected-into output.
if (Attr * outSpecAttr = attrs->get(state->s.outputSpecified)) {
bool outputSpecified = this->state->forceBool(
if (Attr * outSpecAttr = attrs->get(state.ctx.s.outputSpecified)) {
bool outputSpecified = state.forceBool(
*outSpecAttr->value,
outSpecAttr->pos,
"while evaluating the 'outputSpecified' attribute of a derivation"
);
if (outputSpecified) {
auto maybeOut = outputs.find(queryOutputName());
auto maybeOut = outputs.find(queryOutputName(state));
if (maybeOut == outputs.end()) {
throw Error("derivation does not have output '%s'", queryOutputName());
throw Error("derivation does not have output '%s'", queryOutputName(state));
}
return Outputs{*maybeOut};
}
}
/* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */
const Value * outTI = queryMeta("outputsToInstall");
const Value * outTI = queryMeta(state, "outputsToInstall");
if (!outTI) return outputs;
auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'");
/* ^ this shows during `nix-env -i` right under the bad derivation */
@@ -233,51 +233,51 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall
}
std::string DrvInfo::queryOutputName()
std::string DrvInfo::queryOutputName(EvalState & state)
{
if (outputName == "" && attrs) {
Bindings::iterator i = attrs->find(state->s.outputName);
outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value, noPos, "while evaluating the output name of a derivation") : "";
Bindings::iterator i = attrs->find(state.ctx.s.outputName);
outputName = i != attrs->end() ? state.forceStringNoCtx(*i->value, noPos, "while evaluating the output name of a derivation") : "";
}
return outputName;
}
Bindings * DrvInfo::getMeta()
Bindings * DrvInfo::getMeta(EvalState & state)
{
if (meta) return meta;
if (!attrs) return 0;
Bindings::iterator a = attrs->find(state->s.meta);
Bindings::iterator a = attrs->find(state.ctx.s.meta);
if (a == attrs->end()) return 0;
state->forceAttrs(*a->value, a->pos, "while evaluating the 'meta' attribute of a derivation");
state.forceAttrs(*a->value, a->pos, "while evaluating the 'meta' attribute of a derivation");
meta = a->value->attrs;
return meta;
}
StringSet DrvInfo::queryMetaNames()
StringSet DrvInfo::queryMetaNames(EvalState & state)
{
StringSet res;
if (!getMeta()) return res;
if (!getMeta(state)) return res;
for (auto & i : *meta)
res.emplace(state->symbols[i.name]);
res.emplace(state.ctx.symbols[i.name]);
return res;
}
bool DrvInfo::checkMeta(Value & v)
bool DrvInfo::checkMeta(EvalState & state, Value & v)
{
state->forceValue(v, v.determinePos(noPos));
state.forceValue(v, v.determinePos(noPos));
if (v.type() == nList) {
for (auto elem : v.listItems())
if (!checkMeta(*elem)) return false;
if (!checkMeta(state, *elem)) return false;
return true;
}
else if (v.type() == nAttrs) {
Bindings::iterator i = v.attrs->find(state->s.outPath);
Bindings::iterator i = v.attrs->find(state.ctx.s.outPath);
if (i != v.attrs->end()) return false;
for (auto & i : *v.attrs)
if (!checkMeta(*i.value)) return false;
if (!checkMeta(state, *i.value)) return false;
return true;
}
else return v.type() == nInt || v.type() == nBool || v.type() == nString ||
@@ -285,26 +285,26 @@ bool DrvInfo::checkMeta(Value & v)
}
Value * DrvInfo::queryMeta(const std::string & name)
Value * DrvInfo::queryMeta(EvalState & state, const std::string & name)
{
if (!getMeta()) return 0;
Bindings::iterator a = meta->find(state->symbols.create(name));
if (a == meta->end() || !checkMeta(*a->value)) return 0;
if (!getMeta(state)) return 0;
Bindings::iterator a = meta->find(state.ctx.symbols.create(name));
if (a == meta->end() || !checkMeta(state, *a->value)) return 0;
return a->value;
}
std::string DrvInfo::queryMetaString(const std::string & name)
std::string DrvInfo::queryMetaString(EvalState & state, const std::string & name)
{
Value * v = queryMeta(name);
Value * v = queryMeta(state, name);
if (!v || v->type() != nString) return "";
return v->string.s;
}
NixInt DrvInfo::queryMetaInt(const std::string & name, NixInt def)
NixInt DrvInfo::queryMetaInt(EvalState & state, const std::string & name, NixInt def)
{
Value * v = queryMeta(name);
Value * v = queryMeta(state, name);
if (!v) return def;
if (v->type() == nInt) return v->integer;
if (v->type() == nString) {
@@ -316,24 +316,9 @@ NixInt DrvInfo::queryMetaInt(const std::string & name, NixInt def)
return def;
}
NixFloat DrvInfo::queryMetaFloat(const std::string & name, NixFloat def)
bool DrvInfo::queryMetaBool(EvalState & state, const std::string & name, bool def)
{
Value * v = queryMeta(name);
if (!v) return def;
if (v->type() == nFloat) return v->fpoint;
if (v->type() == nString) {
/* Backwards compatibility with before we had support for
float meta fields. */
if (auto n = string2Float<NixFloat>(v->string.s))
return *n;
}
return def;
}
bool DrvInfo::queryMetaBool(const std::string & name, bool def)
{
Value * v = queryMeta(name);
Value * v = queryMeta(state, name);
if (!v) return def;
if (v->type() == nBool) return v->boolean;
if (v->type() == nString) {
@@ -346,11 +331,11 @@ bool DrvInfo::queryMetaBool(const std::string & name, bool def)
}
void DrvInfo::setMeta(const std::string & name, Value * v)
void DrvInfo::setMeta(EvalState & state, const std::string & name, Value * v)
{
getMeta();
auto attrs = state->buildBindings(1 + (meta ? meta->size() : 0));
auto sym = state->symbols.create(name);
getMeta(state);
auto attrs = state.ctx.buildBindings(1 + (meta ? meta->size() : 0));
auto sym = state.ctx.symbols.create(name);
if (meta)
for (auto i : *meta)
if (i.name != sym)
@@ -374,9 +359,9 @@ static bool getDerivation(EvalState & state, Value & v,
state.forceValue(v, v.determinePos(noPos));
if (!state.isDerivation(v)) return true;
DrvInfo drv(state, attrPath, v.attrs);
DrvInfo drv(attrPath, v.attrs);
drv.queryName();
drv.queryName(state);
drvs.push_back(drv);
@@ -446,7 +431,7 @@ static void getDerivations(EvalState & state, Value & vIn,
return;
} else if (v.type() != nAttrs) {
state.error<TypeError>(
state.ctx.errors.make<TypeError>(
"expression was expected to be a derivation or collection of derivations, but instead was %s",
showType(v.type(), true)
).debugThrow();
@@ -462,20 +447,20 @@ static void getDerivations(EvalState & state, Value & vIn,
// FIXME: what the fuck???
/* !!! undocumented hackery to support combining channels in
nix-env.cc. */
bool combineChannels = v.attrs->find(state.symbols.create("_combineChannels")) != v.attrs->end();
bool combineChannels = v.attrs->find(state.ctx.symbols.create("_combineChannels")) != v.attrs->end();
/* Consider the attributes in sorted order to get more
deterministic behaviour in nix-env operations (e.g. when
there are names clashes between derivations, the derivation
bound to the attribute with the "lower" name should take
precedence). */
for (auto & attr : v.attrs->lexicographicOrder(state.symbols)) {
debug("evaluating attribute '%1%'", state.symbols[attr->name]);
for (auto & attr : v.attrs->lexicographicOrder(state.ctx.symbols)) {
debug("evaluating attribute '%1%'", state.ctx.symbols[attr->name]);
// FIXME: only consider attrs with identifier-like names?? Why???
if (!std::regex_match(std::string(state.symbols[attr->name]), attrRegex)) {
if (!std::regex_match(std::string(state.ctx.symbols[attr->name]), attrRegex)) {
continue;
}
std::string joinedAttrPath = addToPath(pathPrefix, state.symbols[attr->name]);
std::string joinedAttrPath = addToPath(pathPrefix, state.ctx.symbols[attr->name]);
if (combineChannels) {
getDerivations(state, *attr->value, joinedAttrPath, autoArgs, drvs, done, ignoreAssertionFailures);
} else if (getDerivation(state, *attr->value, joinedAttrPath, drvs, ignoreAssertionFailures)) {
@@ -483,7 +468,7 @@ static void getDerivations(EvalState & state, Value & vIn,
should we recurse into it? => Only if it has a
`recurseForDerivations = true' attribute. */
if (attr->value->type() == nAttrs) {
Attr * recurseForDrvs = attr->value->attrs->get(state.s.recurseForDerivations);
Attr * recurseForDrvs = attr->value->attrs->get(state.ctx.s.recurseForDerivations);
if (recurseForDrvs == nullptr) {
continue;
}
+18 -21
View File
@@ -17,8 +17,6 @@ public:
typedef std::map<std::string, std::optional<StorePath>> Outputs;
private:
EvalState * state;
std::string name;
std::string system;
std::optional<std::optional<StorePath>> drvPath;
@@ -33,11 +31,11 @@ private:
Bindings * attrs = nullptr, * meta = nullptr;
Bindings * getMeta();
Bindings * getMeta(EvalState & state);
bool checkMeta(Value & v);
bool checkMeta(EvalState & state, Value & v);
void fillOutputs(bool withPaths = true);
void fillOutputs(EvalState & state, bool withPaths = true);
public:
/**
@@ -45,28 +43,27 @@ public:
*/
std::string attrPath;
DrvInfo(EvalState & state, std::string attrPath, Bindings * attrs);
DrvInfo(EvalState & state, ref<Store> store, const std::string & drvPathWithOutputs);
DrvInfo(std::string attrPath, Bindings * attrs);
DrvInfo(ref<Store> store, const std::string & drvPathWithOutputs);
std::string queryName();
std::string querySystem();
std::optional<StorePath> queryDrvPath();
StorePath requireDrvPath();
StorePath queryOutPath();
std::string queryOutputName();
std::string queryName(EvalState & state);
std::string querySystem(EvalState & state);
std::optional<StorePath> queryDrvPath(EvalState & state);
StorePath requireDrvPath(EvalState & state);
StorePath queryOutPath(EvalState & state);
std::string queryOutputName(EvalState & state);
/**
* Return the unordered map of output names to (optional) output paths.
* The "outputs to install" are determined by `meta.outputsToInstall`.
*/
Outputs queryOutputs(bool withPaths = true, bool onlyOutputsToInstall = false);
Outputs queryOutputs(EvalState & state, bool withPaths = true, bool onlyOutputsToInstall = false);
StringSet queryMetaNames();
Value * queryMeta(const std::string & name);
std::string queryMetaString(const std::string & name);
NixInt queryMetaInt(const std::string & name, NixInt def);
NixFloat queryMetaFloat(const std::string & name, NixFloat def);
bool queryMetaBool(const std::string & name, bool def);
void setMeta(const std::string & name, Value * v);
StringSet queryMetaNames(EvalState & state);
Value * queryMeta(EvalState & state, const std::string & name);
std::string queryMetaString(EvalState & state, const std::string & name);
NixInt queryMetaInt(EvalState & state, const std::string & name, NixInt def);
bool queryMetaBool(EvalState & state, const std::string & name, bool def);
void setMeta(EvalState & state, const std::string & name, Value * v);
/*
MetaInfo queryMetaInfo(EvalState & state) const;
+4 -4
View File
@@ -28,7 +28,7 @@ class JSONSax : nlohmann::json_sax<json> {
Value & value(EvalState & state)
{
if (!v)
v = allocRootValue(state.mem.allocValue());
v = allocRootValue(state.ctx.mem.allocValue());
return **v;
}
virtual ~JSONState() {}
@@ -40,7 +40,7 @@ class JSONSax : nlohmann::json_sax<json> {
ValueMap attrs;
std::unique_ptr<JSONState> resolve(EvalState & state) override
{
auto attrs2 = state.buildBindings(attrs.size());
auto attrs2 = state.ctx.buildBindings(attrs.size());
for (auto & i : attrs)
attrs2.insert(i.first, i.second);
parent->value(state).mkAttrs(attrs2.alreadySorted());
@@ -50,7 +50,7 @@ class JSONSax : nlohmann::json_sax<json> {
public:
void key(string_t & name, EvalState & state)
{
attrs.insert_or_assign(state.symbols.create(name), &value(state));
attrs.insert_or_assign(state.ctx.symbols.create(name), &value(state));
}
};
@@ -59,7 +59,7 @@ class JSONSax : nlohmann::json_sax<json> {
std::unique_ptr<JSONState> resolve(EvalState & state) override
{
Value & v = parent->value(state);
v = state.mem.newList(values.size());
v = state.ctx.mem.newList(values.size());
for (size_t n = 0; n < values.size(); ++n) {
v.listElems()[n] = values[n];
}
-1
View File
@@ -7,4 +7,3 @@ Description: Lix Package Manager (libexpr)
Version: @PACKAGE_VERSION@
Requires: lix-base lix-util lix-fetchers lix-store bdw-gc
Libs: -L${libdir} -llixexpr
Cflags: -I${includedir}/lix/libexpr
+33 -33
View File
@@ -21,11 +21,11 @@ std::ostream & operator <<(std::ostream & str, const SymbolStr & symbol)
return printIdentifier(str, s);
}
AttrName::AttrName(Symbol s) : symbol(s)
AttrName::AttrName(PosIdx pos, Symbol s) : pos(pos), symbol(s)
{
}
AttrName::AttrName(std::unique_ptr<Expr> e) : expr(std::move(e))
AttrName::AttrName(PosIdx pos, std::unique_ptr<Expr> e) : pos(pos), expr(std::move(e))
{
}
@@ -139,7 +139,7 @@ void ExprAttrs::showBindings(const SymbolTable & symbols, std::ostream & str) co
}
}
void ExprAttrs::show(const SymbolTable & symbols, std::ostream & str) const
void ExprSet::show(const SymbolTable & symbols, std::ostream & str) const
{
if (recursive) str << "rec ";
str << "{ ";
@@ -202,7 +202,7 @@ void ExprCall::show(const SymbolTable & symbols, std::ostream & str) const
void ExprLet::show(const SymbolTable & symbols, std::ostream & str) const
{
str << "(let ";
attrs->showBindings(symbols, str);
showBindings(symbols, str);
str << "in ";
body->show(symbols, str);
str << ")";
@@ -295,36 +295,36 @@ std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath)
/* Computing levels/displacements for variables. */
void Expr::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
void Expr::bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env)
{
abort();
}
void ExprInt::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
void ExprInt::bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debug)
es.debug->exprEnvs.insert(std::make_pair(this, env));
}
void ExprFloat::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
void ExprFloat::bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debug)
es.debug->exprEnvs.insert(std::make_pair(this, env));
}
void ExprString::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
void ExprString::bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debug)
es.debug->exprEnvs.insert(std::make_pair(this, env));
}
void ExprPath::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
void ExprPath::bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debug)
es.debug->exprEnvs.insert(std::make_pair(this, env));
}
void ExprVar::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
void ExprVar::bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debug)
es.debug->exprEnvs.insert(std::make_pair(this, env));
@@ -364,7 +364,7 @@ void ExprVar::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
enclosing `with'. If there is no `with', then we can issue an
"undefined variable" error now. */
if (withLevel == -1)
es.error<UndefinedVarError>(
es.errors.make<UndefinedVarError>(
"undefined variable '%1%'",
es.symbols[name]
).atPos(pos).debugThrow();
@@ -373,13 +373,13 @@ void ExprVar::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
this->level = withLevel;
}
void ExprInheritFrom::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
void ExprInheritFrom::bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debug)
es.debug->exprEnvs.insert(std::make_pair(this, env));
}
void ExprSelect::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
void ExprSelect::bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debug)
es.debug->exprEnvs.insert(std::make_pair(this, env));
@@ -391,7 +391,7 @@ void ExprSelect::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv>
i.expr->bindVars(es, env);
}
void ExprOpHasAttr::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
void ExprOpHasAttr::bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debug)
es.debug->exprEnvs.insert(std::make_pair(this, env));
@@ -403,7 +403,7 @@ void ExprOpHasAttr::bindVars(EvalState & es, const std::shared_ptr<const StaticE
}
std::shared_ptr<const StaticEnv> ExprAttrs::bindInheritSources(
EvalState & es, const std::shared_ptr<const StaticEnv> & env)
Evaluator & es, const std::shared_ptr<const StaticEnv> & env)
{
if (!inheritFromExprs)
return nullptr;
@@ -423,7 +423,7 @@ std::shared_ptr<const StaticEnv> ExprAttrs::bindInheritSources(
return inner;
}
void ExprAttrs::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
void ExprSet::bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debug)
es.debug->exprEnvs.insert(std::make_pair(this, env));
@@ -462,7 +462,7 @@ void ExprAttrs::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv>
}
}
void ExprList::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
void ExprList::bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debug)
es.debug->exprEnvs.insert(std::make_pair(this, env));
@@ -471,7 +471,7 @@ void ExprList::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
i->bindVars(es, env);
}
void ExprLambda::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
void ExprLambda::bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debug)
es.debug->exprEnvs.insert(std::make_pair(this, env));
@@ -498,7 +498,7 @@ void ExprLambda::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv>
body->bindVars(es, newEnv);
}
void ExprCall::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
void ExprCall::bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debug)
es.debug->exprEnvs.insert(std::make_pair(this, env));
@@ -508,21 +508,21 @@ void ExprCall::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
e->bindVars(es, env);
}
void ExprLet::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
void ExprLet::bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env)
{
auto newEnv = [&] () -> std::shared_ptr<const StaticEnv> {
auto newEnv = std::make_shared<StaticEnv>(nullptr, env.get(), attrs->attrs.size());
auto newEnv = std::make_shared<StaticEnv>(nullptr, env.get(), attrs.size());
Displacement displ = 0;
for (auto & i : attrs->attrs)
for (auto & i : attrs)
newEnv->vars.emplace_back(i.first, i.second.displ = displ++);
return newEnv;
}();
// No need to sort newEnv since attrs->attrs is in sorted order.
// No need to sort newEnv since attrs is in sorted order.
auto inheritFromEnv = attrs->bindInheritSources(es, newEnv);
for (auto & i : attrs->attrs)
auto inheritFromEnv = bindInheritSources(es, newEnv);
for (auto & i : attrs)
i.second.e->bindVars(es, i.second.chooseByKind(newEnv, env, inheritFromEnv));
if (es.debug)
@@ -531,7 +531,7 @@ void ExprLet::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
body->bindVars(es, newEnv);
}
void ExprWith::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
void ExprWith::bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debug)
es.debug->exprEnvs.insert(std::make_pair(this, env));
@@ -557,7 +557,7 @@ void ExprWith::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
body->bindVars(es, newEnv);
}
void ExprIf::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
void ExprIf::bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debug)
es.debug->exprEnvs.insert(std::make_pair(this, env));
@@ -567,7 +567,7 @@ void ExprIf::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & e
else_->bindVars(es, env);
}
void ExprAssert::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
void ExprAssert::bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debug)
es.debug->exprEnvs.insert(std::make_pair(this, env));
@@ -576,7 +576,7 @@ void ExprAssert::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv>
body->bindVars(es, env);
}
void ExprOpNot::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
void ExprOpNot::bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debug)
es.debug->exprEnvs.insert(std::make_pair(this, env));
@@ -584,7 +584,7 @@ void ExprOpNot::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv>
e->bindVars(es, env);
}
void ExprConcatStrings::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
void ExprConcatStrings::bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debug)
es.debug->exprEnvs.insert(std::make_pair(this, env));
@@ -593,7 +593,7 @@ void ExprConcatStrings::bindVars(EvalState & es, const std::shared_ptr<const Sta
i.second->bindVars(es, env);
}
void ExprPos::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
void ExprPos::bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debug)
es.debug->exprEnvs.insert(std::make_pair(this, env));
@@ -617,9 +617,9 @@ void ExprLambda::setName(Symbol name)
std::string ExprLambda::showNamePos(const EvalState & state) const
{
std::string id(name
? concatStrings("'", state.symbols[name], "'")
? concatStrings("'", state.ctx.symbols[name], "'")
: "anonymous function");
return fmt("%1% at %2%", id, state.positions[pos]);
return fmt("%1% at %2%", id, state.ctx.positions[pos]);
}
+31 -20
View File
@@ -18,7 +18,7 @@ namespace nix {
struct Env;
struct Value;
class EvalState;
class Evaluator;
struct ExprWith;
struct StaticEnv;
@@ -28,10 +28,11 @@ struct StaticEnv;
*/
struct AttrName
{
PosIdx pos;
Symbol symbol;
std::unique_ptr<Expr> expr;
AttrName(Symbol s);
AttrName(std::unique_ptr<Expr> e);
AttrName(PosIdx pos, Symbol s);
AttrName(PosIdx pos, std::unique_ptr<Expr> e);
};
typedef std::vector<AttrName> AttrPath;
@@ -59,7 +60,7 @@ public:
virtual ~Expr() { };
virtual void show(const SymbolTable & symbols, std::ostream & str) const;
virtual void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env);
virtual void bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env);
virtual void eval(EvalState & state, Env & env, Value & v);
virtual Value * maybeThunk(EvalState & state, Env & env);
virtual void setName(Symbol name);
@@ -69,7 +70,7 @@ public:
#define COMMON_METHODS \
void show(const SymbolTable & symbols, std::ostream & str) const override; \
void eval(EvalState & state, Env & env, Value & v) override; \
void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override;
void bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env) override;
struct ExprInt : Expr
{
@@ -163,7 +164,7 @@ struct ExprInheritFrom : ExprVar
}
void show(SymbolTable const & symbols, std::ostream & str) const override;
void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override;
void bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env) override;
};
struct ExprSelect : Expr
@@ -182,7 +183,7 @@ struct ExprSelect : Expr
AttrPath attrPath;
ExprSelect(const PosIdx & pos, std::unique_ptr<Expr> e, AttrPath attrPath, std::unique_ptr<Expr> def) : pos(pos), e(std::move(e)), def(std::move(def)), attrPath(std::move(attrPath)) { };
ExprSelect(const PosIdx & pos, std::unique_ptr<Expr> e, Symbol name) : pos(pos), e(std::move(e)) { attrPath.push_back(AttrName(name)); };
ExprSelect(const PosIdx & pos, std::unique_ptr<Expr> e, const PosIdx namePos, Symbol name) : pos(pos), e(std::move(e)) { attrPath.push_back(AttrName(namePos, name)); };
PosIdx getPos() const override { return pos; }
COMMON_METHODS
};
@@ -196,10 +197,16 @@ struct ExprOpHasAttr : Expr
COMMON_METHODS
};
struct ExprAttrs : Expr
/* Helper struct to contain the data shared across lets and sets */
struct ExprAttrs
{
bool recursive;
PosIdx pos;
ExprAttrs() = default;
ExprAttrs(const ExprAttrs &) = delete;
ExprAttrs & operator=(const ExprAttrs &) = delete;
ExprAttrs(ExprAttrs &&) = default;
ExprAttrs & operator=(ExprAttrs &&) = default;
virtual ~ExprAttrs() = default;
struct AttrDef {
enum class Kind {
/** `attr = expr;` */
@@ -243,17 +250,23 @@ struct ExprAttrs : Expr
};
typedef std::vector<DynamicAttrDef> DynamicAttrDefs;
DynamicAttrDefs dynamicAttrs;
ExprAttrs(const PosIdx &pos) : recursive(false), pos(pos) { };
ExprAttrs() : recursive(false) { };
PosIdx getPos() const override { return pos; }
COMMON_METHODS
std::shared_ptr<const StaticEnv> bindInheritSources(
EvalState & es, const std::shared_ptr<const StaticEnv> & env);
Evaluator & es, const std::shared_ptr<const StaticEnv> & env);
Env * buildInheritFromEnv(EvalState & state, Env & up);
void showBindings(const SymbolTable & symbols, std::ostream & str) const;
};
struct ExprSet : Expr, ExprAttrs {
PosIdx pos;
bool recursive = false;
ExprSet(const PosIdx &pos, bool recursive = false) : pos(pos), recursive(recursive) { };
ExprSet() { };
PosIdx getPos() const override { return pos; }
COMMON_METHODS
};
struct ExprList : Expr
{
std::vector<std::unique_ptr<Expr>> elems;
@@ -368,11 +381,9 @@ struct ExprCall : Expr
COMMON_METHODS
};
struct ExprLet : Expr
struct ExprLet : Expr, ExprAttrs
{
std::unique_ptr<ExprAttrs> attrs;
std::unique_ptr<Expr> body;
ExprLet(std::unique_ptr<ExprAttrs> attrs, std::unique_ptr<Expr> body) : attrs(std::move(attrs)), body(std::move(body)) { };
COMMON_METHODS
};
@@ -424,7 +435,7 @@ struct ExprOpNot : Expr
{ \
str << "("; e1->show(symbols, str); str << " " s " "; e2->show(symbols, str); str << ")"; \
} \
void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override \
void bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env) override \
{ \
e1->bindVars(es, env); e2->bindVars(es, env); \
} \
@@ -464,7 +475,7 @@ struct ExprBlackHole : Expr
{
void show(const SymbolTable & symbols, std::ostream & str) const override {}
void eval(EvalState & state, Env & env, Value & v) override;
void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override {}
void bindVars(Evaluator & es, const std::shared_ptr<const StaticEnv> & env) override {}
};
extern ExprBlackHole eBlackHole;
+62 -45
View File
@@ -260,10 +260,10 @@ template<> struct BuildAST<grammar::v1::formals> : change_head<FormalsState> {
struct AttrState : SubexprState {
using SubexprState::SubexprState;
std::vector<AttrName> attrs;
AttrPath attrs;
template <typename T>
void pushAttr(T && attr, PosIdx) { attrs.emplace_back(std::forward<T>(attr)); }
void pushAttr(T && attr, PosIdx pos) { attrs.emplace_back(pos, std::forward<T>(attr)); }
};
template<> struct BuildAST<grammar::v1::attr::simple> {
@@ -285,22 +285,38 @@ template<> struct BuildAST<grammar::v1::attr::string> {
template<> struct BuildAST<grammar::v1::attr::expr> : BuildAST<grammar::v1::attr::string> {};
struct BindingsState : SubexprState {
using SubexprState::SubexprState;
explicit BindingsState(ExprState & up, ExprAttrs & attrs) : SubexprState(up), attrs(attrs) {}
ExprAttrs attrs;
ExprAttrs & attrs;
PosIdx pos;
AttrPath path;
std::unique_ptr<Expr> value;
};
struct BindingsStateSet : BindingsState {
ExprSet set = {};
BindingsStateSet(ExprState & up, State & ps, auto &...) : BindingsState(up, set) { }
};
struct BindingsStateRecSet : BindingsState {
ExprSet set = { PosIdx{}, true };
BindingsStateRecSet(ExprState & up, State & ps, auto &...) : BindingsState(up, set) { }
};
struct BindingsStateLet : BindingsState {
ExprLet let = {};
BindingsStateLet(ExprState & up, State & ps, auto &...) : BindingsState(up, let) { }
};
struct InheritState : SubexprState {
using SubexprState::SubexprState;
std::vector<std::pair<AttrName, PosIdx>> attrs;
std::vector<AttrName> attrs;
std::unique_ptr<Expr> from;
PosIdx fromPos;
template <typename T>
void pushAttr(T && attr, PosIdx pos) { attrs.emplace_back(std::forward<T>(attr), pos); }
void pushAttr(T && attr, PosIdx pos) { attrs.emplace_back(pos, std::forward<T>(attr)); }
};
template<> struct BuildAST<grammar::v1::inherit::from> {
@@ -314,15 +330,15 @@ template<> struct BuildAST<grammar::v1::inherit> : change_head<InheritState> {
static void success0(InheritState & s, BindingsState & b, State & ps) {
auto & attrs = b.attrs.attrs;
// TODO this should not reuse generic attrpath rules.
for (auto & [i, iPos] : s.attrs) {
for (auto & i : s.attrs) {
if (i.symbol)
continue;
if (auto str = dynamic_cast<ExprString *>(i.expr.get()))
i = AttrName(ps.symbols.create(str->s));
i = AttrName(i.pos, ps.symbols.create(str->s));
else {
throw ParseError({
.msg = HintFmt("dynamic attributes not allowed in inherit"),
.pos = ps.positions[iPos]
.pos = ps.positions[i.pos]
});
}
}
@@ -331,9 +347,9 @@ template<> struct BuildAST<grammar::v1::inherit> : change_head<InheritState> {
b.attrs.inheritFromExprs = std::make_unique<std::vector<ref<Expr>>>();
auto fromExpr = ref<Expr>(std::move(s.from));
b.attrs.inheritFromExprs->push_back(fromExpr);
for (auto & [i, iPos] : s.attrs) {
for (auto & i : s.attrs) {
if (attrs.find(i.symbol) != attrs.end())
ps.dupAttr(i.symbol, iPos, attrs[i.symbol].pos);
ps.dupAttr(i.symbol, i.pos, attrs[i.symbol].pos);
auto inheritFrom = std::make_unique<ExprInheritFrom>(
s.fromPos,
b.attrs.inheritFromExprs->size() - 1,
@@ -342,19 +358,19 @@ template<> struct BuildAST<grammar::v1::inherit> : change_head<InheritState> {
attrs.emplace(
i.symbol,
ExprAttrs::AttrDef(
std::make_unique<ExprSelect>(iPos, std::move(inheritFrom), i.symbol),
iPos,
std::make_unique<ExprSelect>(i.pos, std::move(inheritFrom), i.pos, i.symbol),
i.pos,
ExprAttrs::AttrDef::Kind::InheritedFrom));
}
} else {
for (auto & [i, iPos] : s.attrs) {
for (auto & i : s.attrs) {
if (attrs.find(i.symbol) != attrs.end())
ps.dupAttr(i.symbol, iPos, attrs[i.symbol].pos);
ps.dupAttr(i.symbol, i.pos, attrs[i.symbol].pos);
attrs.emplace(
i.symbol,
ExprAttrs::AttrDef(
std::make_unique<ExprVar>(iPos, i.symbol),
iPos,
std::make_unique<ExprVar>(i.pos, i.symbol),
i.pos,
ExprAttrs::AttrDef::Kind::Inherited));
}
}
@@ -589,7 +605,7 @@ template<> struct BuildAST<grammar::v1::path::interpolation> : BuildAST<grammar:
template<> struct BuildAST<grammar::v1::path::anchor> {
static void apply(const auto & in, StringState & s, State & ps) {
Path path(absPath(in.string(), ps.basePath.path.abs()));
Path path(absPath(in.string(), ps.basePath.canonical().abs()));
/* add back in the trailing '/' to the first segment */
if (in.string_view().ends_with('/') && in.size() > 1)
path += "/";
@@ -610,13 +626,21 @@ template<> struct BuildAST<grammar::v1::path::searched_path> {
static void apply(const auto & in, StringState & s, State & ps) {
auto pos = ps.at(in);
std::vector<std::unique_ptr<Expr>> args{2};
args[0] = ps.mkInternalVar(pos, ps.s.nixPath);
/* Overriding __nixPath, while being barely documented, is intended and supported:
* https://github.com/NixOS/nix/commit/62a6eeb1f3da0a5954ad2da54c454eb7fc1c6e5d
* (TODO: Provide a better and officially supported and documented mechanism for doing this)
*/
args[0] = std::make_unique<ExprVar>(pos, ps.s.nixPath);
args[1] = std::make_unique<ExprString>(in.string());
s.parts.emplace_back(
pos,
std::make_unique<ExprCall>(
pos,
ps.mkInternalVar(pos, ps.s.findFile),
/* The option for overriding this should be deprecated eventually, but for now it has to stay
* until we can figure out how to design a better replacement.
* https://git.lix.systems/lix-project/lix/issues/599
*/
std::make_unique<ExprVar>(pos, ps.s.findFile),
std::move(args)));
}
};
@@ -663,8 +687,8 @@ template<> struct BuildAST<grammar::v1::expr::uri> {
}
};
template<> struct BuildAST<grammar::v1::expr::ancient_let> : change_head<BindingsState> {
static void success(const auto & in, BindingsState & b, ExprState & s, State & ps) {
template<> struct BuildAST<grammar::v1::expr::ancient_let> : change_head<BindingsStateRecSet> {
static void success(const auto & in, BindingsStateRecSet & b, ExprState & s, State & ps) {
// Added 2024-09-18. Turn into an error at some point in the future.
// See the documentation on deprecated features for more details.
if (!ps.featureSettings.isEnabled(Dep::AncientLet))
@@ -675,30 +699,23 @@ template<> struct BuildAST<grammar::v1::expr::ancient_let> : change_head<Binding
"--extra-deprecated-features ancient-let"
);
b.attrs.pos = ps.at(in);
b.attrs.recursive = true;
s.pushExpr<ExprSelect>(b.attrs.pos, b.attrs.pos, std::make_unique<ExprAttrs>(std::move(b.attrs)), ps.s.body);
auto pos = ps.at(in);
b.set.pos = pos;
s.pushExpr<ExprSelect>(pos, pos, std::make_unique<ExprSet>(std::move(b.set)), pos, ps.s.body);
}
};
template<> struct BuildAST<grammar::v1::expr::rec_set> : change_head<BindingsState> {
static void success(const auto & in, BindingsState & b, ExprState & s, State & ps) {
// Before inserting new attrs, check for __override and throw an error
// (the error will initially be a warning to ease migration)
if (!featureSettings.isEnabled(Dep::RecSetOverrides) && b.attrs.attrs.contains(ps.s.overrides)) {
ps.overridesFound(ps.at(in));
}
b.attrs.pos = ps.at(in);
b.attrs.recursive = true;
s.pushExpr<ExprAttrs>(b.attrs.pos, std::move(b.attrs));
template<> struct BuildAST<grammar::v1::expr::rec_set> : change_head<BindingsStateRecSet> {
static void success(const auto & in, BindingsStateRecSet & b, ExprState & s, State & ps) {
b.set.pos = ps.at(in);
s.pushExpr<ExprSet>(ps.at(in), std::move(b.set));
}
};
template<> struct BuildAST<grammar::v1::expr::set> : change_head<BindingsState> {
static void success(const auto & in, BindingsState & b, ExprState & s, State & ps) {
b.attrs.pos = ps.at(in);
s.pushExpr<ExprAttrs>(b.attrs.pos, std::move(b.attrs));
template<> struct BuildAST<grammar::v1::expr::set> : change_head<BindingsStateSet> {
static void success(const auto & in, BindingsStateSet & b, ExprState & s, State & ps) {
b.set.pos = ps.at(in);
s.pushExpr<ExprSet>(ps.at(in), std::move(b.set));
}
};
@@ -835,15 +852,15 @@ template<> struct BuildAST<grammar::v1::expr::with> {
}
};
template<> struct BuildAST<grammar::v1::expr::let> : change_head<BindingsState> {
static void success(const auto & in, BindingsState & b, ExprState & s, State & ps) {
if (!b.attrs.dynamicAttrs.empty())
template<> struct BuildAST<grammar::v1::expr::let> : change_head<BindingsStateLet> {
static void success(const auto & in, BindingsStateLet & b, ExprState & s, State & ps) {
if (!b.let.dynamicAttrs.empty())
throw ParseError({
.msg = HintFmt("dynamic attributes not allowed in let"),
.pos = ps.positions[ps.at(in)]
});
s.pushExpr<ExprLet>(ps.at(in), std::make_unique<ExprAttrs>(std::move(b.attrs)), b->popExprOnly());
b.let.body = b->popExprOnly();
s.pushExpr<ExprLet>(ps.at(in), std::move(b.let));
}
};
+1 -1
View File
@@ -22,7 +22,7 @@
namespace nix {
Expr * EvalState::parse(
Expr * Evaluator::parse(
char * text,
size_t length,
Pos::Origin origin,
+10 -9
View File
@@ -102,7 +102,7 @@ inline void State::addAttr(ExprAttrs * attrs, AttrPath && attrPath, std::unique_
ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol);
if (j != attrs->attrs.end()) {
if (j->second.kind != ExprAttrs::AttrDef::Kind::Inherited) {
ExprAttrs * attrs2 = dynamic_cast<ExprAttrs *>(j->second.e.get());
ExprSet * attrs2 = dynamic_cast<ExprSet *>(j->second.e.get());
if (!attrs2) {
attrPath.erase(i + 1, attrPath.end());
dupAttr(attrPath, pos, j->second.pos);
@@ -115,12 +115,12 @@ inline void State::addAttr(ExprAttrs * attrs, AttrPath && attrPath, std::unique_
} else {
auto next = attrs->attrs.emplace(std::piecewise_construct,
std::tuple(i->symbol),
std::tuple(std::make_unique<ExprAttrs>(), pos));
attrs = static_cast<ExprAttrs *>(next.first->second.e.get());
std::tuple(std::make_unique<ExprSet>(), pos));
attrs = static_cast<ExprSet *>(next.first->second.e.get());
}
} else {
auto & next = attrs->dynamicAttrs.emplace_back(std::move(i->expr), std::make_unique<ExprAttrs>(), pos);
attrs = static_cast<ExprAttrs *>(next.valueExpr.get());
auto & next = attrs->dynamicAttrs.emplace_back(std::move(i->expr), std::make_unique<ExprSet>(), pos);
attrs = static_cast<ExprSet *>(next.valueExpr.get());
}
}
// Expr insertion.
@@ -132,8 +132,8 @@ inline void State::addAttr(ExprAttrs * attrs, AttrPath && attrPath, std::unique_
// e and the expr pointed by the attr path are two attribute sets,
// we want to merge them.
// Otherwise, throw an error.
auto * ae = dynamic_cast<ExprAttrs *>(e.get());
auto * jAttrs = dynamic_cast<ExprAttrs *>(j->second.e.get());
auto * ae = dynamic_cast<ExprSet *>(e.get());
auto * jAttrs = dynamic_cast<ExprSet *>(j->second.e.get());
if (jAttrs && ae) {
if (ae->inheritFromExprs && !jAttrs->inheritFromExprs)
jAttrs->inheritFromExprs = std::make_unique<std::vector<ref<Expr>>>();
@@ -157,8 +157,9 @@ inline void State::addAttr(ExprAttrs * attrs, AttrPath && attrPath, std::unique_
} else {
// Before inserting new attrs, check for __override and throw an error
// (the error will initially be a warning to ease migration)
if (attrs->recursive && !featureSettings.isEnabled(Dep::RecSetOverrides) && i->symbol == s.overrides) {
overridesFound(pos);
if (!featureSettings.isEnabled(Dep::RecSetOverrides) && i->symbol == s.overrides) {
if (auto set = dynamic_cast<ExprSet *>(attrs); set && set->recursive)
overridesFound(pos);
}
// This attr path is not defined. Let's create it.
+294 -276
View File
File diff suppressed because it is too large Load Diff
+23 -23
View File
@@ -56,7 +56,7 @@ void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, Value *
auto contextSize = context.size();
if (contextSize != 1) {
state.error<EvalError>(
state.ctx.errors.make<EvalError>(
"context of string '%s' must have exactly one element, but has %d",
*s,
contextSize
@@ -66,9 +66,9 @@ void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, Value *
(NixStringContextElem { std::visit(overloaded {
[&](const NixStringContextElem::Opaque & c) -> NixStringContextElem::DrvDeep {
if (!c.path.isDerivation()) {
state.error<EvalError>(
state.ctx.errors.make<EvalError>(
"path '%s' is not a derivation",
state.store->printStorePath(c.path)
state.ctx.store->printStorePath(c.path)
).atPos(pos).debugThrow();
}
return NixStringContextElem::DrvDeep {
@@ -76,7 +76,7 @@ void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, Value *
};
},
[&](const NixStringContextElem::Built & c) -> NixStringContextElem::DrvDeep {
state.error<EvalError>(
state.ctx.errors.make<EvalError>(
"`addDrvOutputDependencies` can only act on derivations, not on a derivation output such as '%1%'",
c.output
).atPos(pos).debugThrow();
@@ -129,7 +129,7 @@ void prim_getContext(EvalState & state, const PosIdx pos, Value * * args, Value
[&](NixStringContextElem::Built && b) {
// FIXME should eventually show string context as is, no
// resolving here.
auto drvPath = resolveDerivedPath(*state.store, *b.drvPath);
auto drvPath = resolveDerivedPath(*state.ctx.store, *b.drvPath);
contextInfos[std::move(drvPath)].outputs.emplace_back(std::move(b.output));
},
[&](NixStringContextElem::Opaque && o) {
@@ -138,22 +138,22 @@ void prim_getContext(EvalState & state, const PosIdx pos, Value * * args, Value
}, ((NixStringContextElem &&) i).raw);
}
auto attrs = state.buildBindings(contextInfos.size());
auto attrs = state.ctx.buildBindings(contextInfos.size());
auto sAllOutputs = state.symbols.create("allOutputs");
auto sAllOutputs = state.ctx.symbols.create("allOutputs");
for (const auto & info : contextInfos) {
auto infoAttrs = state.buildBindings(3);
auto infoAttrs = state.ctx.buildBindings(3);
if (info.second.path)
infoAttrs.alloc(state.s.path).mkBool(true);
infoAttrs.alloc(state.ctx.s.path).mkBool(true);
if (info.second.allOutputs)
infoAttrs.alloc(sAllOutputs).mkBool(true);
if (!info.second.outputs.empty()) {
auto & outputsVal = infoAttrs.alloc(state.s.outputs);
outputsVal = state.mem.newList(info.second.outputs.size());
auto & outputsVal = infoAttrs.alloc(state.ctx.s.outputs);
outputsVal = state.ctx.mem.newList(info.second.outputs.size());
for (const auto & [i, output] : enumerate(info.second.outputs))
(outputsVal.listElems()[i] = state.mem.allocValue())->mkString(output);
(outputsVal.listElems()[i] = state.ctx.mem.allocValue())->mkString(output);
}
attrs.alloc(state.store->printStorePath(info.first)).mkAttrs(infoAttrs);
attrs.alloc(state.ctx.store->printStorePath(info.first)).mkAttrs(infoAttrs);
}
v.mkAttrs(attrs);
@@ -172,19 +172,19 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.appendContext");
auto sAllOutputs = state.symbols.create("allOutputs");
auto sAllOutputs = state.ctx.symbols.create("allOutputs");
for (auto & i : *args[1]->attrs) {
const auto & name = state.symbols[i.name];
if (!state.store->isStorePath(name))
state.error<EvalError>(
const auto & name = state.ctx.symbols[i.name];
if (!state.ctx.store->isStorePath(name))
state.ctx.errors.make<EvalError>(
"context key '%s' is not a store path",
name
).atPos(i.pos).debugThrow();
auto namePath = state.store->parseStorePath(name);
auto namePath = state.ctx.store->parseStorePath(name);
if (!settings.readOnlyMode)
state.store->ensurePath(namePath);
state.ctx.store->ensurePath(namePath);
state.forceAttrs(*i.value, i.pos, "while evaluating the value of a string context");
auto iter = i.value->attrs->find(state.s.path);
auto iter = i.value->attrs->find(state.ctx.s.path);
if (iter != i.value->attrs->end()) {
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `path` attribute of a string context"))
context.emplace(NixStringContextElem::Opaque {
@@ -196,7 +196,7 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
if (iter != i.value->attrs->end()) {
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `allOutputs` attribute of a string context")) {
if (!isDerivation(name)) {
state.error<EvalError>(
state.ctx.errors.make<EvalError>(
"tried to add all-outputs context of %s, which is not a derivation, to a string",
name
).atPos(i.pos).debugThrow();
@@ -207,11 +207,11 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
}
}
iter = i.value->attrs->find(state.s.outputs);
iter = i.value->attrs->find(state.ctx.s.outputs);
if (iter != i.value->attrs->end()) {
state.forceList(*iter->value, iter->pos, "while evaluating the `outputs` attribute of a string context");
if (iter->value->listSize() && !isDerivation(name)) {
state.error<EvalError>(
state.ctx.errors.make<EvalError>(
"tried to add derivation output context of %s, which is not a derivation, to a string",
name
).atPos(i.pos).debugThrow();
+35 -35
View File
@@ -19,24 +19,24 @@ static void runFetchClosureWithRewrite(EvalState & state, const PosIdx pos, Stor
// establish toPath or throw
if (!toPathMaybe || !state.store->isValidPath(*toPathMaybe)) {
auto rewrittenPath = makeContentAddressed(fromStore, *state.store, fromPath);
if (!toPathMaybe || !state.ctx.store->isValidPath(*toPathMaybe)) {
auto rewrittenPath = makeContentAddressed(fromStore, *state.ctx.store, fromPath);
if (toPathMaybe && *toPathMaybe != rewrittenPath)
throw Error({
.msg = HintFmt("rewriting '%s' to content-addressed form yielded '%s', while '%s' was expected",
state.store->printStorePath(fromPath),
state.store->printStorePath(rewrittenPath),
state.store->printStorePath(*toPathMaybe)),
.pos = state.positions[pos]
state.ctx.store->printStorePath(fromPath),
state.ctx.store->printStorePath(rewrittenPath),
state.ctx.store->printStorePath(*toPathMaybe)),
.pos = state.ctx.positions[pos]
});
if (!toPathMaybe)
throw Error({
.msg = HintFmt(
"rewriting '%s' to content-addressed form yielded '%s'\n"
"Use this value for the 'toPath' attribute passed to 'fetchClosure'",
state.store->printStorePath(fromPath),
state.store->printStorePath(rewrittenPath)),
.pos = state.positions[pos]
state.ctx.store->printStorePath(fromPath),
state.ctx.store->printStorePath(rewrittenPath)),
.pos = state.ctx.positions[pos]
});
}
@@ -44,21 +44,21 @@ static void runFetchClosureWithRewrite(EvalState & state, const PosIdx pos, Stor
// check and return
auto resultInfo = state.store->queryPathInfo(toPath);
auto resultInfo = state.ctx.store->queryPathInfo(toPath);
if (!resultInfo->isContentAddressed(*state.store)) {
if (!resultInfo->isContentAddressed(*state.ctx.store)) {
// We don't perform the rewriting when outPath already exists, as an optimisation.
// However, we can quickly detect a mistake if the toPath is input addressed.
throw Error({
.msg = HintFmt(
"The 'toPath' value '%s' is input-addressed, so it can't possibly be the result of rewriting to a content-addressed path.\n\n"
"Set 'toPath' to an empty string to make Lix report the correct content-addressed path.",
state.store->printStorePath(toPath)),
.pos = state.positions[pos]
state.ctx.store->printStorePath(toPath)),
.pos = state.ctx.positions[pos]
});
}
state.mkStorePathString(toPath, v);
state.ctx.paths.mkStorePathString(toPath, v);
}
/**
@@ -66,12 +66,12 @@ static void runFetchClosureWithRewrite(EvalState & state, const PosIdx pos, Stor
*/
static void runFetchClosureWithContentAddressedPath(EvalState & state, const PosIdx pos, Store & fromStore, const StorePath & fromPath, Value & v) {
if (!state.store->isValidPath(fromPath))
copyClosure(fromStore, *state.store, RealisedPath::Set { fromPath });
if (!state.ctx.store->isValidPath(fromPath))
copyClosure(fromStore, *state.ctx.store, RealisedPath::Set { fromPath });
auto info = state.store->queryPathInfo(fromPath);
auto info = state.ctx.store->queryPathInfo(fromPath);
if (!info->isContentAddressed(*state.store)) {
if (!info->isContentAddressed(*state.ctx.store)) {
throw Error({
.msg = HintFmt(
"The 'fromPath' value '%s' is input-addressed, but 'inputAddressed' is set to 'false' (default).\n\n"
@@ -79,12 +79,12 @@ static void runFetchClosureWithContentAddressedPath(EvalState & state, const Pos
" inputAddressed = true;\n\n"
"to the 'fetchClosure' arguments.\n\n"
"Note that to ensure authenticity input-addressed store paths, users must configure a trusted binary cache public key on their systems. This is not needed for content-addressed paths.",
state.store->printStorePath(fromPath)),
.pos = state.positions[pos]
state.ctx.store->printStorePath(fromPath)),
.pos = state.ctx.positions[pos]
});
}
state.mkStorePathString(fromPath, v);
state.ctx.paths.mkStorePathString(fromPath, v);
}
/**
@@ -92,22 +92,22 @@ static void runFetchClosureWithContentAddressedPath(EvalState & state, const Pos
*/
static void runFetchClosureWithInputAddressedPath(EvalState & state, const PosIdx pos, Store & fromStore, const StorePath & fromPath, Value & v) {
if (!state.store->isValidPath(fromPath))
copyClosure(fromStore, *state.store, RealisedPath::Set { fromPath });
if (!state.ctx.store->isValidPath(fromPath))
copyClosure(fromStore, *state.ctx.store, RealisedPath::Set { fromPath });
auto info = state.store->queryPathInfo(fromPath);
auto info = state.ctx.store->queryPathInfo(fromPath);
if (info->isContentAddressed(*state.store)) {
if (info->isContentAddressed(*state.ctx.store)) {
throw Error({
.msg = HintFmt(
"The store object referred to by 'fromPath' at '%s' is not input-addressed, but 'inputAddressed' is set to 'true'.\n\n"
"Remove the 'inputAddressed' attribute (it defaults to 'false') to expect 'fromPath' to be content-addressed",
state.store->printStorePath(fromPath)),
.pos = state.positions[pos]
state.ctx.store->printStorePath(fromPath)),
.pos = state.ctx.positions[pos]
});
}
state.mkStorePathString(fromPath, v);
state.ctx.paths.mkStorePathString(fromPath, v);
}
typedef std::optional<StorePath> StorePathOrGap;
@@ -122,7 +122,7 @@ void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * args, Valu
std::optional<bool> inputAddressedMaybe;
for (auto & attr : *args[0]->attrs) {
const auto & attrName = state.symbols[attr.name];
const auto & attrName = state.ctx.symbols[attr.name];
auto attrHint = [&]() -> std::string {
return "while evaluating the '" + attrName + "' attribute passed to builtins.fetchClosure";
};
@@ -154,14 +154,14 @@ void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * args, Valu
else
throw Error({
.msg = HintFmt("attribute '%s' isn't supported in call to 'fetchClosure'", attrName),
.pos = state.positions[pos]
.pos = state.ctx.positions[pos]
});
}
if (!fromPath)
throw Error({
.msg = HintFmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"),
.pos = state.positions[pos]
.pos = state.ctx.positions[pos]
});
bool inputAddressed = inputAddressedMaybe.value_or(false);
@@ -172,14 +172,14 @@ void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * args, Valu
.msg = HintFmt("attribute '%s' is set to true, but '%s' is also set. Please remove one of them",
"inputAddressed",
"toPath"),
.pos = state.positions[pos]
.pos = state.ctx.positions[pos]
});
}
if (!fromStoreUrl)
throw Error({
.msg = HintFmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"),
.pos = state.positions[pos]
.pos = state.ctx.positions[pos]
});
auto parsedURL = parseURL(*fromStoreUrl);
@@ -189,13 +189,13 @@ void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * args, Valu
!(getEnv("_NIX_IN_TEST").has_value() && parsedURL.scheme == "file"))
throw Error({
.msg = HintFmt("'fetchClosure' only supports http:// and https:// stores"),
.pos = state.positions[pos]
.pos = state.ctx.positions[pos]
});
if (!parsedURL.query.empty())
throw Error({
.msg = HintFmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl),
.pos = state.positions[pos]
.pos = state.ctx.positions[pos]
});
auto fromStore = openStore(parsedURL.to_string());
+8 -8
View File
@@ -21,7 +21,7 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
if (args[0]->type() == nAttrs) {
for (auto & attr : *args[0]->attrs) {
std::string_view n(state.symbols[attr.name]);
std::string_view n(state.ctx.symbols[attr.name]);
if (n == "url")
url = state.coerceToString(attr.pos, *attr.value, context,
"while evaluating the `url` attribute passed to builtins.fetchMercurial",
@@ -38,11 +38,11 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
else if (n == "name")
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.fetchMercurial");
else
state.error<EvalError>("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]).atPos(attr.pos).debugThrow();
state.ctx.errors.make<EvalError>("unsupported argument '%s' to 'fetchMercurial'", state.ctx.symbols[attr.name]).atPos(attr.pos).debugThrow();
}
if (url.empty())
state.error<EvalError>("'url' argument required").atPos(pos).debugThrow();
state.ctx.errors.make<EvalError>("'url' argument required").atPos(pos).debugThrow();
} else
url = state.coerceToString(pos, *args[0], context,
@@ -51,7 +51,7 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
// FIXME: git externals probably can be used to bypass the URI
// whitelist. Ah well.
state.checkURI(url);
state.ctx.paths.checkURI(url);
if (evalSettings.pureEval && !rev)
throw Error("in pure evaluation mode, 'fetchMercurial' requires a Mercurial revision");
@@ -65,10 +65,10 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
auto input = fetchers::Input::fromAttrs(std::move(attrs));
// FIXME: use name
auto [tree, input2] = input.fetch(state.store);
auto [tree, input2] = input.fetch(state.ctx.store);
auto attrs2 = state.buildBindings(8);
state.mkStorePathString(tree.storePath, attrs2.alloc(state.s.outPath));
auto attrs2 = state.ctx.buildBindings(8);
state.ctx.paths.mkStorePathString(tree.storePath, attrs2.alloc(state.ctx.s.outPath));
if (input2.getRef())
attrs2.alloc("branch").mkString(*input2.getRef());
// Backward compatibility: set 'rev' to
@@ -80,7 +80,7 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
attrs2.alloc("revCount").mkInt(*revCount);
v.mkAttrs(attrs2);
state.allowPath(tree.storePath);
state.ctx.paths.allowPath(tree.storePath);
}
static RegisterPrimOp r_fetchMercurial({
+34 -34
View File
@@ -15,7 +15,7 @@
namespace nix {
void emitTreeAttrs(
EvalState & state,
Evaluator & state,
const fetchers::Tree & tree,
const fetchers::Input & input,
Value & v,
@@ -27,7 +27,7 @@ void emitTreeAttrs(
auto attrs = state.buildBindings(10);
state.mkStorePathString(tree.storePath, attrs.alloc(state.s.outPath));
state.paths.mkStorePathString(tree.storePath, attrs.alloc(state.s.outPath));
// FIXME: support arbitrary input attributes.
@@ -74,7 +74,7 @@ void emitTreeAttrs(
std::string fixURI(std::string uri, EvalState & state, const std::string & defaultScheme = "file")
{
state.checkURI(uri);
state.ctx.paths.checkURI(uri);
if (uri.find("://") == std::string::npos) {
const auto p = ParsedURL {
.scheme = defaultScheme,
@@ -122,50 +122,50 @@ static void fetchTree(
fetchers::Attrs attrs;
if (auto aType = args[0]->attrs->get(state.s.type)) {
if (auto aType = args[0]->attrs->get(state.ctx.s.type)) {
if (type)
state.error<EvalError>(
state.ctx.errors.make<EvalError>(
"unexpected attribute 'type'"
).atPos(pos).debugThrow();
type = state.forceStringNoCtx(*aType->value, aType->pos, "while evaluating the `type` attribute passed to builtins.fetchTree");
} else if (!type)
state.error<EvalError>(
state.ctx.errors.make<EvalError>(
"attribute 'type' is missing in call to 'fetchTree'"
).atPos(pos).debugThrow();
attrs.emplace("type", type.value());
for (auto & attr : *args[0]->attrs) {
if (attr.name == state.s.type) continue;
if (attr.name == state.ctx.s.type) continue;
state.forceValue(*attr.value, attr.pos);
if (attr.value->type() == nPath || attr.value->type() == nString) {
auto s = state.coerceToString(attr.pos, *attr.value, context, "", false, false).toOwned();
attrs.emplace(state.symbols[attr.name],
state.symbols[attr.name] == "url"
attrs.emplace(state.ctx.symbols[attr.name],
state.ctx.symbols[attr.name] == "url"
? type == "git"
? fixURIForGit(s, state)
: fixURI(s, state)
: s);
}
else if (attr.value->type() == nBool)
attrs.emplace(state.symbols[attr.name], Explicit<bool>{attr.value->boolean});
attrs.emplace(state.ctx.symbols[attr.name], Explicit<bool>{attr.value->boolean});
else if (attr.value->type() == nInt) {
auto intValue = attr.value->integer.value;
if (intValue < 0) {
state.error<EvalError>("negative value given for fetchTree attr %1%: %2%", state.symbols[attr.name], intValue).atPos(pos).debugThrow();
state.ctx.errors.make<EvalError>("negative value given for fetchTree attr %1%: %2%", state.ctx.symbols[attr.name], intValue).atPos(pos).debugThrow();
}
unsigned long asUnsigned = intValue;
attrs.emplace(state.symbols[attr.name], asUnsigned);
attrs.emplace(state.ctx.symbols[attr.name], asUnsigned);
} else
state.error<TypeError>("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
state.symbols[attr.name], showType(*attr.value)).debugThrow();
state.ctx.errors.make<TypeError>("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
state.ctx.symbols[attr.name], showType(*attr.value)).debugThrow();
}
if (!params.allowNameArgument)
if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
state.error<EvalError>(
state.ctx.errors.make<EvalError>(
"attribute 'name' isnt supported in call to 'fetchTree'"
).atPos(pos).debugThrow();
@@ -186,17 +186,17 @@ static void fetchTree(
}
if (!evalSettings.pureEval && !input.isDirect())
input = lookupInRegistries(state.store, input).first;
input = lookupInRegistries(state.ctx.store, input).first;
if (evalSettings.pureEval && !input.isLocked()) {
state.error<EvalError>("in pure evaluation mode, 'fetchTree' requires a locked input").atPos(pos).debugThrow();
state.ctx.errors.make<EvalError>("in pure evaluation mode, 'fetchTree' requires a locked input").atPos(pos).debugThrow();
}
auto [tree, input2] = input.fetch(state.store);
auto [tree, input2] = input.fetch(state.ctx.store);
state.allowPath(tree.storePath);
state.ctx.paths.allowPath(tree.storePath);
emitTreeAttrs(state, tree, input2, v, params.emptyRevFallback, false);
emitTreeAttrs(state.ctx, tree, input2, v, params.emptyRevFallback, false);
}
static void prim_fetchTree(EvalState & state, const PosIdx pos, Value * * args, Value & v)
@@ -223,7 +223,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
if (args[0]->type() == nAttrs) {
for (auto & attr : *args[0]->attrs) {
std::string_view n(state.symbols[attr.name]);
std::string_view n(state.ctx.symbols[attr.name]);
if (n == "url")
url = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the url we should fetch");
else if (n == "sha256")
@@ -231,12 +231,12 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
else if (n == "name")
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the name of the content we should fetch");
else
state.error<EvalError>("unsupported argument '%s' to '%s'", n, who)
state.ctx.errors.make<EvalError>("unsupported argument '%s' to '%s'", n, who)
.atPos(pos).debugThrow();
}
if (!url)
state.error<EvalError>(
state.ctx.errors.make<EvalError>(
"'url' argument required").atPos(pos).debugThrow();
} else
url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch");
@@ -244,17 +244,17 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
if (who == "fetchTarball")
url = evalSettings.resolvePseudoUrl(*url);
state.checkURI(*url);
state.ctx.paths.checkURI(*url);
if (name == "")
name = baseNameOf(*url);
if (evalSettings.pureEval && !expectedHash)
state.error<EvalError>("in pure evaluation mode, '%s' requires a 'sha256' argument", who).atPos(pos).debugThrow();
state.ctx.errors.make<EvalError>("in pure evaluation mode, '%s' requires a 'sha256' argument", who).atPos(pos).debugThrow();
// early exit if pinned and already in the store
if (expectedHash && expectedHash->type == HashType::SHA256) {
auto expectedPath = state.store->makeFixedOutputPath(
auto expectedPath = state.ctx.store->makeFixedOutputPath(
name,
FixedOutputInfo {
.method = unpack ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat,
@@ -262,8 +262,8 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
.references = {}
});
if (state.store->isValidPath(expectedPath)) {
state.allowAndSetStorePathString(expectedPath, v);
if (state.ctx.store->isValidPath(expectedPath)) {
state.ctx.paths.allowAndSetStorePathString(expectedPath, v);
return;
}
}
@@ -272,15 +272,15 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
// https://github.com/NixOS/nix/issues/4313
auto storePath =
unpack
? fetchers::downloadTarball(state.store, *url, name, (bool) expectedHash).tree.storePath
: fetchers::downloadFile(state.store, *url, name, (bool) expectedHash).storePath;
? fetchers::downloadTarball(state.ctx.store, *url, name, (bool) expectedHash).tree.storePath
: fetchers::downloadFile(state.ctx.store, *url, name, (bool) expectedHash).storePath;
if (expectedHash) {
auto hash = unpack
? state.store->queryPathInfo(storePath)->narHash
: hashFile(HashType::SHA256, state.store->toRealPath(storePath));
? state.ctx.store->queryPathInfo(storePath)->narHash
: hashFile(HashType::SHA256, state.ctx.store->toRealPath(storePath));
if (hash != *expectedHash) {
state.error<EvalError>(
state.ctx.errors.make<EvalError>(
"hash mismatch in file downloaded from '%s':\n specified: %s\n got: %s",
*url,
expectedHash->to_string(Base::Base32, true),
@@ -290,7 +290,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
}
}
state.allowAndSetStorePathString(storePath, v);
state.ctx.paths.allowAndSetStorePathString(storePath, v);
}
void prim_fetchurl(EvalState & state, const PosIdx pos, Value * * args, Value & v)
+5 -5
View File
@@ -26,7 +26,7 @@ void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, Value &
size_t size = 0;
for (auto & i : table) { (void) i; size++; }
auto attrs = state.buildBindings(size);
auto attrs = state.ctx.buildBindings(size);
for(auto & elem : table)
visit(attrs.alloc(elem.first), elem.second);
@@ -39,9 +39,9 @@ void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, Value &
auto array = toml::get<std::vector<toml::value>>(t);
size_t size = array.size();
v = state.mem.newList(size);
v = state.ctx.mem.newList(size);
for (size_t i = 0; i < size; ++i)
visit(*(v.listElems()[i] = state.mem.allocValue()), array[i]);
visit(*(v.listElems()[i] = state.ctx.mem.allocValue()), array[i]);
}
break;;
case toml::value_t::boolean:
@@ -62,7 +62,7 @@ void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, Value &
case toml::value_t::local_time:
{
if (experimentalFeatureSettings.isEnabled(Xp::ParseTomlTimestamps)) {
auto attrs = state.buildBindings(2);
auto attrs = state.ctx.buildBindings(2);
attrs.alloc("_type").mkString("timestamp");
std::ostringstream s;
s << t;
@@ -83,7 +83,7 @@ void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, Value &
try {
visit(val, toml::parse(tomlStream, "fromTOML" /* the "filename" */));
} catch (std::exception & e) { // TODO: toml::syntax_error
state.error<EvalError>("while parsing TOML: %s", e.what()).atPos(pos).debugThrow();
state.ctx.errors.make<EvalError>("while parsing TOML: %s", e.what()).atPos(pos).debugThrow();
}
}
+5 -11
View File
@@ -231,11 +231,11 @@ private:
void printDerivation(Value & v)
{
Bindings::iterator i = v.attrs->find(state.s.drvPath);
Bindings::iterator i = v.attrs->find(state.ctx.s.drvPath);
NixStringContext context;
std::string storePath;
if (i != v.attrs->end())
storePath = state.store->printStorePath(state.coerceToStorePath(i->pos, *i->value, context, "while evaluating the drvPath of a derivation"));
storePath = state.ctx.store->printStorePath(state.coerceToStorePath(i->pos, *i->value, context, "while evaluating the drvPath of a derivation"));
if (options.ansiColors)
output << ANSI_GREEN;
@@ -287,7 +287,7 @@ private:
AttrVec sorted;
for (auto & i : *v.attrs)
sorted.emplace_back(std::pair(state.symbols[i.name], i.value));
sorted.emplace_back(std::pair(state.ctx.symbols[i.name], i.value));
if (options.maxAttrs == std::numeric_limits<size_t>::max())
std::sort(sorted.begin(), sorted.end());
@@ -396,11 +396,11 @@ private:
output << "lambda";
if (v.lambda.fun) {
if (v.lambda.fun->name) {
output << " " << state.symbols[v.lambda.fun->name];
output << " " << state.ctx.symbols[v.lambda.fun->name];
}
std::ostringstream s;
s << state.positions[v.lambda.fun->pos];
s << state.ctx.positions[v.lambda.fun->pos];
output << " @ " << filterANSIEscapes(s.str());
}
} else if (v.isPrimOp()) {
@@ -581,10 +581,4 @@ fmt_internal::HintFmt & fmt_internal::HintFmt::operator%(const ValuePrinter & va
return *this;
}
std::ostream & operator<<(std::ostream & output, ExprPrinter const & printer)
{
printer.expr.show(printer.state.symbols, output);
return output;
}
}
-22
View File
@@ -81,26 +81,4 @@ std::ostream & operator<<(std::ostream & output, const ValuePrinter & printer);
template<>
fmt_internal::HintFmt & fmt_internal::HintFmt::operator%(const ValuePrinter & value);
/**
* A partially-applied form of Expr::show(), which can be formatted using `<<`
* without allocating an intermediate string.
* This class should not outlive the eval state or it will UAF.
* FIXME: This should take `nix::ref`s, to avoid that, but our eval methods all have
* EvalState &, not ref<EvalState>, and constructing a new shared_ptr to data that
* already has a shared_ptr is a much bigger footgun. In the current architecture of
* libexpr, using an ExprPrinter after an EvalState has been destroyed would be
* pretty hard.
*/
class ExprPrinter
{
/** The eval state used to get symbols. */
EvalState const & state;
/** The expression to print. */
Expr const & expr;
public:
ExprPrinter(EvalState const & state, Expr const & expr) : state(state), expr(expr) { }
friend std::ostream & operator << (std::ostream & output, ExprPrinter const & printer);
};
}
-9
View File
@@ -97,15 +97,6 @@ public:
return Symbol(idx + 1);
}
std::vector<SymbolStr> resolve(const std::vector<Symbol> & symbols) const
{
std::vector<SymbolStr> result;
result.reserve(symbols.size());
for (auto sym : symbols)
result.push_back((*this)[sym]);
return result;
}
SymbolStr operator[](Symbol s) const
{
if (s.id == 0 || s.id > store.size())
+10 -10
View File
@@ -36,10 +36,10 @@ json printValueAsJSON(EvalState & state, bool strict,
case nPath:
if (copyToStore)
out = state.store->printStorePath(
state.copyPathToStore(context, v.path()));
out = state.ctx.store->printStorePath(
state.ctx.paths.copyPathToStore(context, v.path(), state.ctx.repair));
else
out = v.path().path.abs();
out = v.path().to_string();
break;
case nNull:
@@ -52,18 +52,18 @@ json printValueAsJSON(EvalState & state, bool strict,
out = *maybeString;
break;
}
auto i = v.attrs->find(state.s.outPath);
auto i = v.attrs->find(state.ctx.s.outPath);
if (i == v.attrs->end()) {
out = json::object();
StringSet names;
for (auto & j : *v.attrs)
names.emplace(state.symbols[j.name]);
names.emplace(state.ctx.symbols[j.name]);
for (auto & j : names) {
Attr & a(*v.attrs->find(state.symbols.create(j)));
Attr & a(*v.attrs->find(state.ctx.symbols.create(j)));
try {
out[j] = printValueAsJSON(state, strict, *a.value, a.pos, context, copyToStore);
} catch (Error & e) {
e.addTrace(state.positions[a.pos],
e.addTrace(state.ctx.positions[a.pos],
HintFmt("while evaluating attribute '%1%'", j));
throw;
}
@@ -80,7 +80,7 @@ json printValueAsJSON(EvalState & state, bool strict,
try {
out.push_back(printValueAsJSON(state, strict, *elem, pos, context, copyToStore));
} catch (Error & e) {
e.addTrace(state.positions[pos],
e.addTrace(state.ctx.positions[pos],
HintFmt("while evaluating list element at index %1%", i));
throw;
}
@@ -99,7 +99,7 @@ json printValueAsJSON(EvalState & state, bool strict,
case nThunk:
case nFunction:
state.error<TypeError>(
state.ctx.errors.make<TypeError>(
"cannot convert %1% to JSON",
showType(v)
)
@@ -118,7 +118,7 @@ void printValueAsJSON(EvalState & state, bool strict,
json ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
NixStringContext & context, bool copyToStore) const
{
state.error<TypeError>("cannot convert %1% to JSON", showType())
state.ctx.errors.make<TypeError>("cannot convert %1% to JSON", showType())
.debugThrow();
}
+13 -13
View File
@@ -21,8 +21,8 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
static void posToXML(EvalState & state, XMLAttrs & xmlAttrs, const Pos & pos)
{
if (auto path = std::get_if<SourcePath>(&pos.origin))
xmlAttrs["path"] = path->path.abs();
if (auto path = std::get_if<CheckedSourcePath>(&pos.origin))
xmlAttrs["path"] = path->to_string();
xmlAttrs["line"] = fmt("%1%", pos.line);
xmlAttrs["column"] = fmt("%1%", pos.column);
}
@@ -34,14 +34,14 @@ static void showAttrs(EvalState & state, bool strict, bool location,
StringSet names;
for (auto & i : attrs)
names.emplace(state.symbols[i.name]);
names.emplace(state.ctx.symbols[i.name]);
for (auto & i : names) {
Attr & a(*attrs.find(state.symbols.create(i)));
Attr & a(*attrs.find(state.ctx.symbols.create(i)));
XMLAttrs xmlAttrs;
xmlAttrs["name"] = i;
if (location && a.pos) posToXML(state, xmlAttrs, state.positions[a.pos]);
if (location && a.pos) posToXML(state, xmlAttrs, state.ctx.positions[a.pos]);
XMLOpenElement _(doc, "attr", xmlAttrs);
printValueAsXML(state, strict, location,
@@ -86,17 +86,17 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
if (state.isDerivation(v)) {
XMLAttrs xmlAttrs;
Bindings::iterator a = v.attrs->find(state.symbols.create("derivation"));
Bindings::iterator a = v.attrs->find(state.ctx.symbols.create("derivation"));
Path drvPath;
a = v.attrs->find(state.s.drvPath);
a = v.attrs->find(state.ctx.s.drvPath);
if (a != v.attrs->end()) {
if (strict) state.forceValue(*a->value, a->pos);
if (a->value->type() == nString)
xmlAttrs["drvPath"] = drvPath = a->value->string.s;
}
a = v.attrs->find(state.s.outPath);
a = v.attrs->find(state.ctx.s.outPath);
if (a != v.attrs->end()) {
if (strict) state.forceValue(*a->value, a->pos);
if (a->value->type() == nString)
@@ -132,18 +132,18 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
break;
}
XMLAttrs xmlAttrs;
if (location) posToXML(state, xmlAttrs, state.positions[v.lambda.fun->pos]);
if (location) posToXML(state, xmlAttrs, state.ctx.positions[v.lambda.fun->pos]);
XMLOpenElement _(doc, "function", xmlAttrs);
if (v.lambda.fun->hasFormals()) {
XMLAttrs attrs;
if (v.lambda.fun->arg) attrs["name"] = state.symbols[v.lambda.fun->arg];
if (v.lambda.fun->arg) attrs["name"] = state.ctx.symbols[v.lambda.fun->arg];
if (v.lambda.fun->formals->ellipsis) attrs["ellipsis"] = "1";
XMLOpenElement _(doc, "attrspat", attrs);
for (const Formal & i : v.lambda.fun->formals->lexicographicOrder(state.symbols))
doc.writeEmptyElement("attr", singletonAttrs("name", state.symbols[i.name]));
for (const Formal & i : v.lambda.fun->formals->lexicographicOrder(state.ctx.symbols))
doc.writeEmptyElement("attr", singletonAttrs("name", state.ctx.symbols[i.name]));
} else
doc.writeEmptyElement("varpat", singletonAttrs("name", state.symbols[v.lambda.fun->arg]));
doc.writeEmptyElement("varpat", singletonAttrs("name", state.ctx.symbols[v.lambda.fun->arg]));
break;
}
+3 -3
View File
@@ -55,8 +55,8 @@ bool Value::isTrivial() const
internalType != tApp
&& internalType != tPrimOpApp
&& (internalType != tThunk
|| (dynamic_cast<ExprAttrs *>(thunk.expr)
&& static_cast<ExprAttrs *>(thunk.expr)->dynamicAttrs.empty())
|| (dynamic_cast<ExprSet *>(thunk.expr)
&& static_cast<ExprSet *>(thunk.expr)->dynamicAttrs.empty())
|| dynamic_cast<ExprLambda *>(thunk.expr)
|| dynamic_cast<ExprList *>(thunk.expr));
}
@@ -100,7 +100,7 @@ void Value::mkStringMove(const char * s, const NixStringContext & context)
void Value::mkPath(const SourcePath & path)
{
mkPath(gcCopyStringIfNeeded(path.path.abs()));
*this = Value(NewValueAs::path, path);
}
}
+1 -1
View File
@@ -347,7 +347,7 @@ public:
/// dynamic (GC) allocation to do so.
Value(path_t, SourcePath const & path)
: internalType(tPath)
, _path(gcCopyStringIfNeeded(path.path.abs()))
, _path(gcCopyStringIfNeeded(path.canonical().abs()))
, _path_pad(0)
{ }
+3 -3
View File
@@ -6,7 +6,7 @@ namespace nix {
StorePath fetchToStore(
Store & store,
const SourcePath & path,
const CheckedSourcePath & path,
std::string_view name,
FileIngestionMethod method,
PathFilter * filter,
@@ -18,8 +18,8 @@ StorePath fetchToStore(
return
settings.readOnlyMode
? store.computeStorePathForPath(name, path.path.abs(), method, HashType::SHA256, filter2).first
: store.addToStore(name, path.path.abs(), method, HashType::SHA256, filter2, repair);
? store.computeStorePathForPath(name, path.canonical().abs(), method, HashType::SHA256, filter2).first
: store.addToStore(name, path.canonical().abs(), method, HashType::SHA256, filter2, repair);
}
+1 -1
View File
@@ -13,7 +13,7 @@ namespace nix {
*/
StorePath fetchToStore(
Store & store,
const SourcePath & path,
const CheckedSourcePath & path,
std::string_view name = "source",
FileIngestionMethod method = FileIngestionMethod::Recursive,
PathFilter * filter = nullptr,
+3 -9
View File
@@ -263,9 +263,7 @@ std::optional<Hash> Input::getNarHash() const
std::optional<std::string> Input::getRef() const
{
if (auto s = maybeGetStrAttr(attrs, "ref"))
return *s;
return {};
return maybeGetStrAttr(attrs, "ref");
}
std::optional<Hash> Input::getRev() const
@@ -286,16 +284,12 @@ std::optional<Hash> Input::getRev() const
std::optional<uint64_t> Input::getRevCount() const
{
if (auto n = maybeGetIntAttr(attrs, "revCount"))
return *n;
return {};
return maybeGetIntAttr(attrs, "revCount");
}
std::optional<time_t> Input::getLastModified() const
{
if (auto n = maybeGetIntAttr(attrs, "lastModified"))
return *n;
return {};
return maybeGetIntAttr(attrs, "lastModified");
}
ParsedURL InputScheme::toURL(const Input & input) const
+5 -4
View File
@@ -20,6 +20,7 @@
#include <string.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>
using namespace std::string_literals;
@@ -132,7 +133,7 @@ std::optional<std::string> readHeadCached(const std::string & actualUrl)
// This function must behave the same way, so we return the expired
// cached ref here.
warn("could not get HEAD ref for repository '%s'; using expired cached ref '%s'", actualUrl, *cachedRef);
return *cachedRef;
return cachedRef;
}
return std::nullopt;
@@ -166,7 +167,7 @@ WorkdirInfo getWorkdirInfo(const Input & input, const Path & workdir)
.program = "git",
.args = { "-C", workdir, "--git-dir", gitDir, "rev-parse", "--verify", "--no-revs", "HEAD^{commit}" },
.environment = env,
.mergeStderrToStdout = true
.redirections = {{.from = STDERR_FILENO, .to = STDOUT_FILENO}},
});
auto exitCode = WEXITSTATUS(result.first);
auto errorMessage = result.second;
@@ -694,7 +695,7 @@ struct GitInputScheme : InputScheme
auto result = runProgram(RunOptions {
.program = "git",
.args = { "-C", repoDir, "--git-dir", gitDir, "cat-file", "commit", input.getRev()->gitRev() },
.mergeStderrToStdout = true
.redirections = {{.from = STDERR_FILENO, .to = STDOUT_FILENO}},
});
if (WEXITSTATUS(result.first) == 128
&& result.second.find("bad file") != std::string::npos)
@@ -759,7 +760,7 @@ struct GitInputScheme : InputScheme
.args = { "-C", repoDir, "--git-dir", gitDir, "archive", input.getRev()->gitRev() },
.captureStdout = true,
});
Finally const _wait([&] { proc.wait(); });
Finally const _wait([&] { proc.waitAndCheck(); });
unpackTarfile(*proc.getStdout(), tmpDir);
}
-1
View File
@@ -7,4 +7,3 @@ Description: Lix Package Manager (libfetchers)
Version: @PACKAGE_VERSION@
Requires: lix-base lix-util
Libs: -L${libdir} -llixfetchers
Cflags: -I${includedir}/lix/libfetchers
-1
View File
@@ -7,4 +7,3 @@ Description: Lix Package Manager (libmain)
Version: @PACKAGE_VERSION@
Requires: lix-base lix-util
Libs: -L${libdir} -llixmain
Cflags: -I${includedir}/lix/libmain

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