Compare commits

..
Author SHA1 Message Date
Jade Lovelace fd38f625e6 release: 2.94.0 "Açaí na tigela"
Release produced with releng/create_release.xsh

Change-Id: If2ac30e569759c9035a6fc999c631ec545d190fa
2025-11-17 11:42:19 -08:00
Jade Lovelace 49a5495cbc release: release notes for 2.94.0
Release created with releng/create_release.xsh

Change-Id: Id7c776dc0ed46b1f6f09db2c77e04166469f5710
2025-11-17 11:42:12 -08:00
Jade Lovelace b6a8e312e7 2.94: release name
Change-Id: I18f5abe00130b53053e3a04074617071c0f884a2
2025-11-17 11:41:57 -08:00
WeetHet 7d46e9db7e libstore/build: extend DNS configuration access in network sandbox
https://github.com/NixOS/nixpkgs/pull/451579 PR
enabled c-aresSupport for curl on darwin which ended up breaking
DNS resolution in FOD sandboxes:

```
nix-run> exporting https://tangled.org/@weethet.bsky.social/nix-run (rev 73d7bf6b58848fb8f42e3a69816e0847f041c689) into /nix/store/m4m951648wmipxgwrgsml9gzjwfpfhm7-nix-run-73d7bf6
nix-run> Initialized empty Git repository in /nix/store/m4m951648wmipxgwrgsml9gzjwfpfhm7-nix-run-73d7bf6/.git/
nix-run> fatal: unable to access 'https://tangled.org/@weethet.bsky.social/nix-run/': Could not resolve host: tangled.org (Could not contact DNS servers)
nix-run> fatal: unable to access 'https://tangled.org/@weethet.bsky.social/nix-run/': Could not resolve host: tangled.org (Could not contact DNS servers)
nix-run> fatal: unable to access 'https://tangled.org/@weethet.bsky.social/nix-run/': Could not resolve host: tangled.org (Could not contact DNS servers)
nix-run> Unable to checkout 73d7bf6b58848fb8f42e3a69816e0847f041c689 from https://tangled.org/@weethet.bsky.social/nix-run.
```

with these sandbox failures:
```
deny mach-lookup com.apple.SystemConfiguration.DNSConfiguration
deny file-read-metadata /private/etc/hosts
deny file-read-data /private/etc/hosts
```

We allow those so that DNS resolution work fine

Change-Id: I9102293691972feb085adf8e9b1ad915bb3a36ab
2025-11-17 19:37:08 +00:00
Raito Bezarius 7e193f962e libstore/filetransfer: support HTTP/3 transfers if the user requests it
This adds the enablement code to support HTTP/3 if the user requests it.

We leave it disabled because h3 is not onpar with h2 performance.

Change-Id: I1fd3d4c97b972dcf36bccacc6c9a8290e22b31e0
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-11-17 19:11:32 +00:00
adam fb4cc15f39 chore: update nixos channel URLs
Channels have moved from `nixos.org/channels` to `cnannels.nixos.org`.
This udates all relevant links (excluding release notes) to use the new
canonical URLs and replaces HTTP with HTTPS.

Fixes #1031.

Change-Id: I212821c44ac5e482c8e9eaa415c7d8ee17ff8341
Signed-off-by: adam <me@adamperkowski.dev>
2025-11-15 18:21:47 +00:00
Alois Wohlschlager 243836ed0a libutil: remove the no-url-literals experimental feature
URL literals were deprecated in 278fddc317,
effectively stabilizing the experimental feature. Enabling it has no effect any
more. Remove it.

Change-Id: I8fbca03b1a2be6a8cddfce644043ac0a6a6a6964
2025-11-15 15:54:39 +01:00
Raito Bezariusandeldritch horrors 5fd9c65a8f doc/manual/known-issues: init
We start this section with shortcomings of unsandboxed builds.

Fixes #1018.

Co-authored-by: eldritch horrors <pennae@lix.systems>
Change-Id: Ieb17e4340beab0c1197951813ae602de453a3fd9
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-11-14 21:36:48 +01:00
EmilyandQyriad 82463616f9 libstore: skip the nested build directory on Darwin
This is unnecessary because Darwin builds already cannot create
`set{u,g}id` files due to the minimal sandbox policy, because we can
forbid messing with the top‐level build directory directly in the
sandbox rules, and because Darwin builds can trivially avoid isolation
through temporary directories right now anyway.

This does regress the build directory isolation for builds with
`sandbox = false`, but I can’t imagine that mattering given the
above. The sandbox change prepares us for a world where we close
off shared temporary directories for `sandbox = true` builds and try
harder to achieve proper isolation on Darwin, but probably doesn’t
have a meaningful security impact one way or another for now.

With this change, we get down to 41 byte build directory paths on
Darwin, a ~2–3½ byte improvement over the old status quo. We can
also restore the 0710 permissions on Linux.

Change-Id: I6a6a6964a681c0365241fe7234831db656b76799
2025-11-14 14:58:15 +01:00
EmilyandQyriad e87e547a36 libstore: default to /nix/var/nix/b for build-dir
The minimum build directory length on Darwin with default settings
when we were still using `/tmp` was 22 bytes. Deriving build directory
names from my local store, the median and mode were 43, the mean was
around 44½, and the maximum was 127.

The switch to `/nix/var/nix/builds` over `/tmp` added a 15 byte
penalty, and the additional `/b` directory added another 2.

Now that we use opaque build directory names, the length is 48, so
we’re still at a ~3½–5 byte penalty over the previous status
quo. This change brings us down to 43, matching the previous median.

Note that these calculations do not take into account the fact that
`/tmp` is a symbolic link to `/private/tmp` on Darwin. Anything
that was canonicalizing paths would have had an additional 8 byte
penalty in the previous status quo that is not applicable here,
so we may already be ahead even without this change. If the more
opaque directory name here is undesirable, then that factor could
potentially help us squeeze by without. Alternatively, in combination
with dropping the `/b` on Darwin we could use `/nix/var/nix/bld` or
similar, but I feel that the paths in general are sufficiently opaque
that it should be okay to go with the shorter option here. Given that
some projects already had to reduce filename lengths to avoid this
limit even before the recent changes, I think it is best to try and
improve on the previous status quo.

Note that `/nix/var/nix/builds` will unfortunately not be cleaned up
on Darwin. However, we don’t clean up the directories inside it on
Darwin anyway, so hopefully that’s okay for now?

Closes: #913
Change-Id: I6a6a6964bffce7194bcddcaefb4c4a37569c7df5
2025-11-14 14:58:15 +01:00
EmilyandQyriad 13a37f1246 libstore: don’t include derivation names in build directories
They have variable size, which is bad for #913.

Change-Id: I6a6a6964870e984c66277c7556ff3c2bc34ddca1
2025-11-14 14:58:15 +01:00
EmilyandQyriad 1102cc180a tests: remove obsolete code to create custom build directories
We now do in fact do this and decide this.

Change-Id: I6a6a696493b288ed76d809122bda259dc0225846
2025-11-14 14:58:15 +01:00
EmilyandQyriad ea0a2c8e74 libstore: make temporary path prefixes optional
This is not the same thing as passing an empty string, because it
avoids the `-` separator.

Change-Id: I6a6a696451667cbf500914e2dfbca2a4646ff20b
2025-11-14 14:58:15 +01:00
EmilyandQyriad c5221e508f libstore: simplify createTempDir interface
We always use the default temporary directory, because
`createUniqueDir` has an interface nice enough to use directly for
the few bespoke uses.

Change-Id: I6a6a696450b7c0a0bd76655632fb14d7c5e38199
2025-11-14 14:58:15 +01:00
EmilyandQyriad b1cfb58187 libutil: use makeTempPath in createTempSubdir
This makes the paths more nondeterministic, but more reliably unique,
and lets us remove the retry loop.

Note that this adds random entropy to the build directory visible
inside derivations on Darwin and unsandboxed Linux. It was already
non‐deterministic in the presence of concurrent builds and similar,
but now we can reliably expect it to be different every time. On the
whole I think that’s a good thing, as it is impossible to ensure
a single consistent build directory and derivation outputs should
not depend on it.

Package reproducibility isn’t great on Darwin to begin with,
though, and the reproducibility bugs this will turn up in packages
will be more urgent to fix than when the build directory was mostly
consistent. A quick survey of my local store shows that many C, C++,
and Rust binaries contain build directory references, likely due to
use of `__FILE__` and its equivalents; non‐binary offenders include:

* Install logs included in the Rust and Cargo bootstrap compilers
* Example errors in the Rust documentation referencing build paths
* Configuration information installed with CPython itself
* Python 2 metadata from resholve’s closure
* Cython metadata
* Generated headers in Facebook libraries referencing source paths
* Generated CMake files in Facebook libraries referencing source paths

I haven’t built that much in this store since the last GC, so this is
probably only a small sample of the problems across the tree. These are
all instances of <https://reproducible-builds.org/docs/build-path/>,
though, and should probably just be treated as general reproducibility
bugs outside of contexts like the Linux sandbox where we can normalize
them away entirely.

I have implemented away build directory paths for C/C++, applied some
additional fixes for non‐`__FILE__`‐related issues in binaries
from ATF and LLVM, and fixed the derivation bug causing the CPython
3 issue, and will work on upstreaming these changes. Rust is working
on the problem upstream, with some temporary workarounds we can
potentially apply in Nixpkgs for now. The rest will require some
distributed effort.

Change-Id: I6a6a69645b4915c56c0fdef904aa81684e4136c6
2025-11-14 14:58:15 +01:00
EmilyandQyriad 6e7212bad7 libstore: simplify fallback build directory logic
This does change the behaviour when the global temporary directory
does not exist, but other uses of the global temporary directory are
already broken in that circumstance, and it should be fixed centrally
if the use case is considered desirable. The logic was not present
before the recent churn around build directories – it was added now
that Lix is taking ownership of the build directory in the store –
so this should not be a meaningful regression.

Change-Id: I6a6a6964e345ea6803226c5ad759e836de7cb0ed
2025-11-14 14:58:15 +01:00
EmilyandQyriad d23bf515a7 libstore: use makeTemp{,Sibling}Path more
Change-Id: I6a6a6964c885be6dea0a69ee3162fbf4b812471f
2025-11-14 14:58:15 +01:00
EmilyandQyriad 0b2fcd1bcd libstore: simplify makeTemp{,Sibling}Path callers
There is now no risk of race conditions on a system with a functioning
entropy source, and the bespoke prefixes are either redundant to the
default or unnecessary.

Change-Id: I6a6a69641211c6bb979ea48ad30aecb1a53d03f0
2025-11-14 14:58:15 +01:00
EmilyandQyriad d03b1eca57 libutil: use OS‐provided entropy for temporary filenames
Relax the constraints on keeping the exact same filename format to
provide a more robust source of entropy with a simpler interface
(as previously suggested by eldritch horrors). Using 128 bits of
OS‐provided entropy ensures global uniqueness and allows us to
skip any thought of gracefully handling the case where these files
already exist.

My microbenchmark that repeatedly constructed paths like this and
printed them out showed that this takes about 1.23× the time of
the previous implementation, both taking on the order of a couple
microseconds for one iteration. Since everything that uses it is doing
things more expensive than printing to standard output, the actual
performance delta is likely to be lost in the noise. If it somehow
becomes a bottleneck, it can be optimized without sacrificing the
guarantees by reading from the system RNG only to seed a thread‐local
CSPRNG like [ChaCha8Rand], but I think that’s very unlikely.

We also tweak the recommended way of creating a temporary file inside
a directory in anticipation of later changes, and rename the `suffix`
parameter to `prefix` (it’s a prefix to the random characters and
a suffix to the root, but this way is more consistent).

[ChaCha8Rand]: https://c2sp.org/chacha8rand

Change-Id: I6a6a69648502c746d13d8c3bd2768cbbf1b90466
2025-11-14 14:58:15 +01:00
EmilyandQyriad 9f3ba30783 libutil: extract Base32 helpers from Hash
base32Encode now takes std::span<std::byte>, with a base32EncodeStr
convenience wrapper which takes std::string_view.

Co-authored-by: Qyriad <qyriad@qyriad.me>

Change-Id: I6a6a6964f799dc84ecbfb55c7ca03a064cff71d9
2025-11-14 14:58:15 +01:00
eldritch horrors 2fa40c9de4 libstore: bring back some pointer equality nonsense
the change to shareable thunks also removed a few cases of pointer
equality checks that allowed structures containing functions to be
considered equal to other sets containing the same functions, even
if the sets themselves were pointer-equal themselves. *so* busted.

Change-Id: If87fdab658f9037ce2a654f69a9e3da6ae2f53e5
2025-11-14 02:56:53 +00:00
Qyriad 78bdc60c11 libutil: refactor Pos::getSource to Origin
Change-Id: I1501fceb9ff1e768c8f5c45b65d5638568babf63
2025-11-12 10:40:21 +01:00
Qyriad 3ad1af8a98 libexpr: add trace for if 'foo' errors in 'or foo'
Change-Id: I59365e4c1bf4aed71c008554eca0c9e6d085219d
2025-11-07 14:54:02 +01:00
Qyriad 2806613954 libexpr: refactor ExprSelect::eval
Change-Id: Ibb21dbd3c25649b3b301bc7baaa52a8aa938ebe0
2025-11-07 14:54:02 +01:00
Qyriad 2e7bfa4474 libutil: impl <=> for Pos::{Stdin,String}
Change-Id: Ieb5016a261f5a90bbf08b2afb69bb1c288d07ca7
2025-11-07 12:07:23 +01:00
Qyriad f39cbc5d60 libutil: replace Pos operator< with <=> and constexpr ==
Change-Id: I60080c416b2ee2b0efce4584cf13f37c0596d73d
2025-11-07 12:07:23 +01:00
Qyriad f00d720d83 libutil: refactor trace types and functions into their own file
Change-Id: I00fba6b382991322a083bedeaf169eb5a86f5aeb
2025-11-06 19:19:36 +01:00
eldritch horrors 1e386c3780 libstore: add usage hints to repairPath on daemons
we can't change the protocol to allow daemons to do this, and we should
not try to guess what the `auto` store uri means depending on whether a
command was run by root or not due to copious side effects and not even
being able to tell whether the `auto` store uri was given explicitly or
not. while `auto` may *technically* allow this via its naming we should
resist the urge to add a hack and fix the underlying protocols instead,
especially since repairPath should be a rare, superuser only operation.

fixes #888

Change-Id: I1b53245db226199f827a89a237a2ab9907c3f766
2025-11-06 15:10:34 +01:00
eldritch horrors 24054c1107 libutil: restore process context before subcommand exec
fixes #1028

Change-Id: Ic50b9cc0c65607cd96dd81fd770cda34b4caf9d5
2025-11-05 13:56:09 +00:00
eldritch horrors 0d24aee673 testing: drop compression from truncated nar test
some versions of zstd enable checksumming, some don't. don't use it.

fixes #1027

Change-Id: I06811072d9e56a682a792f084cf76a81c7b5b4ce
2025-11-05 00:02:10 +01:00
eldritch horrors f369c711dd libutil: prefer reporting libarchive source errors
if curl fails during a transfer and said transfer is piped through
libarchive we see very strange errors (#1009). in such cases we'll
want to prioritize reporting the source errors to allow debugging.

Change-Id: I8cc231257eca5b749471ecd38df15071170decc2
2025-11-03 23:35:42 +00:00
hexchen 57658b2146 tests/functional/daemon-trust: fallback to uid
The test would fail if the identity cannot be determined
(as is the case e.g. when using auto-allocate-uids under darwin).
Instead we use the uid when the user name is not available.

Fixes: #1025

Signed-off-by: hexchen <hexchen@lilwit.ch>
Change-Id: I12f35840b78f989b5fd09bbd44c833dc6a6a6964
2025-11-03 18:24:07 +01:00
Raito BezariusandQyriad d4f404ded3 nix/doctor: test whether the current profile generation points somewhere
This helps users to debug whether their current profile symlinks are
correctly set.

Expected outputs look like this:
```
❯ sudo ./outputs/out/bin/nix doctor
[snip]

[PASS] All profiles are gcroots.
[PASS] Client protocol matches store protocol.
[INFO] You are trusted by store uri: local
[FAIL] Error: current generation cannot be discovered for profile:
'/nix/var/nix/profiles/default'
```

```
❯ ./outputs/out/bin/nix doctor
[snip]

[PASS] All profiles are gcroots.
[PASS] Client protocol matches store protocol.
[INFO] You are trusted by store uri: daemon
[PASS] You have 28 generations for profile
'/nix/var/nix/profiles/per-user/raito/profile'
The current generation number is '290'
```

Change-Id: I50c69cbeac3291d668f4c2332803411579adc944
Signed-off-by: Raito Bezarius <raito@lix.systems>
Co-authored-by: Qyriad <qyriad@qyriad.me>
2025-11-03 14:41:47 +00:00
Qyriad a30d87eadc nix/doctor: skip inaccessible PATH elems for duplicate nix
Inaccessible-but-extant PATH elements can't be executed anyway, so we
can skip them as if they didn't exist, instead of erroring like before.

Change-Id: I9288c3ecb6768171e62a3088122d98421558eb03
2025-11-03 14:41:47 +00:00
Qyriad d15b99f489 libstore/static-init: don't crash if nixBinDir exists but isn't accessible
In d2696cdd¹, libstore was made to prioritize NIX_BIN_DIR/nix (over
/proc/self/exe) if it exists. But `pathExists()` throws an exception if
lstat() returns any error other than non-existence.

Because this check happens in libstore's Settings constructor for the
static-initialized global `nix::Settings`, this case would abort Lix
well before main().

[1]: d2696cdd1e

Change-Id: I62e44c8a33b28c234900df3e4790c1badefee358
2025-11-03 14:41:47 +00:00
Commentator2.0 acf6e1c6b7 tests/functional2/file_helper: add a way to interpolate environment variables into files
So far it was impossible to put absolute paths into files, as one
wouldn't know whre the test would be placed.
This commit adds a new Fileish variant called `EnvTemplate` which uses
the given string as a template and replaces the `@ENV_VARIABLE_NAME@`
placeholders with the according value of the environment variable.
This way one can use `@HOME@` or `@TEST_ROOT@` to build absolute paths

Change-Id: I425cb6408dceb8d7f26d136ace4ac98b1ca31ec3
2025-11-03 12:58:37 +00:00
Raito Bezarius 85db5e3bf4 libstore/store-api: rate-limit concurrent copies based on system limits
Since we have async, we are unleashed and therefore we started to cause
people to run into system limits like number of open files.

We introduce an async semaphore which uses max(25 % of max open files,
max number of cores) which should lead to 256 maximum concurrent copies.

We believe that the copy operation is the only one that can cause issues
given that the daemon already runs with obscene fd limits.

Fixes #1022.

Change-Id: Iec433d10d5c5003962ea749cf8e32bafb314f0d9
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-11-01 23:47:13 +00:00
eldritch horrors f5ae0d1eff libutil: fix EBADF when cancelling decompression streams
relying on the sink/source code is a mistake. no exceptions.

Change-Id: Ied4ffc4065ce628ed7f8397526cdb68c8ac652d8
2025-11-01 20:42:12 +00:00
Raito Bezarius df862c1655 libstore/s3: attach more information to error messages
In case of empty messages, it is good to print the raw error code.

Additionally, we print request IDs which can help users to reconcile
what happened with the service provider.

Change-Id: I4d83c011c1b7a5514e3d1b21123df38308279044
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-11-01 21:17:26 +01:00
Raito Bezarius ccf196d7f4 libstore/s3: resolve completion status via the transfer status callback
Completion status can be updated in two ways:

- progress callback
- transfer status update callback (esp. important in multipart
transfers)

It's guaranteed that one of the two will provide progress for our
promise.

This fixes #945.

Change-Id: Iac9e92df34f82fbc1facae7a411ccabf1688da52
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-11-01 21:17:26 +01:00
Raito Bezarius 91867941fa libstore/s3: sign payloads if the request demands it
This fixes problems with S3 implementations that have mandatory payload
signing, e.g. Garage v2, AWS S3 itself, etc.

The problem manifested itself when the AWS SDK threw some error codes 99
(NETWORK_CONNECTION) with no messages and retried until it finally came
to a moment where the state machinery decided to send a
`Transfer-Encoding` header in a `Content-Encoding`/`Content-Length`-set
request with signed headers (even though payload signing is disabled),
causing the server to reject the transfer and crash the copy.

I did not debug super far what went wrong in AWS SDK, but I can confirm
this change makes transfers possible to finish with Garage v2.

Change-Id: Icc9e6a9f2afb0d760cf2d1e27816decd385a1d85
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-10-31 20:01:14 +01:00
Commentator2.0 9ed75192ec tests/functional2: fix crash on empty nix.nix([]) calls on darwin
Calling `nix.nix` with an empty argument list, would crash on darwin due
to a Index Out Of Range Error, as the build="auto" functionallity would
try to access the first element of the provided arguments (second
element at the place of call) resulting in a crash

Change-Id: Ia678109808b3f75a30182114baa421cedaa8759b
2025-10-30 18:53:11 +00:00
Raito Bezarius 95f15cf94f libstore/binary-cache: default to zstd for compression
libarchive's xz offers single threaded xz compression which is very slow
and provides ~10-20Mbps compression speed in addition to maxing a core.

In exchange, it achieves optimal compression ratios among all our
compression methods.

Nonetheless, xz prevent the saturation of 1Gbps+ connections and slow
down significantly decompression for end users. As these connections and
faster hardware is becoming prevalent for cache servers and clients, we
offer to default to zstd.

Lix is a "compress once, decompress many times" application. To avoid
incurring a high penalty to end users very sensitive to compress ratio
(very slow Internet connections), we dampen the consequences of
switching to zstd by increasing the default zstd level to 12.

On one example, xz will compress a 4.4GB file to 632MB, zstd on 12 will
compress it to 775MB, that is a ~18 % increase over the optimal xz
compression. zstd took 18 seconds to produce this file.

Increasing to level 14 leads to a 773MB file while taking 37s.
Increasing to level 16 leads to 735MB file while taking 66s.

Finally, xz took 77s, so a 50 % reduction in time taken to compress in
exchange of an increase of 18 % of the compressed size.

This change will reduce issues encountered in #945 but is probably not
the root cause.

References:

- https://discourse.nixos.org/t/switch-cache-nixos-org-to-zstd-to-fix-slow-nixos-updates-nix-downloads/23961

Change-Id: I7beda2bf2c1fed146dcb797b8f85dc290c486ab2
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-10-30 15:10:10 +01:00
eldritch horrors 047aad9992 libstore: fix s3 promise fulfiller lifetimes
if a promise for an async s3 operation is cancelled is inner fulfiller
will be destroyed, but the s3 operation may still be running. once the
operation finished and attempted to fulfill the promise we'd segfault.

Change-Id: I603c75d6cc39a1aee465b3ceee06b638f7f7f596
2025-10-30 13:57:15 +01:00
Raito Bezarius 1f08eaeb55 doc/manual: provide more information about Pasta and its shortcomings
As we saw actual users running into them throughout the debugging of
#920 and #1014.

It's best to document and point end users to this so they can take
measures for themselves.

Change-Id: I7d08407f4354055bf65fc6dd7d1624c5a9304402
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-10-30 00:04:21 +01:00
Raito Bezarius 480c8e52b6 libstore/linux/pasta: always list an IPv4 & IPv6 nameserver
Fixes #1014.

Address family detection is performed by Pasta, we influence Pasta into
enabling dual stack by listing both an IPv4 & IPv6.

Change-Id: I4121e0eae7d5185c287957106adc36edf3f82a40
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-10-29 21:44:12 +00:00
Raito Bezariusandeldritch horrors 12b87538e3 lix/nix-shell: cleanup of shell directories happens in Lix itself
Instead of letting inner shell perform the cleanup, we will fork/execvp
and finish the work ourselves. Once an interruption arrives, we wait
again and return the exit status code of the inner shell.

This idea was suggested by eldritch horrors.

Fixes #1020.

Co-authored-by: eldritch horrors <pennae@lix.systems>
Change-Id: Id38af7ac33874aa1c4b7ca9c6123e3cbd8c3cd8c
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-10-29 20:13:23 +01:00
Raito Bezarius f6d723b22f lix/main: return the legacy command statuses
This was forgotten which should have been part of 64b9247f.

Change-Id: I3cf83d8699f2c1963ce530bbea6a511075202a79
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-10-29 18:53:00 +01:00
Raito Bezarius 64b9427f98 legacy/nix-build: return exit codes
Legacy commands "main function" will now return an exit code.
Fun fact: `main_nix_build` was the only one not returning integers
already.

Change-Id: Ia43a16c3c3fb9a670e8889aefc4ee9b6528a7df4
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-10-28 20:38:39 +01:00
Raito Bezarius 56b4ed3908 libmain/main: propagate status codes via return values for mainWrapped
This also prevent `legacy` to return nothing as it's void.

Change-Id: Ic797544a59b04b41ad9e1c46af4cad3a1cb6fdc8
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-10-28 20:38:39 +01:00
eldritch horrors 2c176afa7a libutil: don't allow interactive runProgram2
realistically only runProgram is ever called for interactive reasons,
and even those calls seem to be rather ill-advised in many cases. the
chance of multiple interactive processes interfering with each other,
whether for input or for output, must be very low to make this in any
way reasonable: if e.g. git calls ssh for multiple fetched inputs and
ssh requests passphrases for both we can otherwise not guarantee that
*any* input is routed correctly. misrouted output is merely annoying.

Change-Id: I794e3fdf0a3238cb9292003a89ac267f0de7a939
2025-10-28 11:53:33 +00:00
Qyriad 9523446901 libexpr/libutil: summarize derivations in stack traces regardless of --show-trace
Eval errors will now print a simple, no frills chain of involved
derivations at the bottom. For example, trying to evaluate
`pkgs.xonsh.override { python3 = pkgs.python2; }` has the usual

Package ‘python-2.7.18.8’ in /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/pkgs/development/interpreters/python/cpython/2.7/default.nix:398 is marked as insecure, refusing to evaluate.

message, but now also includes the following:

       note: trace involved the following derivations:
       derivation 'xonsh-0.19.9'
       derivation 'python2.7-xonsh-0.19.9'
       derivation 'python2.7-setuptools-44.0.0'

To give the user information about why the erroring derivation was
involved in the first place.

We would like more structured information in the future, but this should
still be a significant improvement.

Change-Id: Icf6da52abd0a043cfb63943bf0b0c160c21ee59e
2025-10-27 20:28:27 +01:00
Commentator2.0 73c019c57b tests/functional2/lang: allow adding requiring of global assets like config.nix
Change-Id: Ia740b91fb3d18f7d01c0d551196b71e7f3e46384
2025-10-27 17:07:47 +01:00
Qyriad 836caf1173 libutil: add concepts for constraining iterables ("ranges")
Change-Id: I5d75abb9d3713a3fab863666786aa8d38cc93dbd
2025-10-27 13:16:29 +01:00
Qyriad b8462562f2 libutil: allow moving Trace into Error
`pushTrace` now move-constructs or copy-constructs, instead of always
copy-constructing.

Change-Id: Ie5f02643e98b7a6c1adbe58b0d46d9e116eba28e
2025-10-27 13:14:50 +01:00
eldritch horrors 18efc848fe libstore: move curl-multi wrapper into own class
the wrapper is needed by transfer streams to restart a failed transfer
if desired. curlFileTransfer itself is more of a fancy handler for the
thread we're dedicating to curl io handling. the thread will stay with
the multi handle for now because quit handling needs to stay there. we
could have CurlMulti keep only a flag, but that does not help us much.

Change-Id: I99550f0bbb635b75898ca7260f08275df86050e3
2025-10-23 22:52:09 +00:00
eldritch horrors b460a7a38b libstore: move curlFileTransfer::TransferItem up
we have anonymous namespaces and no other scoping requirements, we can
move it out. this will also make it easier to move *other* things out,
such as the transport streams and all the shared multi state handling.

Change-Id: Iafa1eaeb69e096e98c4667f0531b17d5fe7c78a6
2025-10-24 00:16:15 +02:00
eldritch horrors 253ed1cc05 libstore: abort all transfers when shutting down curl thread
waiting for them to finish leaves us prone to deadlocking, e.g. if some
active transfers is blocked and hasn't been destroyed before our thread
was asked to exit. aborting transfers is the safer option here; even if
we cause use after frees we'll at least have a crash, not hang forever.

Change-Id: I333dcced810e8a85e2ded1f377962a0176798098
2025-10-22 17:36:21 +00:00
Raito Bezarius e0d2fa89d3 libstore/build: resolve symlinks beneath settings.caFile
This behavior existed before I32f8b5ce20fe9b6a911768114c92f95fc886cc07
was introduced.

It was remarked in
https://gerrit.lix.systems/c/lix/+/3765/comment/1ff0b2b7_ae67ab1b/.

Change-Id: I7a72e7c36c6043a6c2cb8cab57ad45a552d5e2b6
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-10-22 13:23:48 +00:00
Alois Wohlschlager 7a0b8834a4 Reapply "functional2: fix in the development shell"
This reverts commit 3ef7e0711b.

Change-Id: I5b76d2c2e9b0dd669818c7218493d3e76a6a6964
2025-10-22 07:41:46 +02:00
Alois Wohlschlager 0e0853cc70 functional2: handle unset or empty BUILD_TEST_SHELL properly
If BUILD_TEST_SHELL is not set or empty, None or the empty string respectively
should not make it into the PATH. Ensure this property.

Change-Id: I4ce9b0c06c407b465308b63b9cb64e7d6a6a6964
2025-10-22 07:41:46 +02:00
eldritch horrors 5346b2bc68 libstore: simplify worker child handling a bit
we no longer need the optional wrapping.

Change-Id: I742cca23753c6e0a97ada0b301bb42f442803ea8
2025-10-20 12:43:22 +00:00
eldritch horrors 31b6eb2786 libstore: convert build-hook logging to rpc
Change-Id: I0c20f89de113dce6032c93a32e9fcd43b4478f55
2025-10-20 12:43:22 +00:00
eldritch horrors 5b7ed433d6 libutil: add logging rpc definitions
this is still somewhat experimental and should be considered in flux. we
will have to nail down a logger interface once we start moving the store
protocol to rpc, but until we do that we can use build hooks to test it.

Change-Id: Id20cd346c9520f45871799c31b0af040adde56ef
2025-10-20 12:43:22 +00:00
eldritch horrors 2d2cd7ac03 libutil: always rethrow kj::CanceledException
the fiber runtime requires this for correctness.

Change-Id: I5ebc6a046596403e86cebaa95962053ff7813877
2025-10-20 12:43:22 +00:00
eldritch horrors bab44b870e libstore: handle log size limiting in only one place
extracting the size-specific log handling into a new sink deduplicates
the size-limiting code and lets us clean up the derivation goal a bit:
a number of log-related fields we kept only because previous ownership
semantics were a lot more ad-hoc can be moved into the new sink class.

Change-Id: Ie770a276597c84f9d928a9624c0f0de86adfe815
2025-10-20 12:43:22 +00:00
eldritch horrors 4b9723a23f libstore: DerivationGoal::{handleChildOutput -> wrapChildHandler}
we'll want to remove handleRawChildStream from DerivationGoal, but the
timeout handling is still important. the wrapper is thus useful, but a
more generic way to pass it a promise to do the handling is necessary.

Change-Id: I55bf041d51e4f8a8b8291deae053423d06724329
2025-10-20 12:43:22 +00:00
eldritch horrors b02a66d13e libstore: simplify hook instance handling
we never need the connection or the rpc client directly after the hook
is constructed, only the hook instance rpc object. wrapping them using
kj primitives makes this more explicit and prevents destruction errors

Change-Id: I7e0b14d7e365c2e001273e04c0e741ba4afb12f2
2025-10-20 12:43:08 +00:00
eldritch horrors 6c416ff2ca libstore: make build hook exit status reusable
this makes it possible to wait for the hook to exit in one place,
process its exit status for some purpose, and later process it in
another place for a different purpose. rpc will need this to shut
down a hook cleanly after the hook has completed its assignments.

Change-Id: I5af2d5aac4b02a0a200d720d0e6f0b5df5496aaf
2025-10-19 16:09:32 +00:00
eldritch horrors 7e171a4abe libstore: localize act, builderActivities
only LocalDerivationGoal needs these, and once the child is done
running (or is begin torn down) we can just destroy all of them.

Change-Id: Id4d9f0105aaf33d6736c435bb891d3f9af8e7894
2025-10-18 21:33:54 +00:00
eldritch horrors 0365d17d7e libutil: disallow implicit ref downcasts
wtf?

Change-Id: I3dbce7052e72525c9430efb0f5090b168db04750
2025-10-18 21:33:54 +00:00
eldritch horrors 641769414b libutil: add a timed wait to async mutex
wrapping the wait in a timeout at the call site is insufficient because
a timeout would not relock the mutex and leave the raii object invalid.

Change-Id: I0be2aaf6b3c9966070fb0f1deaab048765fd7af3
2025-10-18 21:33:54 +00:00
Commentator2.0 2541db5321 tests/functional2/lang: fix expected skip message
it seems like pytest is updating the message printed when skipping a set
due to no parametrization
which leads to breakage on staging-next.
this commit changes the expected message to allow both old and new skip
message

Change-Id: I34d11ad84d7075911d8229fbc89889bb880a27c9
2025-10-18 17:19:01 +02:00
Commentator2.0 0ac71ea1cc tests/functional2/lang: fix wrong unused file error for generic test group collection
Change-Id: I1c548fcebe45d2a6621987091e3a852b8a9b5765
2025-10-18 11:24:00 +00:00
Commentator2.0 4fcfdc66fe tests/functional2: migrate debugger.sh
Change-Id: I82c5b739db09530ff216c91608be2f70a2036e11
2025-10-18 11:23:43 +00:00
helle e8d281eac6 doc: information at the use-cgroups option on conflicts it can give
as discussed on Zulip and with Lunaphied

Change-Id: I7c6b3e7994c1e7961033e3daf509c81348927baf
2025-10-18 09:51:36 +00:00
K900 6e2edbff93 flake: only apply the monotonic clocks patch to capnp<1.2.0
1.2.0 already includes it so building on unstable fails.

Change-Id: Iefa49203c371e5bab164c06b9da6f89a89ce7cfe
2025-10-18 12:04:06 +03:00
eldritch horrors a55fe35dac apply capnp patches again
This partially reverts commit f68233ec43.

while more modern capnp in *does* have the build system changes, it
does *not* have the patches. and those patches are rather important
to us; the clock patch because our CI machiens don't behave the way
capnp expect and the nodiscard patch because it *is* a bugsquasher.

Change-Id: Ic273777f09fd8e5e7001f815db8998dae2dd7b88
2025-10-17 19:21:17 +00:00
EmilyandQyriad 6ea1d8e37e libstore: use makeTempSiblingPath in replaceValidPath
Change-Id: I6a6a69641a3b4e6fdd076faac44dc314e6cc057e
2025-10-17 18:41:58 +00:00
EmilyandQyriad 39ebb982ea libutil: add makeTempSiblingPath helper
The prospective callers of this should probably be doing something
smarter or more abstracted to begin with, but this is useful as an
incremental improvement for call sites with existing `makeTempPath`
logic in the face of filename length limits.

Change-Id: I6a6a69644292f5bbf984a1df90192e06c6022b53
2025-10-17 18:41:58 +00:00
EmilyandQyriad d9eead0eac tests: add fetchTarball test for symlink temporary directories
Change-Id: I6a6a6964dfe85b5ee4f66b8c442fe448c7c5c87c
2025-10-17 18:41:58 +00:00
Qyriad 013d5e54e9 libstore: fix non-awaited calls to optimisePath()
LocalStore::optimisePath() was made asynchronous in 38b75b736¹, but we
lost warnings for not awaiting kj::Promise in f68233ec4², since our
capnproto no longer has the [[nodiscard]] patch.

[1]: 38b75b7367
[2]: f68233ec43

Change-Id: I2363396949f40dd43eae08cc76be9ede6b488cf7
2025-10-17 18:41:43 +00:00
eldritch horrors 20ba2b49fb testing: mock ssh during nix-copy prompt test
we don't need full ssh, we only need to know something makes it onto the terminal.

Change-Id: Ic31808e10e1b876b35d0dbdeee0b5ede7b5b9883
2025-10-17 15:13:37 +02:00
eldritch horrors ab9dddfb7a libutil: wrap async kj exceptions as errors
this is important for rpc error reporting: if the peer disconnects we
want to throw a useful error that can e.g. let pending builds finish,
not kill the entire process because kj errors are non-lix exceptions.

Change-Id: I41ebe10ad7fd65d886238ce52eeca1fa243b1bb2
2025-10-17 11:33:00 +00:00
eldritch horrors 21d7cb8e1c libstore: initialize build hooks via rpc
this removes another file descriptor from the build hook. we are now
using only rpc to communicate between build hook and daemon, setting
the stage for a future world in which we don't even need build hooks

Change-Id: I706d9d3f2a420abd42fc7762c205931a45d3c8c5
2025-10-17 11:33:00 +00:00
eldritch horrors 9b886e2acf libstore: remove unused DerivationGoal::machineName
interpolation of the remote store name into activity text is now done in
the build hook, so we no longer need to send it back to the daemon. this
also reduces the dependency of the daemon on how *exactly* remote builds
are done a little bit, and maybe in time we can make these fully opaque.

Change-Id: Ie51c4f2cd107350fec4ad0a8ef83621ed86db6a2
2025-10-17 11:33:00 +00:00
eldritch horrors 711ad52776 libstore: move logSize into each log handler
there's no need for this to be an instance variable of the goals.

Change-Id: Iaa315e8b5d8649d8eb3c3fedc387c0f0c48945fc
2025-10-17 11:33:00 +00:00
eldritch horrors 645a0533eb libstore: move sandbox log handling to LocalDerivationGoal
only local builds can now have a builderOutFD, remote builds only log
via json streams. these two kinds of logs have different requirements
and each is only needed by its respective build kind. splitting these
apart thus makes sense, though ideally we'd also split DerivationGoal
into a RemoteDerivationGoal to clean up the rest, but that will wait.

Change-Id: Ib577537266d1160355ab9c44b4604ebda87a7d04
2025-10-17 11:33:00 +00:00
eldritch horrors a824877d45 libstore: don't treat legacy ssh like a sandbox
treat it like ssh-ng instead, and have the build hook do the translation
of ssh stdout to the json log message steam the derivaiton goal can use.
since the ssh-ng path also handles all store urls that aren't legacy ssh
we now have a single logging system that handles every remote build kind
equally, without requiring fd passing into the build hook. this is later
required to rpc-ify the log stream emitted by build hooks to the daemon.

Change-Id: Ifb522eb8a9745029050f16b1b3b3601a6ddac748
2025-10-17 11:33:00 +00:00
Alois Wohlschlager ac8209404c nix-eval-jobs: estimate memory usage using the heap size
When the maximum memory size is chosen too large (relative to the free memory
usage), so that the evaluation workers don't fit into the free memory, they
will start being swapped out. Further increase in memory usage will not be
reflected in the RSS, so they will not exit, and instead fill up all swap too
until the system runs out of memory.

The BDW-GC keeps track of its heap size, including any parts of it that may be
swapped out. For this reason it does not suffer from a similar problem. While
not all memory usage by the evaluation worker is accounted for by the GC, a
simple affine model for the overhead works reasonably well in practice. The
specific parameter values were determined using nixpkgs evaluation, and appear
to work quite well also for somewhat different workloads (like the `hydraJobs`
of Lix, which include a bunch of NixOS system configurations). So this is what
gets used.

When Lix is configured without the BDW-GC, of course this approach cannot be
used. In this case the old strategy is retained.

Change-Id: I6cb5f0a9b4ceda9dd14be165dda108cd6a6a6964
2025-10-16 15:22:54 +00:00
Alois Wohlschlager ed9cc5f448 nix-eval-jobs: move more worker initialization code to its file
Change-Id: Ief8b16220c153e402ecbdeabd576344c6a6a6964
2025-10-16 15:22:54 +00:00
Alois Wohlschlager e36ac58f93 nix-eval-jobs: hide hidden flags in the --help
Right now there are no hidden flags so this does not matter, but this will
change very soon.

Change-Id: I6d65ce9c32170b6d4f4a691ff9f8806a6a6a6964
2025-10-16 15:22:54 +00:00
eldritch horrors a85115bd33 libutil: add a log line splitter
we'll need this in other places too soon.

Change-Id: I8ddd57aefa4cbd1f8f7232f35e30a204ace77e58
2025-10-16 12:31:08 +00:00
eldritch horrors daff1b148f build-remote: de-optionalize an always-set parameter
Change-Id: Ifd79229cf5f608e3cfe5532ab24b2a4efb547e15
2025-10-16 11:51:27 +00:00
eldritch horrors cac395ffbc nej: fix some clang-tidy lints
these are really annoying when running `just lint` with nej enabled.

Change-Id: I8d35e77fe9905fdd6c28f95361f7431459079b96
2025-10-15 20:27:28 +02:00
eldritch horrors 59b583b46d n-e-j: disable darwin ci
it's a disaster. cf #1015

Change-Id: I4671c1ea0f1b6b2750b97cf3c42497460ef33259
2025-10-15 13:37:18 +00:00
helle 30a5f8ec15 doc: Mention Buildkite in the hacking document
Includes link to the Buildkite SSO login, which is currently not visible
anywhere else in documentation.

Change-Id: Ic3ec6412127b2ac0df7e6efc4ec6020cc9d8b34f
2025-10-15 09:21:49 +00:00
Jade LovelaceandCommentator2.0 faa0b4f291 functional2/http_server: add some example misbehaviour routes
Part of https://git.lix.systems/lix-project/lix/issues/949.

This also fixes a bug where our SO_REUSEADDR wasn't done before bind was
called and thus didn't work as intended.

Co-authored-by: Commentator2.0 <lix@crystal-cavern.systems>

Change-Id: Ie69fd594a32d7a6f538fb66d9d2cac6fe117d563
2025-10-13 18:30:51 +02:00
Jade LovelaceandCommentator2.0 556012e409 functional2: forbid chdir and setting environment
These are a footgun and are not acceptable in functional2 due to thread
safety, effects on other tests, etc.

Co-authored-by: Commentator2.0 <lix@crystal-cavern.systems>

Change-Id: I8d7285061eaa9bab27edd52f3646024c8cf605e5
2025-10-13 18:30:51 +02:00
Commentator2.0 844feb17b5 tests/functional2/lang: fix duplicate collection when both err and out are provided
When providing both a `.err.exp` and a `.out.exp` for a lang test
**not** containing a toml, the test was collected twice and hence threw
a duplicate id error.
This commit adresses this issue

Change-Id: Ia781d69e0f1db2809a30192d6cd0a98861e6cc32
2025-10-13 14:33:25 +00:00
eldritch horrors 2e3d97eb37 libstore: move currentHookLine into handleHookOutput
Change-Id: I00cdc7fae1b953f2d541bc966d15afa00c04b06a
2025-10-13 11:26:49 +00:00
eldritch horrors 26e8e3caac libutil: allow builders to create all activity types
this restriction was added in 4af2611bd1
and extended later. it makes little sense to allow *one* activity type
specifically and drop all others on the floor, especially since broken
builders can create transfer activities with ids that would be used by
other, "real" activities. the only thing achieved here is to drop logs
written in json format in the build sandbox, which does not help much.

we may revisit this restriction later during other log-related rework.

Change-Id: I8bda494083877b71a2f958470fa52380f6ec4968
2025-10-13 11:26:49 +00:00
eldritch horrors b04521e4ec libstore: move log handling fully into handleBuilderOutput
it was split up like this only because the old worker system had no
promises. since we have them now we can use them for encapsulation.

Change-Id: Idbd523eff617d4c53c14b125ab3dfce4979cdab1
2025-10-13 11:26:49 +00:00
eldritch horrors 1210a4e025 libstore: flush log lines only from handleBuilderOutput
also change the tests to ensure that we handle all lines correctly.

Change-Id: I13de286bda8eee57acd53af1ab5c081d3048b500
2025-10-13 11:26:49 +00:00
eldritch horrors f29dfb3d3c libutil: add buffer state management to loggers
currently all loggers can always accept messages and never suggest
flushing buffers. in the future this may change, and at that point
we're already fully set up for it. local loggers should never keep
asynchronous (i.e. network-backed) buffers, disk buffers are fine.
networked loggers will require buffers and periodic flushes later.

Change-Id: Ide2114f5bc17f4a1d289c92ed4f9381a1d59dacf
2025-10-13 11:26:49 +00:00
eldritch horrors 38b75b7367 libstore: make store optimization non-recursive
this keeps the call stack flat even for very deep store directories.
moving progress reporting out of the method optimizing one path also
lets us move the inevitable IO wait and associated promise overhead.

Change-Id: I5083d88b05c247c7e19a21a646bcf3f62bfa3200
2025-10-13 11:26:49 +00:00
eldritch horrors 34b1f362cd libutil: wrap activity updates in macros
we'll eventually want to yield to flush buffers from the non-sync versions.

Change-Id: I8bcbb1c4c8af39c7d7ebf5a76e1ce1dc98067e00
2025-10-12 22:37:31 +00:00
eldritch horrors b73a7f1815 libutil: clean up the Activity interface a bit
create activities from loggers themselves instead of passing the logger
as a constructor argument and allow direct construction of children, no
direct logger access needed. most call sites are not changed because we
still need to handle the "no parent" case, and the logger method can do
that more cleanly than a ternary at each site that creates an activity.
we may eventually want to create a root activity, which is cleaner too.

Change-Id: I295e056228dabb08a1316eba7973874784baa113
2025-10-12 22:37:31 +00:00
eldritch horrors 47000b658e libutil: don't trust RemoteStore activity ids
remotes can generate the same activity ids we might generate locally,
especially if the remote is accessed over the network. in that case a
pid collision is possible, and since activity ids are sequential it's
very possible to create colliding activity ids on both sides as well.

Change-Id: Id58074a41f5f7a59171b52818d1fb5a1beb4bf40
2025-10-12 22:37:31 +00:00
eldritch horrors ed034d0526 libutil: make Activity a real, move-only class
activities are scopes, and there's no reason scopes should not be able
to move around. this also means we can *create* them elsewhere without
also having to box them in some way, making parent relationships clear

Change-Id: I5df036e12ebd8270feb4dca1f23b6bee1f08e906
2025-10-12 22:37:31 +00:00
Lunaphied e9f6baedd1 libstore: add an informative message about disabling cgroups
Change-Id: I0271eff2f7a357af18ddd6ae0571d548a9adbe3e
2025-10-12 20:12:33 +02:00
Commentator2.0 d9c1c0925c unify version string between lix and nixos-module
Currenlty `lix` and `nixos-module` have slightly diverging version
numbering. This commit fixes the lix side of things, in accordance to
the format proposed in
https://git.lix.systems/lix-project/lix/issues/585#issuecomment-7336
which allows for better use of `builtins.compareVersions`

fixes #585

Change-Id: I2072b701f9fadf780e7aab67b62fbcd2ec5df76d
2025-10-11 18:47:46 +00:00
eldritch horrors b88a6e6f11 libexpr: use pascal strings for eval
this has no performance impact in any benchmarks we've run. nul bytes
are still used as implicit truncation points in many places all over:
rejecting them in all locations that treat them as a string end point
requires large changes such as using a proper path library everywhere

Change-Id: I936158bd435f6abf009a689adfbc24496262c578
2025-10-11 12:57:57 +02:00
eldritch horrors 53d172a308 libutil: remove unused CanonPath methods
Change-Id: Id593e0d03373af88e03a9da473935e5c00f86b33
2025-10-10 16:55:34 +00:00
eldritch horrors 2f16cf6014 treewide: lint syscalls for c string safety
Change-Id: I3caf476e59dcb7899ac5a3d83dfa3fb7ceaaabf0
2025-10-10 16:13:54 +00:00
eldritch horrors 3ef7e0711b Revert "functional2: fix in the development shell"
This reverts commit 42691f0d94.

Reason for revert: darwin builds time out now, apparently due to newly appearing infinite loops in the test suite

Change-Id: I81a09efd53636a287f3095ac87ed8854b6e57922
2025-10-10 12:49:45 +00:00
Commentator2.0 5e2412ea7e tests/functional2: migrate add.sh
Change-Id: I699929b61db19a4ab7bea8281df830de9fea6d04
2025-10-08 13:09:17 +02:00
Alois Wohlschlager 42691f0d94 functional2: fix in the development shell
The functional2 test suite was broken in the development shell for two mostly
separate reasons leading to "no such file or directory" errors:

* The `BUILD_TEST_SHELL` (already containing the correct path) would always be
  set from the Meson option `build_test_shell`, even to the empty string if
  that option is not set. Skip the overwrite in this case to make the
  environment variable work again.
* Sandboxed builds would fail to find their builder `/bin/sh`, since in the
  development shell busybox is not found, so no sandbox shell gets configured.
  Use the shell from `BUILD_TEST_SHELL` instead. (How this does not break the
  old functional tests remains a mystery.)

Change-Id: I6a6a696424e8caaef3f9b68e3738bfd58ea0b056
2025-10-07 20:14:48 +00:00
Raito Bezarius 41fab8a31c legacy/nix-store: do not crash on cache unsoundness
Local store make use of SQLite as a cache for output maps, they can
diverge with actual store reality.

Causing a crash at realization time.

We can just throw an error instead.

Change-Id: I943a4bddfd2461a34933e9ec079a6638ab58e5b1
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-10-07 13:35:42 +00:00
Raito Bezarius feab75bde0 legacy/nix-build: create various temporary directories into a known tempdir
Fixes fj#940.

When running `nix-shell`, the `$NIX_BUILD_TOP` environment variable is
set to `$TMPDIR` or `/tmp`.

nixpkgs stdenv uses $NIX_BUILD_TOP to create `$NIX_BUILD_TOP/env-vars`
which contains all the environment variables set by stdenv. This is used
for debugging purposes in combination with `--keep-failed` to reload the
bash environment of a derivation.

`$TMPDIR` is often unset, therefore, `/tmp/env-vars` was constantly
being created. On a multi-user system or, when you run Lix as root, you
might create a `/tmp/env-vars` with different permission bits.

As a result, `nix-shell` can cease to function because that file will
fail creation for an unprivileged user for example.

fj#940 rightfully remark that the code is not consistent between
nix3-develop and nix-shell and it should be reworked.

Change-Id: Iddf15945385d8bd497b2800b37fee5e1f97689b7
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-10-07 03:28:45 +00:00
eldritch horrorsandRaito Bezarius 7e665feae6 libexpr: make .debug_gdb_scripts aligned
This avoids mangling debug gdb scripts and breaking them resulting in no
script loading at all.

Change-Id: Ib5e7034c37a1e41d1a1c9bd8442b8b8b8758e350
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-10-06 23:00:40 +02:00
Raito Bezariusandeldritch horrors 7b6bcffe8b libexpr/genericClosure: de-ptr-ize UnsafeValueList
UnsafeValueList held pointers from Value which were not necessarily GC
allocated, causing mayhem when evaluating something with genericClosure
(texlive environments).

We get rid of storing pointers and we let comparison take places on
const references.

We keep `gc_allocator<Value>` so that GC can scan things it allocates
inside the `res` list.

Co-authored-by: eldritch horrors <pennae@lix.systems>
Reported-by: qbit
Change-Id: I4fed3a3d9e18a2ef2d751f32d81801540e196f92
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-10-06 23:00:40 +02:00
Raito Bezarius b5d31b7780 libexpr: CompareValues can take const references
Change-Id: I5ab1e9f71c2f066eb9bb39683ff00c5190c1fcd7
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-10-06 22:27:36 +02:00
eldritch horrors 14737888e0 libexpr: add a gdb script with value pretty printers
Change-Id: Ia4a6feb194c73dddf468c78287962713bef582f7
2025-10-05 16:23:05 +02:00
eldritch horrors 80654b84b6 libexpr: de-ptr-ize Value references
thunk values are shareable, and we can represent invalid/uninitialized
values with a special bit pattern that makes no sense otherwise. there
is no need to keep allocating values on the heap, instead we can treat
values like reference-counted smart pointers to heap objects, which in
turn lets us save a lot of allocations and, ultimately, gc heap space.

compared to our baseline (main of 2025-09-27) we save 15%+ memory on a
system rebuild and 17% on nix search. eval time regresses by ~3% for a
system rebuild, while nix search is 7% faster. further optimization is
probably possible (but for now this will just have to be good enough).

Change-Id: Ib6c47acdbe2fac4f76a83c2269f16f30ef66b2e1
2025-10-05 16:23:05 +02:00
eldritch horrors 28c4431c69 libexpr: reformat stuff we'll change soon
mainly to keep the next diff smaller. it'll be large enough as is.

Change-Id: Ib8a34520f03539cbf6aa2f0e66cbed05fe1225eb
2025-10-05 16:22:35 +02:00
eldritch horrors db35aa753b libexpr: de-ptr-ize many Value uses
with thunk state being shareable we no longer need to worry about value
uniqueness, only about value lifetime. this means we can liberally drop
indirections and allocations, passing references instead of pointers or
using stack memory instead of gc-managed memory for some intermediates.

Change-Id: I2d48a6fd57a376d544bd9bd2d05e5420611986d1
2025-10-05 16:22:35 +02:00
eldritch horrors 23df732ddb libexpr: remove ValueVector{,Map}
these typedefs were convenient in the past, but now they're not really.

Change-Id: I7522f7582ead545148af8e6444295d620ee0872a
2025-10-05 16:22:35 +02:00
eldritch horrors ad4c6b6eb1 libexpr: remove unused return types
Change-Id: Ief898b70f9781bd3dbe66734710f388daa2f2fed
2025-10-05 16:22:34 +02:00
eldritch horrors f4dee18693 libexpr: remove type punning in primt_attrValues
there's no measurable performance gain in real-world testing to abusing
our list value storage like this. we haven't tested how much storage we
actually need on the stack to offset most of the temporary storage cost
and used 128 as a "good enough" value instead, reserving 1 kiB of stack
space on 64 bit platforms in a leaf function. this should do for a bit.

Change-Id: Ie98519b1da3e6fe685da88d1c44ffb4580fb592a
2025-10-05 14:56:56 +02:00
eldritch horrors 8108e8a760 libexpr: tag Value::Acb
Value is already tagged. Value::Acb blocks are allocated for lambdas (so
we can fit the value tag into the three bits we have available), but the
current layout is rather wasteful for this purpose. the type bits can be
stored together with parts of pointers, which in the lambda case will be
the scope the lambda captures. the expr could also be used, but Env is a
gc-allocated item and thus guaranteed to be aligned properly for tagging

Change-Id: Ia685875387c7795bc4a00d73d1ce3cfea84e7297
2025-10-05 14:56:56 +02:00
eldritch horrors b19bbdfee1 libexpr: make thunk state shareable
this is a strong prerequisite for making values themselves copyable
without duplicating evaluation side effects. with this we can treat
`Value` the way we treated `Value *` to date and drop indirections.

Change-Id: I08f30d12697614a3ae7149615f6f1da83b13f52b
2025-10-05 14:56:56 +02:00
eldritch horrorsandRaito Bezarius 57333a0e60 libutil: preserve ForeignException type info
preserve type information of wrapped exception when constructing a
ForeignException. this requires using `std::current_exception` for
construction in all cases as passing the exception by reference to
any function erases dynamic type information: `throw` may copy the
thrown exception from its source expression; this only works as we
need it to if static and dynamic type of the expression are equal.

fixes #1000

Change-Id: I7167f298540de7c43c2566592bdb8d7a93c34d31
2025-10-05 00:40:22 +00:00
eldritch horrors dc99a5ea1f libexpr: reduce Value size to one pointer
Change-Id: Id6e4a2f68eaa4afdd006379ebcf839c4a126b819
2025-10-04 21:06:52 +02:00
eldritch horrors 0e9e9fd917 libexpr: heap-allocate "large" integers
Change-Id: Ic391f2f1bf87f044d7a688196ba9e0ad766d65aa
2025-10-04 16:31:45 +02:00
eldritch horrors 79586575c5 libexpr: heap-allocate app nodes
Change-Id: I9a39dcf0be7589cedf494757e21665c5d50e446b
2025-10-04 16:31:45 +02:00
eldritch horrors 94a866d44f libexpr: heap-allocate thunk control state
Change-Id: I20d8ab1d6f683c0a2f3b77edf9bdad147d62c8fa
2025-10-04 16:31:45 +02:00
eldritch horrors d84f3003cf libexpr: move lambdas to auxiliary storage
Change-Id: Ibe4885f17c0ba1634ed6dbca0a45f8bd4619d69b
2025-10-04 16:25:05 +02:00
eldritch horrors 1b5f4eb5eb libexpr: move primops to auxiliary storage
same as for null: we have few of them, they're statically allocated,
and they're not the largest contributor to the value population. not
storing them in Value itself frees up resources we *will* use later.

Change-Id: I521f9f243f48f56a78f7bffdf1dc1f0bc40a5c2d
2025-10-04 16:25:04 +02:00
eldritch horrors 05b80a5ba1 libexpr: move null to auxiliary storage
we only need the one object for it. there's no need to waste precious
bits of the value internal type enum for this simple singleton datum.

Change-Id: Ie314b5bf429015e518798d9d65ad8ab2bb84a38e
2025-10-04 16:20:07 +02:00
eldritch horrors 1d998a343b libexpr: move floats to auxiliary storage
floats are used very rarely, and our float support is bad enough to
strongly discourage using them on reproducibility grounds alone. we
can thus move them to more expensive storage without hurting folks.

Change-Id: I1086f612f85e294dd3fae4a2d334e09f52bbe4a8
2025-10-04 16:20:07 +02:00
eldritch horrors 4d7f468c87 libexpr: move external value refs to aux storage
external values very rarely appear during eval "normal" eval, and
creating them is pretty expensive. does *anything* even use them?

Change-Id: Id50fa3f76b7e1f551d550d99996a1ed5880b2531
2025-10-04 16:20:07 +02:00
eldritch horrors 90be3e34c9 libexpr: heap-alloc string control blocks
despite not using allocation caches this does not have a statistically
significant performance impact, with less than 1% extra memory needed.

Change-Id: Ibe51a55ba986e471f217f3724977af17880fafff
2025-10-04 16:20:07 +02:00
f1ef994f12 libexpr: enable parallel marking in boehm-gc
Using parallel marking in GC speeds up evaluation a fair bit

```
Benchmark 1: ./lix-main/bin/nix search nixpkgs hello --no-eval-cache
  Time (mean ± σ):     20.740 s ±  0.046 s    [User: 17.583 s, System: 3.062 s]
  Range (min … max):   20.678 s … 20.794 s    10 runs

Benchmark 2: ./lix-parallel-marking/bin/nix search nixpkgs hello --no-eval-cache
  Time (mean ± σ):     15.037 s ±  0.080 s    [User: 19.602 s, System: 3.125 s]
  Range (min … max):   14.960 s … 15.227 s    10 runs

Summary
  ./lix-parallel-marking/bin/nix search nixpkgs hello --no-eval-cache ran
    1.38 ± 0.01 times faster than ./lix-main/bin/nix search nixpkgs hello --no-eval-cache
```

Based-on: https://github.com/NixOS/nix/pull/13708
Co-authored-by: Eelco Dolstra <edolstra@gmail.com>
Change-Id: Ibc7625f21e0ee7c8ad66203eeb3aca5d83977731
2025-10-01 23:07:28 +00:00
eldritch horrors 1c4e77387a libexpr: remove tPrimOpApp
using the same nodes as tApp is possible, and thanks to multi-arg app
nodes it can even be a bit faster than the linked lists used to date.

Change-Id: Idccb7c0b54c808e62da85d1c42ee09e6e92c4f7b
2025-09-29 17:56:05 +02:00
eldritch horrors 0b5b14ddc7 libexpr: add multi-arg app nodes
these behave like the old chains of app nodes, but they can store more
than one argument per node. for tApp values themselves this is not all
that useful, but if we could share tApp and tPrimOpApp backing storage
we could avoid creating and traversing the linked lists of values that
are currently needed to represent partially applied builtin functions.

Change-Id: I5a2a02d9733e1e0be5443459e2998d62fd3b9a5b
2025-09-29 17:56:05 +02:00
eldritch horrors 3350ab8a56 libexpr: use std::span for callFunction
Change-Id: I8c94bafabdb2416c85d9721d3d6424f52fbd45e0
2025-09-29 17:56:05 +02:00
eldritch horrors 0d69da184c libexpr: unify strings and paths
paths already are just strings with slightly magic semantics. the type
can mirror that at no perf cost, letting us drop one internalType tag.

Change-Id: I98acaa4fe3bedd28fc0841e1b81184d8dcddacc5
2025-09-29 17:56:05 +02:00
eldritch horrors d23e3a09a4 libexpr: alloc list storage as a (length, vla) type
memory overhead is minimal and performance impact not measurable. once
we've done something like this for all value types that don't fit in a
single machine word we can cut a word from Value, offsetting the cost.

Change-Id: I9813bacd7e851957ad3426aed8f74033179a4212
2025-09-29 17:55:54 +02:00
eldritch horrors 01f1852360 libexpr: don't inline small lists into values
this has no measurable performance impact thanks to the new caches.

Change-Id: Ib403a9a567161675f78e8c5d314d6340183d181d
2025-09-29 15:22:41 +02:00
eldritch horrors 739624ddcf libexpr: cache more allocation sizes
we now use a single cache set for a number of sizes from one to eight
words. this also matches small attrsets, but perf impact seems small.

Change-Id: Icf16b329b98a20fcc9fe75e6395e148f0852c798
2025-09-29 15:22:41 +02:00
eldritch horrors c7cc7d6c31 libexpr: "hide" Value union members
on its own this is not very useful, but having accessors for every value
kind is a prerequisite for doing smart things with Value than the union.
the net effect for now is only to add a few parentheses across the tree.

Change-Id: I88688ac09eb08495dad1eb221034ca540f094950
2025-09-29 15:22:41 +02:00
eldritch horrors 2dae1141d9 bench: add memory benchmark
Change-Id: I4b2aa305b452a3a0c73e37953a5c332219d43d2c
2025-09-29 15:22:41 +02:00
eldritch horrors f31e8e2b55 bench: allow benchmarking a single build
Change-Id: Ie77247f9348eaeea740b0f894819ee2383d92312
2025-09-28 13:35:37 +00:00
eldritch horrors e4ee5e2eb5 testing: disable substitution in f2
mirrors f1. much faster.

Change-Id: I38bbbd5b26220f480afe76ef9302e5c90f21507a
2025-09-28 13:19:13 +00:00
Raito Bezarius 992c3ae981 nix3/develop: support structured attrs-based output checks
nix develop should ignore output checks in general.

This was done only for the old way of specifying output checks, the
structured attrs way requires rewriting the JSON and removing the output
checks pieces.

We take a brutal approach of removing as many as possible including
non-recommended ways of doing it.

Fixes #997.

Change-Id: Iaf83029016c71b5171e56e15d4eadc1a60a8be98
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-09-23 14:35:08 +00:00
Alois Wohlschlager 43325669df daemon: let subdaemons outlive their parent again
In 7b37d5ea6a, aside from subdaemons getting
properly executed, they also lost the ability to outlive their parent, due to
now getting set the parent death signal like most other processes spawned by
Lix. This has annoying consequences like all concurrent builds being forcefully
terminated on system updates requiring a nix-daemon restart. As the behaviour
change was not documented and the systemd service file retained
`KillMode=process`, it seems to have been accidental. Restore the old behaviour
of letting the subdaemons outlive their parent.

Change-Id: I6a6a69645312a90dbce55495c2fef3825dd3c097
2025-09-22 19:51:58 +02:00
K900 f68233ec43 flake: unvendor capnproto
Nixpkgs has a recent enough version and all the relevant packaging changes now.

Change-Id: Ifdf814f80689803dd63ba7d16049e7d37e389542
2025-09-22 10:04:17 +03:00
Commentator2.0 1e69b17f0a tests/functional2: migrate timeout.sh
Change-Id: Ieb12c46bc987c74a6adda07e6c6ed33aebf8a96c
2025-09-21 17:56:19 +02:00
Commentator2.0 e0739afb42 tests/functional2: migrate build.sh
Change-Id: Iac3f3dc021436fc693a3729ba522d4bf1f124220
2025-09-21 13:58:26 +02:00
Raito BezariusandSergei Zimmerman a20a83b0a7 libexpr: replace Bindings::find by Bindings::get
In preparations for a new representation of bindings that will make it
impossible to write an efficient `Bindings::find`.

Change-Id: I4e5a25b8d37d01b5728f7fe43978ceda2ab1b9b6
Signed-off-by: Raito Bezarius <raito@lix.systems>
Co-authored-by: Sergei Zimmerman <sergei@zimmerman.foo>
2025-09-20 23:46:16 +02:00
Raito Bezarius 257e910247 libutil/comparator: support > for generation
Change-Id: Idd40b06c7051b9b0bd4c125359af0eb69996313a
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-09-20 23:45:41 +02:00
Raito Bezarius ccf3c5e30d libexpr/primops: make zipAttrsWith not mutate attribute sets
The way zipAttrsWith works is to replace the attribute set value by a
call to a function (the argument of zipAttrsWith) over the list of
attributes sharing a common key.

Instead of that, we will insert into the resulting attribute set the
various lazy calls and return that.

Change-Id: I2aae054eb99b1d1f8b0e7c658cc8d3488e5cdb01
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-09-20 23:45:41 +02:00
Raito Bezarius 19e4f11e88 libexpr/symbol-table: efficiently generate ≤, <, >, ≥, ==, != for Symbol
C++ has the "spaceship" operator which can be auto-implemented and
generates efficiently a strong ordering.

Change-Id: Idfd1fd68039b395e54401cbe913454e0cbd80fb3
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-09-20 23:45:38 +02:00
Raito BezariusandSergei Zimmerman 057b725ae3 libexpr: use const references or pointers to attribute sets
Prior to this change, references or pointers could be mutated. In
practice, we do not require this capability in the codebase except in
zipAttrsWith.

This cleans up all easy sites in preparation to have a smarter
representation of attribute sets albeit one that requires constant
references.

Change-Id: I2be20cce040a9228bde9e5f7b42c0499fba9550b
Signed-off-by: Raito Bezarius <raito@lix.systems>
Co-authored-by: Sergei Zimmerman <sergei@zimmerman.foo>
2025-09-20 20:31:19 +02:00
Alois Wohlschlager 3ee839cc5e libutil,nix: fix clashes with stdio stream identifiers
The stdio stream identifiers (stdin, stdout, stderr) are allowed to be macros.
In musl libc they are, for example doing `#define stdout (stdout)`, breaking
compilation with an error when one of the clashing variables is attempted to be
initialized the "wrong" way:

    ../lix/libutil/processes.cc:272:7: error: expected class member or base class name
      272 |     , stdout(stdout ? std::make_unique<AsyncFdIoStream>(std::move(stdout)) : nullptr)
          |       ^
    /nix/store/ziw42d7rvgnf3vkbfc8kry07kipwf1xm-musl-static-x86_64-unknown-linux-musl-1.2.5-dev/include/stdio.h:67:16: note: expanded from macro 'stdout'
       67 | #define stdout (stdout)
          |                ^

Other places only cause warnings on musl:

    ../lix/libutil/processes.cc:254:17: warning: parentheses were disambiguated as redundant parentheses around declaration of variable named 'stdout' [-Wvexing-parse]
      254 |     std::string stdout;
          |                 ^~~~~~
    /nix/store/ziw42d7rvgnf3vkbfc8kry07kipwf1xm-musl-static-x86_64-unknown-linux-musl-1.2.5-dev/include/stdio.h:67:16: note: expanded from macro 'stdout'
       67 | #define stdout (stdout)
          |                ^~~~~~~~
    ../lix/libutil/processes.cc:254:17: note: add a variable name to declare a 'std::string' (aka 'basic_string<char>') initialized with 'stdout'
      254 |     std::string stdout;
          |                 ^
          |                  varname
    /nix/store/ziw42d7rvgnf3vkbfc8kry07kipwf1xm-musl-static-x86_64-unknown-linux-musl-1.2.5-dev/include/stdio.h:67:16: note: expanded from macro 'stdout'
       67 | #define stdout (stdout)
          |                ^
    ../lix/libutil/processes.cc:254:5: note: add enclosing parentheses to perform a function-style cast
      254 |     std::string stdout;
          |     ^                 
          |     (                 )
    ../lix/libutil/processes.cc:254:17: note: remove parentheses to silence this warning
      254 |     std::string stdout;
          |                 ^
    /nix/store/ziw42d7rvgnf3vkbfc8kry07kipwf1xm-musl-static-x86_64-unknown-linux-musl-1.2.5-dev/include/stdio.h:67:16: note: expanded from macro 'stdout'
       67 | #define stdout (stdout)
          |                ^

However they are still wrong, since the macro could be more complicated. Fix
them as well.

Change-Id: I6a6a6964a50ef7dec8f05f0bd8fc8f13f3036d51
2025-09-20 11:25:00 +02:00
Alois Wohlschlager a7e1b23165 libexpr: remove liblixexpr_mstatic
It was supposed to be removed in 480fdf146d, as
it is not needed any more with the prelinked library. Due to a mistake in
rebase conflict resolution it reappeared by accident. Actually remove it now.

Change-Id: I6a6a6964d175fdb0ba0ad9ac55d4d22d7b27ad3f
2025-09-20 08:30:35 +00:00
Commentator2.0 3a4f9488f0 tests/functional2: migrate nix_path.sh
Change-Id: Ibc4f848721d4da628c44aa074faf009c87af165d
2025-09-19 20:51:49 +02:00
Commentator2.0 fc2eedf9db tests/functional2: migrate why-depends.sh
Make builds work on darwin and migrate the first building test :D

Change-Id: Ia301c78d3bc771cd826d93dc114b098167043e97
2025-09-19 20:51:49 +02:00
Commentator2.0 a25a5739c7 tests/functional2: migrate search.sh
Change-Id: Id0f47fc72191d913442ff35d47df1b87ceb9f0be
2025-09-19 15:09:22 +02:00
piegames 2a622d7de7 tests/functional2/lang: Migrate value printing tests
Change-Id: I39303ef44552eb9fc17ccc2da5206c8906e46f27
2025-09-18 23:38:46 +02:00
piegames fe7a8d5e67 tests/functional2/lang: Migrate arithmetic tests
Change-Id: I73259875debc053dca46fe34f9f8d10b8486fa4f
2025-09-18 23:38:46 +02:00
piegames 096aa37cf1 tests/functional2/lang: Migrate ind-string tests
Change-Id: Ibc764ab97a53b751287654300211f3a9d8750b74
2025-09-18 23:38:46 +02:00
piegames 21a0b81a26 tests/functional2/lang: Migrate function args tests
Change-Id: Ia805d7fbf74c671fc419fb8c9db3960b6be62354
2025-09-18 23:38:46 +02:00
piegames f2b64142a9 tests/functional2/lang: Migrate dup-attrs tests
Change-Id: Ia5976229c8b2e6bf89d821b5676a9a397e4539d7
2025-09-18 23:38:46 +02:00
piegames 0d4a6132dc tests/functional2/lang: Migrate builtins.deepSeq test
Change-Id: I5caeb8a606d8cba519267c6011561afc9813598c
2025-09-18 23:38:46 +02:00
piegames 3692cf1ea2 tests/functional2/lang: Migrate inherit tests
Change-Id: I0f09f2b4ffa2edc5031351d0426865d7605ff7fa
2025-09-18 23:38:46 +02:00
piegames c7b8ac7cd9 tests/functional2/lang: Migrate substring tests
Change-Id: I902de6f3aebc902d56a79f8909bd6c9b5ba70cda
2025-09-18 23:38:46 +02:00
piegames 81baa64847 tests/functional2/lang: Migrate eval-okay-eq*
Change-Id: I17f81a765bbdbde3a27dd547e1ef65bffa97bce5
2025-09-18 23:38:46 +02:00
piegames 16db4da8f0 tests/functional2/lang: Rename tests
Most tests for builtins now have `builtins.builtinName` as their name.
This makes navigating the test list a bit easier

Change-Id: Ief5af5c568a419bf9130601f9590e7a696b0dc0a
2025-09-18 23:38:46 +02:00
piegames 5cfe785352 tests/functional2/lang: More trivial eval-okay test migrations
In the first pass I erred on the cautious side, only migrating safe
bets, to here's some trivial migrations that I missed

Change-Id: I934011919837b0aa491113afdcad603cf6b9cbbb
2025-09-18 23:38:46 +02:00
Jade Lovelace 0018b98859 codeowners: add some missing directories
Change-Id: I22aa53074dedd822517a5e8dfa2f0a520d0000bb
2025-09-17 16:26:49 +00:00
Alois Wohlschlager d63a31e7a9 Remove obsolete editline.patch
Its usage has been removed in 3936f9b5de but for
some reason the file remained. Drop it now.

Change-Id: I6a6a696420f3425587f82bd485c6307e1ccff08d
2025-09-17 06:09:44 +00:00
Commentator2.0 a9f28622c8 tests/functional2: migrate pure-eval.sh
Change-Id: Ieadb54a27b1cedcf3a4ce65e024a68440759ce7e
2025-09-16 20:06:07 +02:00
eldritch horrors d648ec55eb libutil: asyncify runProgram2 stdout stream
fun side effect: post-build hooks are no longer single-threaded!

Change-Id: Ie5c75e588e4b2b8d515d4f1ae36de45b0a0807f7
2025-09-16 16:10:59 +00:00
Maximilian BoschandJörg Thalheim d6b0b8b382 nix-eval-jobs: add --no-instantiate flag
Closes #987

The patch adds a flag `--no-instantiate` which only performs evaluation
without instantiating any derivations. Hence, GC root creation is also
skipped. To achieve that, Lix is also put in read-only mode and all
operations that require reading a derivation (e.g. constituents or
listing input derivations) are disabled fallback values are set.

This is a port of an upstream PR[1]. Given the divergence of the
codebases (different restructurings on both ends, no more CA derivations)
I decided to redo large portions from scratch instead of
cherry-picking the patches. Hence, the authorship.

Additionally the clean up of casts down to a local store are removed or
guarded behind an if, as done in the upstream PR.

[1] https://github.com/nix-community/nix-eval-jobs/pull/379

Co-authored-by: Jörg Thalheim <joerg@thalheim.io>
Change-Id: Ib84f44e7799bc5577fd2ee98912458f16ebeab81
2025-09-16 13:03:53 +02:00
eldritch horrors 812f466e0d libutil: asyncify runProgram
Change-Id: I397d02b1a993dc4f0c2dc2b2f90d0c1aebda4c2e
2025-09-15 16:18:01 +00:00
Marie Ramlow 8981fb5a18 meson: only require libatomic if the test program doesn't link
95448347 made lix require libatomic if the platform is able to link a
simple program using atomics, but it should actually be the other way
around. We need to require libatomic if it fails.

Change-Id: I6a6a6964ca6ee90a59314ddf1865753e83713772
2025-09-13 21:30:52 +02:00
eldritch horrors d90e4a6581 libutil: reject malformed nars harder with better errors
fixes #993

Change-Id: I56aff32498ecd32e65f1576661d5a15808a4439f
2025-09-12 23:22:11 +02:00
eldritch horrors 95d43d1e39 testing: unrace functional-repl
using sleep(1) as a synchronization mechanism does not work. use fifos instead.

fixes #690 and ci constantly falling over in the same exact fucking source line

Change-Id: I51725f8e439b6753f3212d2897dbb0620ad77a37
2025-09-12 23:22:11 +02:00
Sergei Zimmerman 88517c324f libexpr: slim down attrsets by 8 bytes on 64 bit systems
It's pretty bad that Bindings effectively wasted whole 8
bytes (4 for capacity and + 4 for alignment padding) to
store something it doesn't actually need. BindingsBuilder
allows the capacity to be checked at construction time,
after which the Bindings does not get mutated aside from
the ugly case of builtins, which doesn't get built all
at once.

For `nix search --no-eval-cache github:nixos/nixpkgs/e1fa12d4f6c6fe19ccb59cac54b5b3f25e160870 hello`
this shaves off around 53MB allocations out of 2GB used
for attrsets in total:

<     "bytes": 2001170768,
---
>     "bytes": 1947398072,

<     "Bindings": 16,
---
>     "Bindings": 8,

Nix PR: https://github.com/NixOS/nix/pull/13919

Change-Id: I939c5ac545f5abbca048370dcf4936346339d75c
2025-09-12 19:23:38 +03:00
eldritch horrors b966d2e53b libfetchers: asyncify some git/hg internals
Change-Id: I3aea82173a610dc9584e956aa74d3e3ad06bfe68
2025-09-12 11:52:03 +00:00
eldritch horrors 9b01455ada libfetchers: asyncify Input{,Scheme}::{putFile,clone}
Change-Id: I084b6fc2271ea99a9325b1969f18b83778702bea
2025-09-12 11:52:03 +00:00
eldritch horrors 078e1d7245 libstore: asyncify LocalStore::findPlatformRoots
Change-Id: I173fae8aa17cb757b9c2aceadb4d68259a236b32
2025-09-12 11:52:03 +00:00
Qyriad 138c7161be nix3-develop: correctly escape ASCII control characters for JSON
Fixes #991.

Change-Id: Iafc7d9603fbc3615393d32d9630f0e8fe548950b
2025-09-12 09:42:20 +00:00
Qyriad ee91eec5cf libutil: add bashEscape, which escapes non-printing characters with $''
Change-Id: I23b416372677acb80bae9c124e9fd6d867e7af6d
2025-09-12 09:42:20 +00:00
Jade LovelaceandRutile b5cf7dff7b package: fix running pytest in a dev shell
I have no idea how seemingly I am the only one hitting this, but either
way, this fixes it.

Fixes: https://git.lix.systems/lix-project/lix/issues/964
Workaround-For: https://github.com/NixOS/nixpkgs/pull/435749
Change-Id: If2a7ad89f98f0054928868eabb62b35c2df28e6e
2025-09-11 19:15:51 +00:00
Marie Ramlow 95448347ea meson: link against libatomic if required
Some platforms like 32-Bit PowerPC need linking against libatomic.
Try to compile and link a very simple snippet of code which uses atomics
and make libatomic required if it fails.
Because we're using `dependency('atomic')`, the required meson versions
gets bumped to 1.7.0. See https://mesonbuild.com/Dependencies.html#atomic-stdatomic

Change-Id: I6a6a696471e1d352fb161c537ba9023b97c2d31e
2025-09-11 17:58:25 +00:00
eldritch horrors b04419e719 libutil: asyncify streaming unpackTarFile
libarchive is not async and cannot be used async without involving green
threads, which have already proven to be very problematic. unpacking tar
archives is rare enough that spawning a new thread for each shouldn't be
too much overhead, and the additional data copy probably also won't hurt
performance too much. we may even benefit from being able to extract not
just one archive per event loop but as many archives as we can keep fed.

Change-Id: Iece82bd566ada0a2a49de54c4e69caf6d93f6720
2025-09-11 16:36:52 +00:00
eldritch horrors 6f0bf9798a libstore: process post-build-hook logs directly
using a sink for this has long been a bit weird anyway. originally it
was necessary due to api limitations, but it hasn't been for a while.

Change-Id: I3dfa157944618349bfd6f398ee1667fc31519d86
2025-09-11 16:36:52 +00:00
Alois Wohlschlager 91bf54d726 flake: update nixpkgs input
Without https://github.com/NixOS/nixpkgs/pull/434761 evaluation of the
`nixpkgsLibTests` will fail in CI with recent enough Lix, due to reliance on
the TOML integer saturation bug.

Reported-by: Sergei Zimmerman <sergei@zimmerman.foo>

Change-Id: I6a6a6964838009d2c525f67035f84072fdfad988
2025-09-07 16:50:37 +02:00
Jade Lovelace f4bdddf0fd tree-wide: log commands executed at --debug --quiet
Fixes: https://git.lix.systems/lix-project/lix/issues/973

Information about which commands were executed is really valuable to
debug Lix and is much more user relevant than the vast majority of the
e.g. build loop junk printed at debug level. Currently we have a *whole
lot* of call sites where we call execv* which should probably be cleaned
up, but that's future work.

I chose to print argv0 rather than the executable path if these differ,
since the code is shorter and since the command could be a fully
resolved symlink or so where argv0 is the actual command name being run.
However, it's not exactly *hard* to write std::ranges::drop_view(args,
1).

Change-Id: I73c3abb20b229d5e2d64277aa29cbbeed7764bab
2025-08-28 17:19:14 -07:00
Qyriad fe6395cd4d ci: add job to ensure static builds eval and configure
Static builds have broken and been fixed multiple times now[1][2]. It's
too expensive to add another "build Lix" job to CI, but a lot
of failures happen at eval time or during build configuration. We can at
least catch those.

[1]: https://git.lix.systems/lix-project/lix/commit/ac80a11300eb60006b7de90fb10ad6789e5beeed
[2]: https://git.lix.systems/lix-project/lix/commit/c82af241f58d7aeaecbb2a6d17bd8d7bfcc834ce

Change-Id: I5618f1ba7554324e60f4f510e70b1ccea6445f4e
2025-08-27 03:29:46 +00:00
eldritch horrors d5970d8a8b libutil: remove unused logger function
Change-Id: I8505346baa88b9f174707e127f1c78088238cf3e
2025-08-25 21:00:15 +00:00
eldritch horrors ed9a78fccd treewide: colorize logs by default
printTaggedWarning already colorized its messages. we can do the same
for most other log messages.

Change-Id: Idcd31bbf4f8d0d703395b0d2b7b9bc33264d969f
2025-08-25 21:00:15 +00:00
eldritch horrors 18285afa76 treewide: force log format strings to be literals
luckily none of these a format strings vulnerabilities because
boost::format is smart enough to throw an exception when given
fewer format string arguments than are requested by specifiers

Change-Id: I5fa78f0d1396263271f6e1dbcee9c0b2e9e18c34
2025-08-25 21:00:15 +00:00
eldritch horrors 466115c9c8 treewide: don't call Logger output functions directly
always use log macros, which also have the benefit of respecting the
verbosity setting without needing virtual function calls to read it.

Change-Id: I1c605562a53e54140724d5225e040abcf49ac996
2025-08-25 21:00:15 +00:00
eldritch horrors 4c6c01786f treewide: turn nix::warn into a macro
we add two variants: one that just prints a message at the warning
level (mirroring the other printer macros), and one that also adds
the colored "warning: " prefix the function added. since there are
no overriders of this function in tree it looks safe to remove it.

Change-Id: I7008fd0f31d59fbc9259472e29359c8df19ff87d
2025-08-25 21:00:15 +00:00
eldritch horrors 03ab20e191 libutil, cli: remove Logger::warn
only use the free function instead, so we can turn that into a macro.

Change-Id: I0319e9f7bdebb96f6159053e8b7b7a82559c9b33
2025-08-25 21:00:15 +00:00
eldritch horrors f40a60f46a libutil: make log macros usable outside of nix namespaces
mostly useful for nix-eval-jobs which currently has to call the logger
functions directly because its main code *isn't* in the nix namespace.

Change-Id: Ia8440d86a293d9006ffef2562b1859e9aaa79a62
2025-08-25 21:00:15 +00:00
Commentator2.0 9cdc2bf61c tests/functional2: migrate hash.sh
Change-Id: I5061479b972088c904ca1db4d9004e738c2b5a57
2025-08-25 21:29:27 +02:00
Alois Wohlschlager 451a14980b libstore: use OpenSSL for Ed25519 signatures
Previously two cryptography libraries were linked into Lix: OpenSSL used for
hashing and (in usual configurations) indirectly via curl for TLS, and Sodium
used only for handling the Ed25519 path info signatures. The latter is
functionally redundant since OpenSSL supports the same use case as well.
Reimplement the Ed25519 handling using OpenSSL and drop Sodium.

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

Change-Id: I6a6a696456b9d3ad7fdc2bf9b0759836a6247a38
2025-08-25 17:11:45 +00:00
Raito Bezarius 836644a7a1 libstore/build: put nix log command on its own line
This way, you can copy paste it easily.

Change-Id: Ie5799d1dea59f26cc0a1b3fde17d4449f49361c5
2025-08-25 09:51:07 +00:00
Raito Bezarius d6ccc6b89c libstore/build: make build errors extensible by children
Currently, DerivationGoal prints a pretty generic message.

For many valid reasons, children may have better knowledge of the detail
of what has happened and would like to extend the error message.

What we did is to printError at convenient places but this is
counterproductive because the build error can bury the notes.

This is still not perfect because there's no fine-grained structured
information that children can use to act upon the generic messaging, but
this is already an improvement for LocalDerivationGoal and keep failed
which will occur in the next change.

Change-Id: I5835cbbb30c4f2aa64abefb83999018d30ca4a0c
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-08-25 09:51:07 +00:00
Alois Wohlschlager 8d55251ebd libstore: remove StorePath::random
It was only used for impure derivations, which were finally removed in commit
be07629820. Delete the unused function.

Change-Id: I6a6a696481711f68a8c3ea7eac7978fcf5884cce
2025-08-24 19:34:20 +02:00
Commentator2.0andCommentator2.0 7b6a85982b tests/functional2: provide a way to easily access often used assets
Change-Id: I461ee08d9752d972e7485e15186426f98e68ba13
2025-08-24 10:38:39 +02:00
Maximilian Bosch e2641cb890 libstore/local-derivation-goal: show tree with references that lead to an output cycle
Closes #551

This adds a special accessor that falls back to checking if a store-path
exists within a chroot if it's not a valid path. That way,
`genGraphString` can find out which files have which references before
the outputs are registered.

Change-Id: I03c9d508fa3c72e5c262194461a25d71f3f4de15
2025-08-24 01:15:15 +02:00
Maximilian Bosch 4886e506f2 libstore: move LocalStoreAccessor definition to header
That way it's possible to inherit from LocalStoreAccessor to implement
special behavior such as an accessor that falls back to the chroot
directory if it can't find a store-path (which is what we'll do in the
next commit).

Change-Id: If689eb3f410e81e629f1d13cc2b48594fecb1001
2025-08-23 18:36:53 +02:00
Maximilian Bosch 10c04ce846 libstore/local-derivation-goal: create reference maps between outputs outside of topoSort()
That way we do now have linear complexity to determine output
references per output within each step of the topological sort.

Instead, this is done before and the topo-sort only filters the output
map for other derivation outputs.

Following up on this, we can re-use `outputGraph` to generate a tree
with references to display which files cause an output reference cycle
if needed.

Change-Id: Ibdd46e7b2e895bfeeebc173046d1297b41998181
2025-08-23 18:36:53 +02:00
Maximilian Bosch 2175d007e5 libstore/path-tree: allow passing a custom accessor
If none is given, we fall back to whatever accessor we get from the
store.

To display which paths actually contain the references leading to
e.g. a cycle or triggering a disallowedRequisites error, we'd
potentially have to look into the chroot from the previously finished
build. This behavior should not be part of the local accessor by
default, but part of a "special" accessor. This change allows using such
an accessor for `genGraphString()`.

Now that we inject the accessor from the outside, we have to mock it
anyways in the tests. Hence, this also adds a testcase for the
precise=True case.

Change-Id: I58465fb944776c2b0262ba054d1f296ed2ae3406
2025-08-23 18:36:49 +02:00
Maximilian Bosch 312e90f4b6 tests: add small testcase for output cycle detection
Change-Id: I186937dfbca4d051e5ad860239720816429a0a8e
2025-08-23 16:23:35 +02:00
Maximilian Bosch f7871fcb57 libutil/topo-sort: return std::variant<std::vector<T>, Cycle>
The variant has on the left-hand side the topologically sorted vector
and the right-hand side is a pair showing the path and its parent that
represent a cycle in the graph making the sort impossible.

The goal is to implement #551 which needs to throw an error if the
topo-sort fails. However, the error-message is supposed to contain a
graph of store-paths and the API to generate this is inherently async.

Now, catching the exception and re-throwing another one is impossible
since `co_await` is forbidden in `catch`-blocks and adding another
topoSort variant that allows an async `makeError` also seems odd. Hence,
I decided to alter the data-structure in use a bit for this use-case.
One out of two uses of the function are affected after all.

Change-Id: I70a987f470437df8beb3b1cc203ff88701d0aa1b
2025-08-23 16:23:35 +02:00
eldritch horrors be438c62e1 nix/eval: remove --write-to
it's broken, can write arbitrary file paths when run as root, and only
supports strings and recursive sets of strings. this was only used for
manpage generation in a build system that has not woken up since 1976.

fixes #974
fixes #227

Change-Id: I4f18599685a3077c15ddc02c759558f986c8c6e4
2025-08-23 10:39:30 +00:00
Alois Wohlschlager 0f50bc452e libutil: make backoffTimeouts inline
Commit 5dc847b47b introduced it as a non-inline
function with definition in the header, which can result in linker errors like
the following:

    /build/source/build/lix/libutil/backoff.hh:36: multiple definition of `nix::backoffTimeouts(unsigned int, std::chrono::duration<long, std::ratio<1l, 1000l> >, std::chrono::duration<long, std::ratio<1l, 1000l> >, std::chrono::duration<long, std::ratio<1l, 1000l> >)'; tests/unit/liblixutil-tests.p/libutil_backoff.cc.o:/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-gtest-static-x86_64-unknown-linux-musl-1.17.0-dev/include/gtest/gtest-printers.h:1223: first defined here

This error was observed during trying to bump `lixPackageSets.git` in nixpkgs.
I am not sure why it can't be observed in the in-tree `nixStatic` package but
the definition is wrong in any case.

Change-Id: I6a6a6964e218a03ca2a2e8eddbb72d44e06e904e
2025-08-23 11:23:15 +02:00
Maximilian Bosch 5dc847b47b libstore: exponential backoff for downloads
Closes #932

`connect-timeout` gets replaced by an exponential backoff for the
download timeout where the initial value is controlled by the setting
`initial-connect-timeout`.

Per iteration, the upper limit of the timeout is increased set to

    timeout := min(max_connect_timeout, initial_connect_timeout * 2^i)

I decided to move the entire timeout / tracking of attempts into its own
class to not make the filetransfer implementation more complex. Also,
that allows us to write unit-tests for it.

Setting `--download-attempts` to `0` is forbidden now and an exception
will be thrown. For `--offline` we set it to `1`, the behavior is
equivalent to what it was before: whether the max tries were exceeded is
only checked after the first download exception got thrown, i.e. there's
still one attempt being made.

The end-result - with timeouts being caused by a wrongly set proxy -
looks like this:

    $ env HTTPS_PROXY=1.1.1.1 nix store ping --store https://example.com
    warning: error: unable to download 'https://example.com/nix-cache-info': Connection timed out after 5006 milliseconds (curl error code=28); retrying in 422ms ms (attempt 1/5)
    warning: error: unable to download 'https://example.com/nix-cache-info': Connection timed out after 10010 milliseconds (curl error code=28); retrying in 1003ms ms (attempt 2/5)
    warning: error: unable to download 'https://example.com/nix-cache-info': Connection timed out after 20020 milliseconds (curl error code=28); retrying in 2018ms ms (attempt 3/5)
    warning: error: unable to download 'https://example.com/nix-cache-info': Connection timed out after 40007 milliseconds (curl error code=28); retrying in 4087ms ms (attempt 4/5)
    error: unable to download 'https://example.com/nix-cache-info': Connection timed out after 80074 milliseconds (curl error code=28)

Change-Id: I9e8d08d78275bcf60080d663febc9e075243d36b
2025-08-22 16:19:46 +02:00
Commentator2.0andCommentator2.0 7553d0a983 tests/functional2/lang: don't throw unused file errors on invalid configurations
Currenlty, when a test group is invalid already, we also throw unsued
file errors.
This leads to clutter as more often than not, the unused files are
caused by an invalid configuration, making the debug stack bigger
without reason.

With this commit the behavior is changed to only error about unused
files, when no other configuration issues were found

Change-Id: I92a819753f13b8ed5a07dae53ecaee5d84b5ce64
2025-08-22 07:50:20 +02:00
Commentator2.0andCommentator2.0 0a3e43590c tests/functional2: improve files ux
Currenlty one is required to always write the bulky `mark.parametrize`
with indirect and things

This commit adds a custom decorator for usage of files, which hides the
parametrization complexity from the user.

Change-Id: I526e016d12006669dc302dfc5af619735399c503
2025-08-22 07:50:20 +02:00
Jade Lovelace a84355a813 perl: passthru perl
Required for compat with CppNix derivation.

Fixes: https://git.lix.systems/lix-project/lix/issues/971
Change-Id: I6c38545b7a34b22843fc9acfbb042259cd824d84
2025-08-22 01:51:47 +00:00
Raito Bezarius ac9721a92e Revert "libutil: add makeTempSiblingPath helper"
Revert submission 3850

Reason for revert: caused multiple regressions noticed in https://git.lix.systems/lix-project/lix/issues/975 and https://git.lix.systems/lix-project/lix/issues/966 (suspected).

Root cause analysis has not been done yet and this breaks Lix on Darwin on HEAD.

Reverted changes: /q/submissionid:3850

Change-Id: Ifc8ccc212ec73f51958b20c9419c81d723f87b0d
2025-08-21 14:37:46 +00:00
Raito Bezarius 1fdaa6888a Revert "libstore: use makeTempSiblingPath in replaceValidPath"
Revert submission 3850

Reason for revert: caused multiple regressions noticed in https://git.lix.systems/lix-project/lix/issues/975 and https://git.lix.systems/lix-project/lix/issues/966 (suspected).

Root cause analysis has not been done yet and this breaks Lix on Darwin on HEAD.

Reverted changes: /q/submissionid:3850

Change-Id: I23f659664bcb1f38ea8cd053279d3275f33e001c
2025-08-21 14:37:46 +00:00
Raito Bezarius 7622d28dd4 Revert "libutil: extract Base32 helpers from Hash"
Revert submission 3850

Reason for revert: caused multiple regressions noticed in https://git.lix.systems/lix-project/lix/issues/975 and https://git.lix.systems/lix-project/lix/issues/966 (suspected).

Root cause analysis has not been done yet and this breaks Lix on Darwin on HEAD.

Reverted changes: /q/submissionid:3850

Change-Id: I2dae7147030c883a57be8a8c205e492e16425a23
2025-08-21 14:37:46 +00:00
Raito Bezarius 28bcc7fb24 Revert "libutil: use OS‐provided entropy for temporary filenames"
Revert submission 3850

Reason for revert: caused multiple regressions noticed in https://git.lix.systems/lix-project/lix/issues/975 and https://git.lix.systems/lix-project/lix/issues/966 (suspected).

Root cause analysis has not been done yet and this breaks Lix on Darwin on HEAD.

Reverted changes: /q/submissionid:3850

Change-Id: Icdbb89bbf031581250fbda3c9ab9095e7af10ec1
2025-08-21 14:37:46 +00:00
Raito Bezarius 7c03f42759 Revert "libstore: simplify makeTemp{,Sibling}Path callers"
Revert submission 3850

Reason for revert: caused multiple regressions noticed in https://git.lix.systems/lix-project/lix/issues/975 and https://git.lix.systems/lix-project/lix/issues/966 (suspected).

Root cause analysis has not been done yet and this breaks Lix on Darwin on HEAD.

Reverted changes: /q/submissionid:3850

Change-Id: I410046be0ed84e84d1530748d29383489e36c72c
2025-08-21 14:37:46 +00:00
Raito Bezarius ca3328dac0 Revert "libstore: use makeTemp{,Sibling}Path more"
Revert submission 3850

Reason for revert: caused multiple regressions noticed in https://git.lix.systems/lix-project/lix/issues/975 and https://git.lix.systems/lix-project/lix/issues/966 (suspected).

Root cause analysis has not been done yet and this breaks Lix on Darwin on HEAD.

Reverted changes: /q/submissionid:3850

Change-Id: I22a7e48239783bfee53734f65d4e723a7690c3f5
2025-08-21 14:37:46 +00:00
Raito Bezarius 805aeffece Revert "libstore: simplify fallback build directory logic"
Revert submission 3850

Reason for revert: caused multiple regressions noticed in https://git.lix.systems/lix-project/lix/issues/975 and https://git.lix.systems/lix-project/lix/issues/966 (suspected).

Root cause analysis has not been done yet and this breaks Lix on Darwin on HEAD.

Reverted changes: /q/submissionid:3850

Change-Id: Id6db066f3f5c454b258157bc12e0f26020de94a7
2025-08-21 14:37:46 +00:00
Raito Bezarius bde4a50740 Revert "libutil: use makeTempPath in createTempSubdir"
Revert submission 3850

Reason for revert: caused multiple regressions noticed in https://git.lix.systems/lix-project/lix/issues/975 and https://git.lix.systems/lix-project/lix/issues/966 (suspected).

Root cause analysis has not been done yet and this breaks Lix on Darwin on HEAD.

Reverted changes: /q/submissionid:3850

Change-Id: I22d2c315bd8b1eed536c08f2d5b368331907f994
2025-08-21 14:37:46 +00:00
Raito Bezarius e3ff4e0365 Revert "libstore: simplify createTempDir interface"
Revert submission 3850

Reason for revert: caused multiple regressions noticed in https://git.lix.systems/lix-project/lix/issues/975 and https://git.lix.systems/lix-project/lix/issues/966 (suspected).

Root cause analysis has not been done yet and this breaks Lix on Darwin on HEAD.

Reverted changes: /q/submissionid:3850

Change-Id: I76930bdc1fb51edb5b0c591272ecb6f69085f197
2025-08-21 14:37:46 +00:00
Raito Bezarius e7253d0621 Revert "libstore: make temporary path prefixes optional"
Revert submission 3850

Reason for revert: caused multiple regressions noticed in https://git.lix.systems/lix-project/lix/issues/975 and https://git.lix.systems/lix-project/lix/issues/966 (suspected).

Root cause analysis has not been done yet and this breaks Lix on Darwin on HEAD.

Reverted changes: /q/submissionid:3850

Change-Id: Ib4d3179cbfa8e9f861d7416fe08f7d7e3a7d55e2
2025-08-21 14:37:46 +00:00
Raito Bezarius c9cbbc4f63 Revert "tests: remove obsolete code to create custom build direc..."
Revert submission 3850

Reason for revert: caused multiple regressions noticed in https://git.lix.systems/lix-project/lix/issues/975 and https://git.lix.systems/lix-project/lix/issues/966 (suspected).

Root cause analysis has not been done yet and this breaks Lix on Darwin on HEAD.

Reverted changes: /q/submissionid:3850

Change-Id: I56f871786760eb9f54c02773f0617de9878c1382
2025-08-21 14:37:46 +00:00
Raito Bezarius 9cabe56fcd Revert "libstore: don’t include derivation names in build direct..."
Revert submission 3850

Reason for revert: caused multiple regressions noticed in https://git.lix.systems/lix-project/lix/issues/975 and https://git.lix.systems/lix-project/lix/issues/966 (suspected).

Root cause analysis has not been done yet and this breaks Lix on Darwin on HEAD.

Reverted changes: /q/submissionid:3850

Change-Id: Iee4c3f071238fa58e0e92f0bcc5584ded4a9d71f
2025-08-21 14:37:46 +00:00
Jade Lovelace 61955d0a40 libexpr: hyperlink attr names to their definition locations
Concept: what if you could, in your fancy terminal, in the year of our
lord 2025, just click on the attrs you're looking at to go to where
they're defined. Currently we only expose this info as
builtins.unsafeGetAttrPos, which is inconvenient as it's not
discoverable to users.

By putting it in this more visible yet invisible spot, it's more likely
to be more useful to more people.

In the current state, this is not the most useful ever due to stuff like
https://github.com/neovim/neovim/discussions/35097. However, it can be
expanded by perhaps adding something like the url format setting ripgrep
has.

Change-Id: I3947f97d5c2056d59099af468d7b855486438227
2025-08-20 20:55:54 +00:00
Alois Wohlschlager c82af241f5 packaging: fix static build
Normally `pkgsStatic` adds ` -static` to `NIX_CFLAGS_COMPILE`. Due to a bug
this did not apply with `__structuredAttrs`. As the fix [1] has not been
backported yet, put it in the package manually.

[1] https://github.com/NixOS/nixpkgs/pull/428430

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

Change-Id: I6a6a6964c6a33f486ba3df3be16f715ad1b060c3
2025-08-20 18:31:52 +02:00
Alois Wohlschlager 010dae39d3 capnproto: fix platform offset
Backport of https://github.com/NixOS/nixpkgs/commit/8b0263aba578e2fa611a528bedcfc5cf4d5a687c .

Change-Id: I6a6a6964917df3610cd728c696ecdcd8ea6e61a4
2025-08-20 18:25:57 +02:00
eldritch horrors 001c70d2ba libstore: mark all non-local stores as thread-unsafe
this pretty much only impacts store verification via the nix3 cli. no
other thread pools are left, and the verification pool may *actually*
be important for throughput since verification involves much hashing.

Change-Id: I32152e6169a82a1268a790e333f21a0430ede7f4
2025-08-19 13:08:53 +00:00
eldritch horrors 3cecd2306b nix/sigs: remove sign pool
signing is very cheap, it's only the store access that is expensive.
http binary caches parallelize async accesses extremely well though.

Change-Id: Ifdbf398bd328ba16ec4e8caba3f5f99a6cf3e046
2025-08-19 13:08:53 +00:00
eldritch horrors d63edddeb0 libstore: remove debug info upload pool
Change-Id: Ife5f69ddaeb82f002f4dfe05347fe700d201df72
2025-08-19 13:08:53 +00:00
eldritch horrors 13509ef773 libstore: rewrite debug info upload with generators
Change-Id: I1b0424c3b8aed5a90d1ca3d61b11cc5a1bf92cf0
2025-08-19 13:08:53 +00:00
eldritch horrors c917f4ec78 nix/sigs: remove CopySigs thread pool
we don't have benchmarks for this one, but a 10x improvement seems likely.

Change-Id: I76f4e9c9ebff86fd7d451ed7e125ab309011457e
2025-08-19 13:08:53 +00:00
eldritch horrors 20f84eb6bf libstore: remove queryValidPaths thread pool
this is used by nix-env and copyPaths, which in turn is used to upload
to binary caches. for a large path set we have seen 10x a improvement.

Change-Id: Ieadd0e66180e5ceecefaf944a5bb2f0523374954
2025-08-19 13:08:53 +00:00
eldritch horrors 5fc6ab2e50 libutil: make generator iterators adl-visible
lets us use generators in algorithms that use adl iterator access

Change-Id: I05b06f070e370ef21c693e61979d07d9b7206a9f
2025-08-19 13:08:53 +00:00
eldritch horrors 5a30005e2f libutil: remove unused runAsyncInNewThread
Change-Id: I55d161201737fc6b7dbfb0a387b253939dfe5784
2025-08-19 13:08:53 +00:00
sternenseemann e339480592 Test build with lowdown < 1.4 in CI
Change-Id: I486dec6f2d91580d21712c1f4f18462aa02473b3
2025-08-19 10:45:40 +00:00
Commentator2.0 d8b1fb7799 tests/functional2/lang: replace toml with tomllib
currenlty we use the external package `toml`, this just adds an
unnessecary dependency, as python ships its own toml as `tomllib`

Change-Id: Ia63fa7558973e853ada20cbfa21d897d700444f8
2025-08-18 20:08:09 +02:00
WeetHet 1f47ecef4e libstore/build: adjust setupConfiguredCertificateAuthority
Always use tmpDir on darwin. Call setupConfiguredCertificateAuthority even if useChroot = 1 on non-linux.
Even though macOS builds are not executed in a chroot, enabling the sandbox
sets `useChroot = 1`. Basically, useChroot is set when the sandbox is enabled,
not really when a chroot build is executed

Change-Id: I8d4c1e617abcc05dfabd998a8ce94bb11587f9d1
2025-08-18 13:08:42 +00:00
Emily ce6dcf18d6 libstore: don’t include derivation names in build directories
They have variable size, which is bad for #913.

Change-Id: I6a6a6964a84804dee281ade48e6517419a6143fa
2025-08-18 09:27:31 +00:00
Emily 246f0eed82 tests: remove obsolete code to create custom build directories
We now do in fact do this and decide this.

Change-Id: I6a6a69643af2449370df001f8be787eba889b857
2025-08-18 09:27:31 +00:00
Emily 6aedc0ee70 libstore: make temporary path prefixes optional
This is not the same thing as passing an empty string, because it
avoids the `-` separator.

Change-Id: I6a6a69646aa76953078a08c606e8f82c9ec03e8e
2025-08-18 09:27:31 +00:00
Emily 0173d0dde4 libstore: simplify createTempDir interface
We always use the default temporary directory, because
`createUniqueDir` has an interface nice enough to use directly for
the few bespoke uses.

Change-Id: I6a6a6964c15c31bb3e131fbe1db1837987a6d6dc
2025-08-18 09:27:31 +00:00
Emily 2a17164865 libutil: use makeTempPath in createTempSubdir
This makes the paths more nondeterministic, but more reliably unique,
and lets us remove the retry loop.

Note that this adds random entropy to the build directory visible
inside derivations on Darwin and unsandboxed Linux. It was already
non‐deterministic in the presence of concurrent builds and similar,
but now we can reliably expect it to be different every time. On the
whole I think that’s a good thing, as it is impossible to ensure
a single consistent build directory and derivation outputs should
not depend on it.

Package reproducibility isn’t great on Darwin to begin with,
though, and the reproducibility bugs this will turn up in packages
will be more urgent to fix than when the build directory was mostly
consistent. A quick survey of my local store shows that many C, C++,
and Rust binaries contain build directory references, likely due to
use of `__FILE__` and its equivalents; non‐binary offenders include:

* Install logs included in the Rust and Cargo bootstrap compilers
* Example errors in the Rust documentation referencing build paths
* Configuration information installed with CPython itself
* Python 2 metadata from resholve’s closure
* Cython metadata
* Generated headers in Facebook libraries referencing source paths
* Generated CMake files in Facebook libraries referencing source paths

I haven’t built that much in this store since the last GC, so this is
probably only a small sample of the problems across the tree. These are
all instances of <https://reproducible-builds.org/docs/build-path/>,
though, and should probably just be treated as general reproducibility
bugs outside of contexts like the Linux sandbox where we can normalize
them away entirely.

I have implemented away build directory paths for C/C++, applied some
additional fixes for non‐`__FILE__`‐related issues in binaries
from ATF and LLVM, and fixed the derivation bug causing the CPython
3 issue, and will work on upstreaming these changes. Rust is working
on the problem upstream, with some temporary workarounds we can
potentially apply in Nixpkgs for now. The rest will require some
distributed effort.

Change-Id: I6a6a69648f74d85c6fca86cc52f38fd957e4f9ad
2025-08-18 09:27:31 +00:00
Emily 267d9e100c libstore: simplify fallback build directory logic
This does change the behaviour when the global temporary directory
does not exist, but other uses of the global temporary directory are
already broken in that circumstance, and it should be fixed centrally
if the use case is considered desirable. The logic was not present
before the recent churn around build directories – it was added now
that Lix is taking ownership of the build directory in the store –
so this should not be a meaningful regression.

Change-Id: I6a6a69648054ae201b3ce36d11e49c93793fdb0e
2025-08-18 09:27:31 +00:00
Emily cad304420f libstore: use makeTemp{,Sibling}Path more
Change-Id: I6a6a69641f9c9d3ab339d25d061dad6cd1f8416d
2025-08-18 09:27:31 +00:00
Emily 24ccf500d1 libstore: simplify makeTemp{,Sibling}Path callers
There is now no risk of race conditions on a system with a functioning
entropy source, and the bespoke prefixes are either redundant to the
default or unnecessary.

Change-Id: I6a6a69648a3b8060333e97269ea8b72499614559
2025-08-18 09:27:31 +00:00
Emily 03beb9a1d3 libutil: use OS‐provided entropy for temporary filenames
Relax the constraints on keeping the exact same filename format to
provide a more robust source of entropy with a simpler interface
(as previously suggested by eldritch horrors). Using 128 bits of
OS‐provided entropy ensures global uniqueness and allows us to
skip any thought of gracefully handling the case where these files
already exist.

My microbenchmark that repeatedly constructed paths like this and
printed them out showed that this takes about 1.23× the time of
the previous implementation, both taking on the order of a couple
microseconds for one iteration. Since everything that uses it is doing
things more expensive than printing to standard output, the actual
performance delta is likely to be lost in the noise. If it somehow
becomes a bottleneck, it can be optimized without sacrificing the
guarantees by reading from the system RNG only to seed a thread‐local
CSPRNG like [ChaCha8Rand], but I think that’s very unlikely.

We also tweak the recommended way of creating a temporary file inside
a directory in anticipation of later changes, and rename the `suffix`
parameter to `prefix` (it’s a prefix to the random characters and
a suffix to the root, but this way is more consistent).

[ChaCha8Rand]: https://c2sp.org/chacha8rand

Change-Id: I5bd7badf1392243f485935c4a016c1f833cb16d3
2025-08-18 09:27:31 +00:00
Emily 76baa4c50d libutil: extract Base32 helpers from Hash
Change-Id: I6a6a6964f95aecf152090a3bf82b5ec287a21481
2025-08-18 09:27:31 +00:00
Emily a42cb1c43d libstore: use makeTempSiblingPath in replaceValidPath
Change-Id: I6a6a69643732c1bb4942425ae1f179a411412fd5
2025-08-18 09:27:31 +00:00
Emily e2d85579d8 libutil: add makeTempSiblingPath helper
The prospective callers of this should probably be doing something
smarter or more abstracted to begin with, but this is useful as an
incremental improvement for call sites with existing `makeTempPath`
logic in the face of filename length limits.

Change-Id: I6a6a6964374f47abbf0ec10aa8d945c4e50a43af
2025-08-18 09:27:31 +00:00
eldritch horrors 3de996f521 libexpr: stringviewify some more APIs
notably this also includes the symbol table because it stores real
strings that are referenced by eval values, and an upcoming change
will make it impossible to share those strings with value strings.

Change-Id: I20a3644db8aa0850efe29630e0b73d424cb2aa56
2025-08-17 14:55:13 +02:00
eldritch horrors 8208c6ebb7 libexpr: don't read Value::string.s directly
Change-Id: I0f224459fcbff8bc53c3668bc6ea52881c453fd0
2025-08-17 12:11:19 +00:00
Emily 27f7075f4d libexpr: remove the parse-toml-timestamps experimental feature
See [my comment] on the Nix PR to restore the previous behaviour
for why I believe we should remove this for the next release. The PR
should still be backported to stable releases to avoid making breaking
changes to their semantics.

[my comment]: <https://github.com/NixOS/nix/pull/13741#issuecomment-3180851635>

Fixing this across supported Lix versions is required for Nixpkgs to
update toml11, which is a blocker for the CMake 4 update.

Change-Id: I6a6a69642e6b6cb13a9fccc0778e9158b53102d5
2025-08-16 14:27:13 +01:00
4de09b6b54 packaging: bump toml11 to 4.4.0
This version changes the handling of TOML timestamps, and throws an
error on out‐of‐range integer literals rather than the previous
saturating behaviour, as required by [the TOML v1.0.0 specification]:

> Arbitrary 64-bit signed integers (from −2^63 to 2^63−1) should be
> accepted and handled losslessly. If an integer cannot be represented
> losslessly, an error must be thrown.

[the TOML v1.0.0 specification]: <https://toml.io/en/v1.0.0#integer>

The only known use of this is a questionable Nixpkgs test that I have
proposed [a fix] for.

[a fix]: <https://github.com/NixOS/nixpkgs/pull/433710>

Bumping this ahead of Nixpkgs ensures we can test these cases on
HEAD in advance. I presume that the next Lix major version will be
released after 25.05 goes out of support, so it should be fine to
drop support for the old version of toml11.

The co‐authors of this commit are the contributors to the vendored
package definition from Nixpkgs.

Co-authored-by: Anderson Torres <torres.anderson.85@protonmail.com>
Co-authored-by: Artturin <Artturin@artturin.com>
Co-authored-by: Silvan Mosberger <silvan.mosberger@moduscreate.com>
Change-Id: I6a6a69644a188b6e09eee5c9cf91ddd3c81d24ee
2025-08-16 14:27:13 +01:00
Emily 7ee442079d tests/functional2/lang: add tests for builtins.fromTOML overflow
This behaviour is bad and will be fixed, but adding a test for the
present state documents the change.

Change-Id: I6a6a6964b88dc929269ee136804857b3852dfafe
2025-08-16 13:39:20 +01:00
Sergei ZimmermanandEmily 2898b9e7dc libexpr: Canonicalize TOML timestamps for toml11 > 4.0
This addresses several changes from toml11 4.0 bump in
nixpkgs [1].

1. Added more regression tests for timestamp formats.
   Special attention needs to be paid to the precision
   of the subsecond range for local-time. Prior versions select the closest
   (upwards) multiple of 3 with a hard cap of 9 digits.

2. Normalize local datetime and offset datetime to always
   use the uppercase separator `T`. This is actually the issue
   surfaced in [2]. This canonicalization is basically a requirement
   by (a certain reading) of rfc3339 section 5.6 [3].

3. If using toml11 >= 4.0 also keep the old behavior wrt
   to the number of digits used for subsecond part of the local-time.

[1]: https://www.github.com/NixOS/nixpkgs/pull/331649
[2]: https://www.github.com/NixOS/nix/issues/11441
[3]: https://datatracker.ietf.org/doc/html/rfc3339

(cherry picked from commit dc769d72cb8ad22a0f89768682b5499a9d2b3d8b)
Upstream-PR: https://github.com/NixOS/nix/pull/13741
Change-Id: Iac4fbe5108be79be585e9670fa42dfd11f3c5e89
2025-08-16 13:39:20 +01:00
Sergei ZimmermanandEmily 19d9a87c2f libexpr: Use table.size() instead of unnecessary loop
(cherry picked from commit d8fc55a46e0c09241131097dbf1d6fa09e0a9808)
Upstream-PR: https://github.com/NixOS/nix/pull/13741
Change-Id: I8a11e21ae3bff3a885e13fbab74e1deb162a34cf
2025-08-16 01:20:12 +01:00
Sergei ZimmermanandEmily 5badc1bc8a libexpr: Use recursive lambda instead of std::function
There's no reason to use a std::function for recursive lambdas
since there are polymorphic lambdas.

(cherry picked from commit a80a5c4dba0d944fab8f5ed57a343869ae96bf16)
Upstream-PR: https://github.com/NixOS/nix/pull/13741
Change-Id: I593bd04597e2ae000374ca1eca4d8928e986c0b5
2025-08-16 01:20:12 +01:00
Sergei ZimmermanandEmily 2ca5670ec3 libexpr: Remove extra trailing semicolons (NFC)
This looks really weird after the reformat.

(cherry picked from commit df4e55ffc13c413e270af134227115a20a2341ba)
Upstream-PR: https://github.com/NixOS/nix/pull/13741
Change-Id: I8de92d58620cc4545a31d8b7d533d2f1e9f4f233
2025-08-16 01:14:07 +01:00
Emily 86303ffb05 libexpr: treat too‐large unsigned JSON integers as floats
JSON has only one numeric type, with vague semantics. [RFC 8259] says:

> This specification allows implementations to set limits on the range
> and precision of numbers accepted.  Since software that implements
> IEEE 754 binary64 (double precision) numbers [IEEE754] is generally
> available and widely used, good interoperability can be achieved by
> implementations that expect no more precision or range than these
> provide, in the sense that implementations will approximate JSON
> numbers within the expected precision.  A JSON number such as 1E400
> or 3.141592653589793238462643383279 may indicate potential
> interoperability problems, since it suggests that the software that
> created it expects receiving software to have greater capabilities
> for numeric magnitude and precision than is widely available.
>
> Note that when such software is used, numbers that are integers and
> are in the range [-(2**53)+1, (2**53)-1] are interoperable in the
> sense that implementations will agree exactly on their numeric
> values.

[RFC 8259]: <https://www.rfc-editor.org/rfc/rfc8259.html#section-6>

Floating‐point numbers are annoying to deal with in Nix, so it
optimistically parses integer‐looking literals as Nix‐language
integers where possible. Nixpkgs relies on this behaviour, as it backs
its `lib.toInt` family of functions with `builtins.fromJSON` in lieu
of a real integer‐parsing built‐in, and treats floating‐point
outputs as an error. Therefore, dealing with integer‐looking
JSON number literals that are outside the interoperable range is
unavoidable.

However, this raises the question of how literals that look
like integers, but exceed the range of a Nix‐language integer,
should be handled. The JSON library we use attempts to represent
integer‐looking literals as a unsigned or signed C++ integer type
before falling back to floating‐point numbers.

This means that we were parsing literals below −2⁶³ as
floating‐point numbers, while rejecting ones above (2⁶³ + 1)
with an error. This was done to avoid the C++ undefined behaviour in
the previous code path, but is hard to justify. This change causes
them to both be parsed as floating‐point numbers.

The alternative would be to reject the signed case too. However,
I believe that is less consistent with how JSON handles its single
numeric type, less interoperable with JSON documents found in the wild,
and that it is preferable to avoid the function being needlessly
partial in this case. It does mean that round‐tripping is less
lossless than before, but extreme floating‐point values already
caused these kinds of issues, and from JSON’s point of view that’s
exactly what these literals represent. Only numeric values outside the
RFC’s suggested interoperable range will have any round‐tripping
issues, and we continue to guarantee the behaviour on all values that
can be represented as Nix‐language integers.

Change-Id: I6a6a696412383e8a2cc160397716cb7f7bc7a2d4
2025-08-15 23:46:14 +01:00
eldritch horrors 935901cd46 nix/ls: fall back to downloading the nar if the .ls is bad
fixes #959

Change-Id: I1715f6fcc938dac7287d9cf4a85e93c9d66768a0
2025-08-15 18:05:06 +02:00
eldritch horrors 612d553a4f libstore: fail makeLazyNarAccessor for broken .ls files
Change-Id: I58ecc89046ee5fc58d05c980acb3616e91ee4741
2025-08-15 18:05:06 +02:00
eldritch horrors c3bfb6fe17 testing: unbreak functional tests in devshells
turns out that adding busybox to checkInputs just totally trashes f1 testing.

Change-Id: Iac8d49a59a39c44e0663d7ae760e0a0473ebf7fd
2025-08-15 15:26:27 +02:00
Commentator2.0andRutile 7869385b98 tests/functional2: Make Command Environment actually declarative
So far, the environment used by `command` was completely leaky and the
one used by `nix` was very leaky despite it trying to be a "hermetic"
environment.
This commit moves the hermaticity to `command` and changes its
implementation to be not leak anything.

To achieve this, the following changes were also nessecary:
- the `files` and `snapshot` fixture now use the folder `test-home`
within the tmp_path directory by default, as the `HOME` environment
variable is set to there. (extraction not possible due to dependencies
of command etc also using this directory)

Fixes: #847, #848

Change-Id: I55f86ee0e1615e73fcf442ee2f28f3b89893bbb4
2025-08-15 06:56:07 +00:00
Sergei ZimmermanandEmily b2e48aac5c tests/functional/lang: Add more tests for TOML timestamps
Current test suite doesn't cover the subsecond formatting at
all and toml11 is quite finicky with that. We should at the very
least test its behavior to avoid silent breakages on updates.

(cherry picked from commit 7ed0229d1abd4414144c7af396842462ce6fc1eb)
Upstream-PR: https://github.com/NixOS/nix/pull/13741
Change-Id: I6a6a696433b168072d6ad2585dce8a3c10ccbc39
2025-08-15 01:11:52 +01:00
Emily c586596a9f libexpr: format fromTOML source
Otherwise the next diff becomes very messy.

Change-Id: I6a6a6964d96543ade130d491f413ebd9fe2b7ff1
2025-08-15 01:11:52 +01:00
Emily d84bc45e17 tests/functional2/lang: add test for underflowing JSON number literal
Change-Id: I6a6a6964c49a82244f7c0062c27ec9a5feadd910
2025-08-15 00:19:25 +01:00
EmilyandPhilipp Otterbein 860984be27 tests/functional2/lang: add tests for NUL bytes in JSON and TOML
Based on the tests in the upstream PR.

Co-authored-by: Philipp Otterbein <potterbein@blockstream.com>
Upstream-PR: https://github.com/NixOS/nix/pull/12024
Change-Id: I6a6a69643ae9bd2445e551013d09e64ac1fe9916
2025-08-14 22:19:29 +01:00
Emily 5359ab9a76 doc: update emilazy in change-authors
Change-Id: I6a6a6964bf752f27fde54d3bb3f94afdae4fe139
2025-08-14 19:56:18 +01:00
janw4ld 15a42d21a1 libexpr/flake: add inputs.self.submodules flake attribute
a port of <https://github.com/NixOS/nix/pull/12421> from CppNix, gated behind a `flake-self-attrs` experimental feature.

Fixes: https://git.lix.systems/lix-project/lix/issues/942
Change-Id: I655ce089e603d95abc693d6b77130fcf00c7f456
2025-08-13 03:05:49 +00:00
eldritch horrors 4a119e6e46 libutil: support async deserialization on musl
use a thread pool and allow only buffered sources for performance. we
may want to use this code path unconditionally due to gc interactions
of fibers in circumstances we should not even be able to trigger, but
if that becomes important we will have a useful implementation ready.

Change-Id: Ib4e1531fe920847d8e30a42e8df393ace549f52e
2025-08-12 14:14:35 +02:00
Linus Heckemann 52ef1181f0 build: fix cross-compilation
We need capnproto-lix to be provided by callPackage, otherwise it won't
be spliced and the same (built for the cross host platform) for both
buildInputs (correct) and nativeBuildInputs (incorrect). We thus move it
into the overlay.

A similar problem exists for the lowdown build. We thus use callPackage
to override it as well. This is horrible (especially because it means we
need to pass the enableDarwinSandbox package argument through
ourselves), but at least it builds...

Fixes: https://git.lix.systems/lix-project/lix/issues/939
Change-Id: I802152072d852903401ef701f526195aa99475f2
2025-08-08 09:48:42 +02:00
Commentator2.0andCommentator2.0 8bbd5e1d0d tests/functional2: do not copy testlib tests to internal environment
When testing specific internal functionallity while needing things from
the testlib, so far, the tests for the testlib have always been copied
too.
To reduce the amount of additional program required when later making
the env of the pytest_command declarative, and to not test the same
tests a multitude of times (and potentially reaching infinite recursion)
those tests will no longer be copied

Change-Id: I36ec3824a21ed30f9b8ff19948031d1edbf6c76c
2025-08-07 14:25:51 +02:00
eldritch horrors a9117791e2 libutil: remove unused FdSource::specialEndOfFileError
Change-Id: Ia03e270c359014ab3675a043bad342c4bbfdbafd
2025-08-06 14:43:47 +00:00
eldritch horrors 22cf5eb989 libstore: use async io for remote store IO
this means both the worker protocol and the serve protocol, i.e.
ssh-ng/local connections and legacy ssh connections. now we have
no blocking reads left anywhere in our client store connections.

Change-Id: I2f628d4d2e71ef0a7006918f175192f3f58eea95
2025-08-06 14:43:47 +00:00
eldritch horrors db0ed505e9 libutil: add async serialization helpers
Change-Id: I5c123e1ac31172d61c9e1d293e99ca1022bcec4e
2025-08-05 19:10:46 +00:00
eldritch horrors 7e8b44d718 libutil: allow access to async stream buffers
this is easier than remembering which buffer was used during construction.

Change-Id: I64a8bdcbc0104c5a4a3330266b508cfd9297852e
2025-08-05 19:49:11 +02:00
Maximilian BoschandMic92 cad6118e20 subprojects/nix-eval-jobs: don't set NIX_PATH
Corresponding upstream change: https://github.com/nix-community/nix-eval-jobs/commit/f584ae856ebab2d20115c5d1424f4d2fb9e0e6a6.patch

Co-authored-by: Mic92 <joerg@thalheim.io>
Change-Id: Id6085968f7f4ccdbc8035f608ff75420fbf962a5
2025-08-02 14:21:13 +02:00
Emily 52872026fb packaging: actually run install checks
`mesonCheckPhase` of course runs `preCheck` and `postCheck` hooks,
not `preInstallCheck`, so this was not doing the right thing. Really
sorry for breaking CI :(

Fixes: e6da29ad6b
Change-Id: I6a6a69642a242e2c8bfe10ac61d3f6756d666548
2025-08-01 18:17:47 +01:00
Maximilian Bosch f077a6f36e libstore/binary-cache-store: don't cache narinfo on nix copy, remove negative entry
Snix's nar-bridge[1] stores NARs under a different URL, i.e.
`nar/snix-castore/<hash>.nar` rather than `nar/<filehash>.nar`. Right
now, when copying into such a store via the http binary-cache, we'd end
up with wrong cache entries that point to the wrong NAR URL.

On Hydra, this is a fatal error, i.e. builds that depend on previously
built paths (that were written to the cache before by the queue runner)
would be aborted because of that.

This patch removes the caching since we'd have to re-fetch the narinfo
to do taht and this can also happen the next time, the narinfo is
queried. Also, removes the negative cache entry indicating that the
store-path doesn't exist in the store.

We don't have any coverage for http-stores so far, so I wrote a small
testcase for the "default" case and the nar-bridge case in functional2
since it has a very nice fixture for an HTTP server ready. I'm aware
that there's a CL for a nicer cache server[2], but given I'm down a
pretty deep rabbit hole by playing around with Snix, I decided to not
finish the CL and write something small for the tests in here. This can
be replaced by the fixtures from that CL later on as well.

[1] https://snix.dev/docs/components/overview/#nar-bridge
[2] https://gerrit.lix.systems/c/lix/+/2431/1

Change-Id: I4fcdf47a6bf9c3c8fbeb235eeca7a48914a4d693
2025-08-01 12:44:28 +02:00
eldritch horrors d5cfc6f19c libstore/remote: frame buffers, don't buffer frames
shoving a nar dump directly into a framed sink created a bunch of tiny
frames, each of which requires at least two syscalls to read. this can
lead to immense performance loss when using a daemon; we have seen 15%
in benchmarks on main and even more with async code involved ... oops.

Change-Id: I8529506e3de74d92834d1f4ee228dcaf32eb756c
2025-08-01 01:10:50 +02:00
eldritch horrors 98a27fbfd2 bench: soup up the benchmark script
add daemon support, fix the rebuild cases by using an installer config

Change-Id: I47cbb0dd401abf5e041e9936077e502f5e0cabf9
2025-07-31 15:15:24 +00:00
Alois Wohlschlager becb2f0002 packaging: fix pkg-config files
Notably, this adds the Requires.private required for proper static linkage of
Lix libraries. Some minor missing or duplicated dependencies are also fixed
along the way (although some optional dependencies are omitted due to their
size).

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

Change-Id: I6a6a696413d538124d9ac75c68f100cc3089284f
2025-07-31 06:43:58 +02:00
Alois Wohlschlager e69aaa5f65 packaging: only install lix_doc for static builds
The stated rationale for installing lix_doc static library only applies to
static builds. Doing it even for dynamic builds is unnecessary and has lead to
the questionable practice of deleting the library in the nixpkgs packaging [1],
in turn contributing to breaking downstream static linkage.

[1] https://github.com/NixOS/nixpkgs/blob/bbb1dbe938ff2341e6618872671de7432c98078f/pkgs/tools/package-management/lix/common-lix.nix#L294-L296

Change-Id: I6a6a69642be04c6ee6976e324ab87abf1e112b90
2025-07-30 06:18:39 +02:00
Alois Wohlschlager dfb9e77c22 packaging: fix dependencies
A bunch of dependencies were superfluous, while others were missing (in
particular on internal sub-libraries) and things just happened to work because
they leaked in or were explicitly required in other places. Make efforts to fix
them all.

Change-Id: I6a6a69643e35ac4b0b66a72f4e42a2ba5ed52488
2025-07-30 06:18:39 +02:00
Alois Wohlschlager 480fdf146d packaging: prelink static libraries that need to be linked fully
Some of the Lix libraries always need to be linked in full due to their
reliance on static initializers. This was achieved internally using link_whole,
but they are still easy to abuse by external users who manually need to
remember passing linker flags such as `--whole-archive` (GNU) or `-force_load`
(Apple), and the obvious way to shove it in pkg-config breaks Meson due to
potentially including a library's flags multiple times, and then deduplicating
only the file names leaving a stale `-force_load` around causing trouble.

Instead we now "prelink" the static libraries, by merging them into one object
file. Since the static linker will always link entire object files, this will
have the same effect as whole-archive linking (except the library won't be
included if it's completely unused, which should not cause trouble since it's
unused after all, and dynamic libraries behave the same way). Unfortunately
Meson's native prelink functionality cannot be used due to missing (non-Apple)
Clang support [1], so write our own one. While not particularly portable, it
should work with Clang which is the only officially supported compiler, as well
as GCC.

[1] https://github.com/mesonbuild/meson/pull/14846

Change-Id: I6a6a6964a82241ce3b0b11fe8397fd451b8027f2
2025-07-30 06:18:39 +02:00
Alois Wohlschlager 321807a40e packaging: stop requesting linkage against boost_container
Meson only requires the `modules` option for Boost libraries that should be
linked against [1]. However, we use only header-only portions of the Boost
container library; in fact we only do not run into the disallowedReferences
because the linker drops the unused library. Remove the misleading option.

[1] https://mesonbuild.com/Dependencies.html#boost

Change-Id: I6a6a69648b806bf6bcf784391263f5ee9cd63a0b
2025-07-30 06:18:39 +02:00
Emily 93acdd40f6 packaging: remove redundant apple-sdk_11
This is now the default in all supported Nixpkgs versions.

Change-Id: I6a6a69642951c2e1ae117d8360b450cc9fdc741b
2025-07-29 20:45:58 +01:00
Emily 99cf661187 packaging: remove redundant --print-errorlogs
These were added to Nixpkgs in
4dec85d68b81ed971e7f2953cfea895f0f0c0c2c, back in 2024.

Change-Id: I6a6a69646f6a09fb3d8bae3efbf9d6e56d662ca3
2025-07-29 20:45:58 +01:00
Emily e6da29ad6b packaging: use the upstream mesonCheckPhase for installCheckPhase
This gets us `--print-errorlogs` and `--timeout-multiplier=0` for free,
and also fixes the concatenation logic with `__structuredAttrs`.

Change-Id: I6a6a69643ea7224cb24508505c554143f83ae243
2025-07-29 20:45:58 +01:00
OPNA2608 8c2891ae14 meson: Actually use host_cpu in host_system, expand fixups
Otherwise we'll get system names like `x86-linux`, which is nonesense.

Also add fixups for 32-bit ARM, and MIPS.

Change-Id: I6bd773bec82dcc54b40bbc018641cd614b272a28
2025-07-29 14:38:52 +00:00
eldritch horrors f0150eb8a3 libutil: add AsyncFramedInputStream
the async version of FramedSource, with all its weaknesses for bug-compat.

Change-Id: Ifcdd4a5819f7cf25a0e8b01c63975ffead54079c
2025-07-29 13:37:53 +00:00
eldritch horrors 96200d5768 libstore: use async io from RemoteStore::Connection::processStderr
this lets us avoid a bunch of fcntl calls to unset and re-set O_NONBLOCK
on remote store connections. the overhead of these isn't high, but doing
it is still wasteful and a maintenance burden when we have async readers

Change-Id: I900cdca2a16202380c8b6f9b86da7d9b0f1e34ac
2025-07-29 13:37:53 +00:00
eldritch horrors 7f22e9e6cc libutil: duplicate deserializers for async streams
this is needed to asyncify the wire protocol deserializers.

Change-Id: I8087c852ab7b64bc1749310aa1493912af8b1c6e
2025-07-29 13:37:53 +00:00
eldritch horrors 45d81027e3 libutil: remove readInt, readLongLong
the former isn't even int, it's unsigned. the latter is uint64. both
should be explicit about this to avoid problems that we have already
had, such as the FramedSource wire protocol writing using 64 bits to
write frame sizes, but 32 bits to read them. large frames will cause
the reading end to crash with an unnecessary serialization exception

Change-Id: I3c15e911f649eec719d4b1c135dde1b6ba020271
2025-07-29 12:25:56 +00:00
eldritch horrors 7923dcc034 libtuil: remove deserializing operator>>
they will not work well with async deserialization and are not used
consistently anyway. just like the serializing operator<< these are
protocol stability hazards: changing the type of a field influences
the wire protocol layout and type constraints, which is not amazing

Change-Id: I54b20a133048f4ca15a9fb0f4d8b94dc78f62d89
2025-07-29 11:54:33 +00:00
eldritch horrors d647bf7d0a libutil: remove unused readString overload
Change-Id: I8419efe895617b5473170f3cbb76697f0aa2def2
2025-07-29 11:47:40 +00:00
eldritch horrors 51a7f1f37a libutil: add AsyncInputStream::readRange
this is an equivalent of the regular kj read interface which also takes
a min/max pair. we do not need this very often though, so we'll keep it
as a separate method for now. if we do find we use it more than read we
can still rename read to readSingle and readRange to read. we will see.

Change-Id: Ib04ca146911adae7081cf4b2df097217ea5fe9f8
2025-07-29 11:47:40 +00:00
eldritch horrors 874e878ba0 libutil: move LengthSink into sole user
this should've been a filter from the start. since filter support in the
old IO model is very bad we just move it into the only use of it instead

Change-Id: Ifb9cfecf07587ae1d2d55072ddf505c86c79cc1b
2025-07-29 10:42:00 +00:00
eldritch horrors db7c3a88b2 libutil: move SizedSource into its only user
we should not encourage this kind of framing. in the future we will have
to do this on async streams, which we *absolutely* should not encourage.

Change-Id: Ib89e144afb564284db64fc7367cba7fffc18fdaf
2025-07-29 10:42:00 +00:00
eldritch horrors 5c2ee71c61 libstore: update exceptionAbortsDownload test
the exception no longer actually matters, only that the download stream
is destroyed before the download finishes does. exceptions during drain
calls on the returned stream will cause the stream to be destroyed, but
draining this stream is just a remnant of the old download-to-sink API.

Change-Id: Ic6de40deb2ccff09d77180148afe746f29f55d23
2025-07-29 10:42:00 +00:00
eldritch horrors 0acb43f6d3 libutil: pass owned sources to makeDecompressionSource
we lose reference lifetime constraints for minimal runtime overhead.

Change-Id: I198b521a0fc56f9a3499ec1d6ae9aa8655daa59e
2025-07-29 10:42:00 +00:00
Qyriad fde2a4be63 rnix: 0.11.0 -> 0.12.0
Fixes rnix not parsing files with chained pipe operators in them. Though
honestly I'm not sure how this was working before with any pipe
operators at all?

See: https://github.com/nix-community/rnix-parser/pull/167

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

Change-Id: Ifbd737a02bd43f994bfef61d035d40391836aeb9
2025-07-29 09:51:33 +00:00
OPNA2608andRaito Bezarius ce3e9e81b0 meson: Fix Meson CPU names for powerpc CPUs
Nixpkgs expects the `builtin.currentSystem` for POWER CPUs to be:

`powerpc[64][le]-linux`

But using `host_machine.cpu_family()` for the CPU part of the system
string on POWER produces this instead:

`ppc[64]-linux`

So evaluating Nixpkgs errors out on:

`error: Unknown CPU type: ppc64`

To fix this, change
`ppc` -> `powerpc`
`ppc64` -> `powerpc64`
and append `le` if `host_machine.endian() == 'little'`.

I can't actually test this on hardware rn due to hitting a kernel bug
on the host system when linking big things[1], but the approach here
is similar to how it was fixed in cppnix[2][3], so it *should* be fine.

[1] https://git.adelielinux.org/adelie/packages/-/issues/1315
[2] https://github.com/NixOS/nix/pull/13514
[3] https://github.com/NixOS/nix/pull/13520

Change-Id: Ib82839cdaf2198bf18b89e82caaa1217f88e11ed
2025-07-29 05:46:08 +00:00
jadeandLix Systems Gerrit 2b42901ec7 Revert "contrib/plugins: init with mTLS store example"
Revert submission 3698

Reason for revert: Regressed the build, needs some rework.

Reverted changes: /q/submissionid:3698

Change-Id: I651e35435f1006c0abf8c37feb3aa385ac6d4256
2025-07-29 03:34:49 +00:00
569dcf9392 contrib/plugins: init with mTLS store example
This is a collection of Lix plugins that showcase how to write one for
various usecases.

The first is a mTLS store plugin that enable mTLS cache URIs
(`https+mtls://`).

We enable meson build system support for this plugin but we are not
going to distribute it in the official packaging of Lix, we will
repackage each relevant plugin downstream in Nixpkgs.

These plugins have *NO* guarantee support, they are provided as useful
references and are possibly production-ready if your usecase is simple
enough.

Reference: https://github.com/NixOS/nix/pull/13030 (this change has
resemblances but our APIs are different, the tests harness is mostly
from CppNix).

Change-Id: Ib354271981b35dff6c134b12c4748c3eaf743fcb
Co-authored-by: Jörg Thalheim <joerg@thalheim.io>
Co-authored-by: László Vaskó <1771332+vlaci@users.noreply.github.com>
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-07-28 23:00:44 +00:00
Raito Bezarius 2d0c1e29d2 tests/functional: support loading (contributed) plugins
This is useful to test extended features in Lix.

Change-Id: Idb2416a080329116677809b883950e6c33028a44
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-07-28 23:00:44 +00:00
eldritch horrors 173e6fe995 libstore: optimize nar copy stream adapter
we don't need to report progress for every read call. that's way too
much. batching like this greatly reduces CPU usage for copies out of
or into remote buidlers due to likewise greatly reduced log traffic.

Change-Id: I3db2b2ab113fbaadefc69cfde6f977fb0c6cd5ad
2025-07-28 23:57:16 +02:00
Jade Lovelace 2512d7a801 clang-tidy: fix some lints on macOS
I think this is partially stuff like
https://git.lix.systems/lix-project/lix/issues/806 but idk.

Change-Id: I4c38bb66fc9552269f33590eb3e99025942f382f
2025-07-27 23:34:59 -07:00
Jade Lovelace e29a4b64ef rl-next: fix incorrect "2.18 or older"
2.18 is still supported and will not become unsupported because we are
2.18 also.

Change-Id: If8af27e2924f67952e29b8f4db461b6784f5106e
2025-07-27 23:05:30 -07:00
Raito Bezarius 2d0109898a libstore/build: rewire builder's environment in presence of a global CA
Historically, Nix would support copying certificate authorities inside
the sandbox so you could use them.

In addition to that, the primitives consisting of leaking environment
variables via `impureEnvVars` and `extra-sandbox-paths` to render paths
external to the sandbox visible to the builder would also constitute a
mechanism to expose special inodes which should have no influence on the
output result, e.g. interception CAs.

Unfortunately, in nixpkgs, `lib.fetchers.proxyImpureEnvVars` set
`NIX_SSL_CERT_FILE` as an impure environment variable.

A confused user may set `ssl-cert-file` via `NIX_SSL_CERT_FILE` outside the
builder believing that this will set magically the right
`NIX_SSL_CERT_FILE` inside the sandbox, but this is not true.

The combination of impure environment variables and setting `caFile`
creates a weird interaction where `NIX_SSL_CERT_FILE` points to an
"outside the builder's world" inode *AND* `ssl-cert-file` creates
this very same certificate file in /etc/ssl/certs/ca-certificates.crt
without rewriting the environment variable.

This footgun is closed by making these two features mutually
incompatible with a warning and forcibly rewriting the SSL family of
environment variables even if it was set via impure environment
variables.

Users who truly meant to use `impureEnvVars` can obtain the right
behavior by setting `ssl-cert-file` to an empty string and will have to use
`extra-sandbox-paths`.

Users who meant to use `ssl-cert-file` will have everything work
automatically with a warning hinting at nixpkgs *fixing its own bug*,
i.e. passing `NIX_SSL_CERT_FILE` as an impure environment variable and
expecting the Nix interpreter to magically reconcile the diverging
values or expecting the user to actually do the work to render the path
visible available via `extra-sandbox-paths`.

Fixes #885.

Change-Id: I32f8b5ce20fe9b6a911768114c92f95fc886cc07
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-07-27 19:42:37 +00:00
Raito Bezarius e854c5364a libutil/mount: accept copy flags for bind path
Sometimes, `bindPath` will detect the source is a symlink and we are not
using the new mount API which support symlinks (kernel ≥ 5.12 IIRC?).

In those instances, we copy the inode to the target.

But some callers may want to follow the symlink in such circumstances,
we add a new default argument to the previous value and let caller
decide for themselves.

Change-Id: I8505b613fc614ce539eb89258fbbb7eaecebe23b
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-07-27 00:20:09 +00:00
Raito Bezarius ef94901156 libstore/build: pathContentsGood is physical path aware now
`pathContentsGood` is used to assess the validity of a path as part of
derivation goals *in repair mode*.

When repair is used with a diverted store, i.e. a store where
fsPath(toRealPath(s)) != fsPath(s) for s a store path, this result in
utterly broken behavior because it will attempt to assess the goodness
of the *logical* store locations, most of the time: /nix/store/...

So, if you are repairing your system using a live NixOS ISO. Your ISO
contains a `/nix/store` (assumed to be good) and you repair your system
which is rooted at /mnt and contains its own /nix/store, that is, a Nix
store at /mnt/nix/store.

Performing the following operation `nix-store --verify --repair --store
/mnt` will assess the contents goodness of the ISO's Nix store.

To avoid this, we assess the path existence of the *physical path*, aka
the result of `store.toRealPath` applied to a *logical* store path
string representation and we verify the hash of the *physical path*.

The error messages are not taken care of in this CL as those are purely
cosmetic and helps the user understand what is going on.

Fixes #892.

Change-Id: Ib9e0153cb5683edcf37f1963ebf065ceba5e5dfb
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-07-27 01:42:41 +02:00
Raito Bezarius 66860eec01 libstore/entry-points: failure to repair inform about the physical store path
Change-Id: I5e4cf19c591662b5823f0302fe074021a19a8ba3
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-07-26 20:52:25 +00:00
Raito Bezarius 015b6ca452 libstore/local-store: verifyStore inform about the physical store locations
Change-Id: I0af9c600f1a26c99eb787996e6b24847151dd2c0
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-07-26 20:52:25 +00:00
Raito Bezarius 527d0a001f libstore/local-store: invalidatePath inform about the physical store locations
Change-Id: I2d0d6ac4e42be9c7139602a29adf6e0450e1c9d0
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-07-26 20:52:25 +00:00
Raito Bezarius 5019eba747 libstore/local-store: verifyPath inform about the physical location store
Change-Id: I2e28acbff01d91484a9bf24a8ac8c37c53306af7
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-07-26 20:52:25 +00:00
Raito Bezarius bde5830352 libstore/store-api: isValidPath inform the user about physical store locations
Instead of showing logical Nix store paths, we show the actual physical
location so that the user can stat by copy-pasting these paths.

The bad thing about this change is that certain Nix porcelain only
accept their logical counterparts.

Change-Id: Id0eb45d1bf08a23508dfc2bb694c88155654f585
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-07-26 20:52:25 +00:00
Raito Bezarius b7a9da96be libstore/worker: pathContentsGood inform about physical store locations
When a user runs a repair or check sequence, they might be confused of
seeing references to a logical /nix/store path rather than the actual
physical store location.

Change-Id: I042031a6159cdd1054e7e200a220bf6c321d5fb8
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-07-26 20:52:25 +00:00
Jade Lovelace 95ea633547 docs: properly explain conversions in nix eval --json/--raw options.
jade: I rewrote this PR to be consistent between nix-instantiate and nix
eval. It turns out that nix eval *doesn't* copy to store with `--json`,
whereas nix-instantiate does. Wat.

Closes: https://github.com/lix-project/lix/pull/17
Co-authored-by: tyberiusprime <tyberiusprime@noreply.git.lix.systems>

Change-Id: Id22deec1cee0fed3bd5689567869b70bab26bae5
2025-07-26 20:05:54 +00:00
Alois Wohlschlager e02c9b5a01 packaging: stop copying unnecessary boost libraries
Originally, libboost_context and dependent dynamic libraries have been copied
into the nix package to remove boost from the closure and consequently reduce
the closure size. Since commit ef0de7c79f we do
not depend on Boost coroutines any more, so these libraries are not needed at
all any more and (somewhat ironically) only increase the package size. Remove
them.

Change-Id: I6a6a6964dc3e0b29dfad8b2b232b428ba3cc653c
2025-07-26 20:04:59 +00:00
Qyriad 275ca3fda0 build: simplify -Dnix-eval-jobs handling
Meson conveniently does lets you pass feature objects to `required :`
arguments[1], which is handy

[1]: https://mesonbuild.com/Build-options.html#features

Change-Id: I54194b235a9b3dc207f3f78e0a8c50f957e1fd1f
2025-07-26 19:38:35 +00:00
Alois Wohlschlager ae64570ee4 packaging: remove obsolete boost-finding environment variables
The issue requiring these environment variables to be set for Meson to find
Boost [1] has been fixed [2] for quite some time now. Drop them since they are
unnecessary in all supported nixpkgs releases (in fact they have already been
removed in the lix package there).

[1] https://github.com/NixOS/nixpkgs/issues/86131
[2] https://github.com/NixOS/nixpkgs/pull/315998

Change-Id: I6a6a69640a30e917cd7a983b9d846d023b393dcd
2025-07-26 17:08:14 +00:00
Raito Bezarius 5e4cbf39cf libstore/build: endure dangling symlinks of system files at sandbox setup
In certain scenarios, a path may exist but is a broken symlink. For
instance, this happens frequently if you are rescuing an existing NixOS
system via `nixos-enter` or a manual `chroot` invocation because the
rescued system `/etc` may have broken links or the system prior to the
root pivot may interfere.

Nonetheless, these dangling symlinks are not always fatal for the builds
so we can just warn and skip their setup.

The warnings will provide a better diagnostics for system administrators
also.

Fixes #893.

Change-Id: Ifa12be3a43f23c973d7b466e8b73bd776abf3e7b
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-07-26 14:54:59 +00:00
Raito BezariusandMaximilian Bosch 3c614a136b libstore/binary-cache: fix catching JSON exceptions
We were catching ForeignExceptions believing it came from the TRY_AWAIT
handler, but this was misguided.

`j.dump()` is evaluated in synchronous context, outside of the `try {
... } catch (...)` block from `TRY_AWAIT`.

Therefore, we need to use `JSON::Exception` directly.

The previous test case did not catch it because:

(1) https://git.lix.systems/lix-project/lix/issues/865 hid the fact that
`--arg` was wrong.
(2) we did not grep for the warning because… we were not even copying
the strange store path to the binary cache.
(3) checking for the NAR happened after the NAR directory was emptied
for test reasons and this was not even caught neither.

Anyway, the test case was completely busted and has now been tested
without this commit and after this commit and we can confirm that prior
to this commit, the test will fail with an exception trace.

Co-authored-by: Maximilian Bosch <maximilian@mbosch.me>
Change-Id: I8df5befd06c4a449072b987f82a67bc4437e7e49
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-07-25 21:04:16 +02:00
eldritch horrors 57b1c289b5 treewide: drop PushActivity in favor of explicit context
PushActivity does not work with async code since we have no such thing
as promise-local storage. it will be confusing at best, and completely
wrong at worst, with the current thread-local linking state. if we can
find a way to get promise-local storage we may want to bring this back
though, explicit context passing is rather error-prone. luckily we are
not using parent links for anything important, just to keep the multi-
line activity display from filling up with stuff we're already showing

Change-Id: Ie373d713080a3db811b2d5abd681f78137735e45
2025-07-25 13:05:24 +02:00
eldritch horrors e88a85bd92 build-remote: don't copy failed paths with ssh-ng & --keep-going
checking that the remote build actually succeeded only implied-trusted
remotes or CA derivations makes *absolutely* no sense. we should check
that builds have succeeded before trying to copy them from the remote.

Change-Id: Ib2cf216c580f4c577dd9fef8849acc033ae082b9
2025-07-24 17:56:05 +02:00
Raito Bezarius f6c0aea824 libstore/http: expose HttpBinaryCacheStore in the header
Including the `.cc` is possible but is suspicious.

Change-Id: Ie18fef1e30e517edff4ab96f4a9c339e2b3145b5
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-07-23 15:35:32 +00:00
sternenseemann cc73479558 Use lowdown 2.0.2 in CI
postInstall needs to know the name of the (versioned) .so file since it
needs to be renamed for Darwin platforms. Unfortunately, the .so version
is not properly overrideable, so we need to use string replacement.

Change-Id: Idf9671f84fac955a52d82a20ec0f381d05fdc762
2025-07-23 16:48:52 +02:00
sternenseemann 858de5f47a libcmd: add support for lowdown >= 1.4
lowdown 1.4.0 changed the lowdown_opts to include a new and separate
lowdown_opts_term which allows for configuring values specific to
-Tterm (which we're using). This version should have been called 2.0.0
according to semver, hence 2.0.0 was released later without any actual
breaking changes to sort of migitate the problem.

We need to support lowdown >= 1.3 && < 1.4 since the ship has sailed for
updating lowdown in NixOS 25.05 as well as lowdown >= 1.4 or we'll be
stuck in Nixpkgs forever. Support for < 1.4 can be dropped as soon as
NixOS 25.05 is EOL, assuming this change lands before NixOS 25.11
branch-off.

We detect the changed API based on the lowdown version from pkg-config
and define LOWDOWN_SEPARATE_TERM_OPTS based on that. The ifdef is named
according to the specific API change that impacts us, so that it's
hopefully a little simpler to maintain going forward. In the new API,
all newly configurable settings use what would have been the (implicit)
default before. Changing some of these values, especially hpadding,
could be interesting in future changes.

Compared to cl/3081, this change makes sure to initialize all new fields
of lowdown_opts_term explicitly.

It seems that, while making -Tterm more configurable, lowdown's word
wrapping behavior changed slightly which broke basic_repl.test. I've
chosen to work around this by using builtins.add as an example which has
a very short documentation string, so wrapping doesn't matter.

Change-Id: Id73be4c0e43d7eb4f56e10a261b4254402698ff8
2025-07-23 16:45:25 +02:00
eldritch horrors 43d6a79863 libstore: work around capnp fd passing bug
capnp does not handle fd passing correctly in all circumstances. we hit
such cirumstances when passing large closures path lists to build-hook.
since capnp seems to ignore fds passed in non-final segments of any rpc
message we just ensure that the capability including the log fd will be
small enough to not be fragmented on the receiving side of the channel.

cf https://github.com/capnproto/capnproto/issues/2359

Change-Id: Id22309264936b3a57bcc68a0753c3bfb3c9a43d2
2025-07-23 13:53:24 +00:00
2c6542bd9c libstore/http: provide makeOptions hook
Some users may have arbitrary needs to connect to their store URIs, e.g.
mTLS authentication, Kerberos authentication, custom renewal using any
RPC mechanism of their preference and so on.

To avoid encoding all these patterns in Lix itself, we push the
configuration to the plugin boundaries and offer a hook for end users to
inherit from `HttpBinaryCacheStore` and provide new store schemes like
`https+mtls://my.very.secure.cache?tls-certificate=...&tls-key=...` or
`https+krb5://my.kerberos.enabled.cache`.

Co-authored-by: George Shammas <george@shamm.as>
Co-authored-by: eldritch horrors <pennae@lix.systems>
Signed-off-by: Raito Bezarius <raito@lix.systems>
Change-Id: I79f322b1a74632500fc79d53f5c920f9e43fd0c4
2025-07-23 13:17:28 +00:00
Raito Bezarius f8ccd9d572 libutil/async-io: augment read type safety w.r.t to EOFs
Usually, EOFs are represented by returning 0 in the `read` APIs, at
least, this is what read(2) dictate.

As clever creature, we may sum zeroes sometimes (advanced form:
`buf->added(got)`) and forego handling the EOF condition.

To avoid the bug that lurked in remote-store.cc and caused busy looping
if the remote end disconnects suddenly, we return
`Result<Option<size_t>>` forcing the caller to perform a specific
processing for the EOF situation.

The conversion did not raise any other offending code path.

Change-Id: I185fdcb77aa82d87ab0802d66ac37c1363657a73
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-07-23 10:35:37 +00:00
Jade Lovelace 8a2d25054d libstore: rename confusing const identifiers
Also adds an assert that store path hash part length is what we expect
because it's alarmingly easy to forget to truncate a hash before
throwing it into there. It's kind of messy code, someone could improve
it more later.

Change-Id: I5296ea3d5b854323d092f0256defb598dd5b87e8
2025-07-23 02:51:45 +00:00
eldritch horrors 72cad8918b libstore: fix remote build failures killing everything
remote builds failures used to be signaled via exit status 1 of the
build hook, which in turn only happened because the build errors we
got from remote stores was thrown and bubbled up to main which then
logged the error and exited with code 1. with rpc we cannot do this
any more. barring a rewrite of the worker infra to allow for errors
being reported with something other than process exit codes this is
the best can do. ideally we would wrap remote builds in a new goal.
(and then remove all exit code shenanigans from DerivationGoal too)

fixes #928

Change-Id: Idc3ede3cbaca34c8c8e40247da52794f2a5013b9
2025-07-22 15:32:25 +02:00
eldritch horrors 9eb3c1be80 daemon: restore daemon-trust-override
apparently this feature got lost in the migration to exec'ing daemons.

Change-Id: Iac9425cf6d20781bb49e5cf12f2056f4a3ec23ba
2025-07-22 12:33:49 +00:00
eldritch horrors cb96940042 libstore: pass a log pipe into build-hook
this way we don't have to duplicate build log parsing in the hook.

Change-Id: I1c96b75aea3b4bb747aa0f0cc76c00eace8911c4
2025-07-22 12:33:49 +00:00
eldritch horrors 6ddd3045f0 testing: remove obsolete daemon tests
remove all daemon version checks targeting daemons we no longer support.

Change-Id: If722024c3d66c73fa1b3cdd63134a09389ac6ea5
2025-07-22 10:39:32 +00:00
eldritch horrors d8dfffbe37 daemon: use kj for splicing stdio connections
drop our reimplementation of splice for non-linux in favor of using kj
pumpTo. this avoids select() for its O(maxfd) behavior, and if kj ever
uses something more efficient than read/write loops we'll benefit too.

Change-Id: Id01ba84bf8831455af2d9755bf1a3039d215bb47
2025-07-22 10:39:32 +00:00
Jade Lovelace bf3d52e5bb rl-next: link to the fixed issue for removing old wires
Fixes: https://git.lix.systems/lix-project/lix/issues/510
Change-Id: Ia100f2f0bb48a4880b2c55d5622be9dab25b313b
2025-07-21 15:17:54 -07:00
eldritch horrors 2fc47b65b8 libstore: weaken tmpdir root access mode
libarchive *should* not break with 0710 on the tmpdir root on darwin,
just like it doesn't break on linux, but for some reason it does. the
restriction to 0710 can be weakened to 0750 with causing any trouble.

fixes #921

Change-Id: Ia9fc2f8eb9695fc19cefae9857368d5a4e58c8b9
2025-07-20 16:25:04 +00:00
K900 97a3a8cb67 readFile: don't explode on negative st_size
Should this ever happen? No. Does it? Evidently.

Change-Id: I62fa7530fbc2cdfd6112088dbc82a4a615cf6820
2025-07-20 11:06:05 +03:00
eldritch horrors 9d8ab80435 libstore: don't wait for empty cgroups to empty
oops. m(

Change-Id: Ia421589e76a9740b3e49133598c893339f92250e
2025-07-18 18:57:19 +02:00
eldritch horrors 6d6cccee75 libstore: restrict build-hook parallelism
previously we only had one build hook in waiting at most because build
hook rpc was synchronous. now that it no longer is we attempt to start
one hook per derivation, which depending on scheduling can be a *very*
large number. restrict the waiting hook count to 4 to some concurrency
without collecting a large number of hooks that may never do anything.

Change-Id: Ic0b1125cec4acd69e8a0d4639c232e71b825e01d
2025-07-18 13:43:02 +02:00
Jade LovelaceandJade Lovelace d906c7965b OWNERS: fix some wrong emails
Change-Id: Id5c5d50eb493a328eaecdf84f33f8bad18c33f5d
2025-07-17 12:36:54 -07:00
Jade LovelaceandJade Lovelace ab33a5b01f manual: reorg contributing docs, talk about gerrit
This section was kind of a mess so I've had a go at making it better.

Change-Id: Ia13b79db9cb0555660abc6ce795f6fe827ee15ad
2025-07-17 12:36:54 -07:00
eldritch horrors ae3b8e58c3 libstore: chown build dirs with --keep-failed
although we only chown if the build was requested by a local daemon
user. daemonless invocations will not chown as they do not have to.
remote builds *can* chown to the remote builder user, but that does
not seem to happen (for some reason keep-failed is not propagated).

Change-Id: Ic0ead406b38b4ca0556fec42d84888efa25123bf
2025-07-17 15:05:32 +02:00
eldritch horrors 9d5a5c4dc0 libstore: add intermediate directory to build-dirs
this makes the actual build directories used by builders invisible and
inaccessible to other processes on the system, avoiding another vector
for outside processes to interfere with builds or pass credentials the
build sandbox should not have access to into the build sandbox anyway.

fixes #919

Change-Id: Ifaa4d8e3940cfde1406e925f75c1375d2e86d81a
2025-07-16 23:02:16 +00:00
eldritch horrors 8f325fe436 libstore: convert build-hook protocol to rpc
Change-Id: I8da74acdc965aba5091c089101745f7aa501befa
2025-07-15 20:33:30 +02:00
eldritch horrors 8a5a477ca3 treewide: add first batch of capnp rpc types
this touches both libutil and libstore because with no rpc users it
doesn't make that much sense to separate the two. note that all our
strings are represented as Data (ie, blobs) because capnp Text must
be nul-terminated. while it's technically possible to use Text with
strings containing non-terminating NULs it is a bit of a hassle and
could lead to rpc users erroneously stopping at the first NUL byte.

Change-Id: I4c75e03b79a226ffa8d7cd985e3ac632a0cd7c1c
2025-07-15 20:33:30 +02:00
eldritch horrors ca12657a68 build: add capnp compiler wrapper
we need this to generate dependency information, and it'll be the entry
point for custom codegen once we need it. a wrapper also makes it a lot
easier to generate a whole namespace's worth of rpc definitions at once

Change-Id: Iba7a1c92a8a40bede9ed71aa3ab455477ff5e568
2025-07-15 06:40:48 +00:00
eldritch horrors ea11d075e6 libutil: add low-level provider to AsyncContext
without it we can't wrap socket fds for capnp rpc.

Change-Id: I0d603c82d8574b7b0f6eb07b2dff655d94418ec9
2025-07-15 06:40:48 +00:00
eldritch horrors 4a1d16ebca libstore: asyncify build-remote functions
Change-Id: I9fbee928eb955e03c41dfe8ce74bd8a90f5e26cd
2025-07-15 06:40:48 +00:00
eldritch horrors 6877ae5fb8 libstore: handle the entire hook lifecycle in tryBuildHook
if the hook accepts the build request we can handle the entire request
in tryBuildHook. there is no need to punt a partially handled build to
the caller (we only did this to minimize churn during asyncification).

Change-Id: Iec3e35a8103da4fc5fbef394cc28a134ee62a198
2025-07-15 06:40:48 +00:00
eldritch horrors 07ba511921 libutil: add a type-mapping TRY_AWAIT
mapping the result of an await operation before unpacking it lets us
inject rpc type conversion functions without duplicating all that is
needed for proper exception wrapping and async error traces support.

Change-Id: Ibcba1cc6d2b275757e3475881ef20f95dd4d684f
2025-07-15 06:40:48 +00:00
eldritch horrors e01ae1f453 libutil: add generic unix socketpair wrapper
previously we used this only for SSH, but other uses may appear soon.

Change-Id: Ibe9666d63aaea07525ebad57decda88b11964cc0
2025-07-15 06:40:48 +00:00
Jade LovelaceandJade Lovelace f4a11d0336 Draft of OWNERS
Goals:
- Distribute reviews to people who can do the reviews
- Not prevent anything from getting done
- Allow giving away more commit access

Anti-goals:
- Silo people into particular areas
- Discourage contributing to any area

This was drafted by glancing at git logs. It is not likely to be very
accurate; the goal here is that we figure out a way to distribute
reviews to the right people.

Change-Id: I8be44bf7fdeca23da8099124eec7bc3a30e34627
2025-07-14 18:20:47 -07:00
eldritch horrors b43d7b8136 libstore: don't use Outcome<void, T> in goals
`Outcome<void, T>` and `Result<std::optional<T>>` can be interpreted as
being the same thing, but the latter is easier to use: not only do they
allow TRY_AWAIT usage for their promises, we also don't have the error/
exception confusion of outcomes (where the T above is the "error" type)

Change-Id: I92c9241481cecc97e2992445b3dced53c82a2524
2025-07-14 17:02:30 +00:00
eldritch horrors 280772583f libutil: remote unsafeLockFileSingleThreaded
while this does require spawning a thread for every contended lock now
we don't expect performance to be impacted. only build-remote used the
synchronous method, and it only used it to serialize uploads to remote
builders. these uploads are expensive enough to dwarf the thread cost.

Change-Id: Iad0aa0cd738bc96fd06a90d655803dadffa09c47
2025-07-14 17:02:30 +00:00
eldritch horrors df45583c7a libutil: add AsyncContext::timeoutAfter
this is just a Result-aware version of kj's Timer::timeoutAfter.

Change-Id: I17ff77d40201996b9dafc32bbff0298db8a4fcc9
2025-07-14 17:02:30 +00:00
eldritch horrors 9c1e7d4d07 libstore: use AsyncInputStream for reading builder output
DerivationGoal::InputStream existed only because we did not have an
error-reporting AsyncInputStream of our own yet. we do have one now
though and can thus delete old code in favor of the generic variant

Change-Id: I01c7c564554f8794bdf54603b239b7a808faeda0
2025-07-14 17:02:30 +00:00
eldritch horrors 334b8e2b20 libstore: add log-fd to ssh-ng as well
this way we can get ssh error message if connection setup fails.

Change-Id: Ifc001f77ec0477fb9786f7767a47f3745d6475ff
2025-07-14 17:02:30 +00:00
eldritch horrors d20c3d3643 libutil: remove ErrorInfo::programName
it's effectively unused. one use is a write and a read immediately after
the write, the other use checks whether it's not equal to itself (..wat)

Change-Id: I5f6ce26e75a6bfa500c2e9ac3fc70e8dafc9bd74
2025-07-14 17:02:30 +00:00
Alois Wohlschlager 2090853b80 falsify the glibc store paths
Commits 205c59367c and
325e7e1824 introduced real glibc store paths from
current nixpkgs unstable into the source. Since nixpkgs `fetchFromGitea` (and
similar fixed-output derivations) depends on a C library, on x86_64-linux they
will fail with the forbidden reference error:

error: the fixed-output derivation '/nix/store/wnmnj3jzc82y89sfmyicr04kilg8zs2k-source.drv' must not reference store paths but 1 such references were found:
         /nix/store/q4wq65gl3r8fy746v9bbwgx4gzn0r2kl-glibc-2.40-66

Falsify the store path to prevent this failure.

Change-Id: I949033567bcad070f9a0a19cefdb33a79222e421
2025-07-13 10:56:47 +02:00
Jade Lovelace ae00b12983 fix: VERSION_SUFFIX was not getting into meson
It was a regression caused by switching to structured attrs, I think.

Fixes: https://git.lix.systems/lix-project/lix/issues/908
Change-Id: Ia62892919945a1f16a81a2e0bb585595fac46669
2025-07-11 20:02:00 -07:00
Jade Lovelace 9a8f111af2 darwin: fix repeated delivery of MonitorFdHup callbacks
My lix build failed today with this result:

```
lix> [----------] 3 tests from MonitorFdHup
lix> [ RUN      ] MonitorFdHup.works
lix> [       OK ] MonitorFdHup.works (0 ms)
lix> [ RUN      ] MonitorFdHup.works_with_pipes
lix> stderr:
lix> Using configuration: seed=6402097764877502971
lix> libc++abi: terminating due to uncaught exception of type std::__1::future_error: The state of the promise has already been set.
lix> 4/5 lix:check / libstore-unit-tests        OK              1.10s
lix> 5/5 lix:check / libexpr-unit-tests         OK              1.12s
lix> Summary of Failures:
lix> 3/5 lix:check / libutil-unit-tests  FAIL            0.93s   killed by signal 6 SIGABRT
lix> Ok:                4
lix> Fail:              1
lix> Full log written to /nix/var/nix/builds/nix-build-lix-2.94.0-dev-pre20250711-65ef28d.drv-0/source/build/meson-logs/testlog.txt
```

I had a response best described as "wtf". I think the cause of this
problem is that there's a race condition with the test in which the loop
gets gone around again a second time because it's triggered by the
terminate fd (and I guess the flags remained what they were before?
seems reasonable), and this is probably racing with the quit atomic
being first to break out of the loop.

I don't know how many hundreds of lixes I've compiled without my test
failing, but this is definitely a bug. I don't think this affects actual
usage as the only impact is repeat delivery of Ctrl-C which is harmless
and which users do regularly.

Change-Id: I60da81d4ac2e79052cd323b5171f9d8bd0aa6783
2025-07-11 19:41:18 -07:00
Matthew Bauerandjade 65ef28df6d completions: Show description in zsh completions
Uses the second part of NIX_GET_COMPLETIONS if available.

Change-Id: I35efaaea3f35eea25a8de723ab32ceecd054e7df
Fixes: https://git.lix.systems/lix-project/lix/issues/910
2025-07-11 02:54:08 +00:00
eldritch horrors 1a0d05d852 libstore, build-remote: delete static ssh:// fds
this was a mess. ssh:// remotes used the extra static fds for build
logs, ssh-ng:// remotes did not. ssh-ng remotes did not use them at
all since ssh-ng never redirected them to begin with. we now create
pipes dynamically and only for ssh:// builders, then translate logs
received over these pipes into the same format used by ssh-ng. this
requires a new activity we did not have before, but since we have a
great many activities that rarely show up already this shouldn't be
a problem for external tooling. if anything external tools can tell
what's going on much better now (at least for ssh:// remote builds)

Change-Id: I02010cee45598362a947faa3a5b04800d39daa31
2025-07-09 18:21:20 +00:00
eldritch horrors 11d854281a build-remote: extract connecting to builder
Change-Id: I364f20f1c689f87dd4e6ec98a36b43f34647ff43
2025-07-09 15:48:07 +00:00
eldritch horrors a17771569e build-remote: extract machine selection
Change-Id: Id5abe9a46ee8ba71f00379af31b442c3fa7cda92
2025-07-09 14:20:51 +02:00
eldritch horrors 44895d1ed5 build-remote: extract error message printer
Change-Id: I5eab56be27d6178caa341fb2ff1f52475252aab8
2025-07-09 14:20:51 +02:00
Philipp OtterbeinandRaito Bezarius fc3261c340 libexpr/primops: fix overflows and silent type casts
Avoids incorrect behavior with large integers in `elemAt`, `substring`,
`genList`, etc, which results into crashing the Lix interpreter.

At the same time, unit tests were added for these edge cases with 2^32
as an argument of these primops.

Port of https://github.com/NixOS/nix/pull/13309.
Prior art in https://github.com/NixOS/nix/pull/7222 (forgotten by the original project…).

Change-Id: I1c43ed64f26bcb60e51869e11a74e5de2b7db53a
Co-authored-by: Raito Bezarius <raito@lix.systems>
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-07-07 23:32:18 +00:00
Raito Bezarius b225794503 libcmd: drop warning about interpretation of store paths
Fixes #897.

Change-Id: Ie4da11d0427d8c9f48cd6e979d80bd39474e8390
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-07-07 21:13:16 +00:00
eldritch horrors 487b37291f libutil: optimize Sync<T, AsyncMutex>
async queries easily lead to high contention on the localstore sqlite
lock. optimizing the lock wakeup scheme improves query performance by
a linear factor (with the O(waiters) wakeup replaced by O(1) wakeup).
on 100k drv closures we're now at 55s query, down from >8min in 2.93.

Change-Id: I9b96e792c4518a782c690dea92e61260f08f0bad
2025-07-07 18:13:16 +00:00
Maximilian Bosch da0df360e1 libstore: print dependency in tree boldly
That way it's easier to spot whether a node is the "final" node in the
graph which is especially helpful for larger graphs.

Change-Id: I460a699f07f5455917792599f4247ebf8f430d93
2025-07-07 11:44:17 +00:00
Maximilian BoschandRobert Hensing 325e7e1824 libstore: show forbidden chain(s) to forbidden path from disallowedRequisites
Closes #334
Closes #626

This is loosely based on upstream PR#10877[1], but heavily changed to
use the graph logic from `nix why-depends`.

`precise` is `false` here since the out-path of the drv being built
isn't registered yet, so the path accessor cannot scan through files
yet.

Example output (from an openssh build with `pcsclite.lib` & `glibc` in
`disallowedRequisites`):

    error: output '/nix/store/hr8lmmjmd1jk6s3p5ymggyk4am7n2lmb-openssh-10.0p2' is not allowed to refer to the following paths:
           /nix/store/p6r5awz3ywrz66symnrn0xb85xzmcysf-pcsclite-2.3.0-lib
           /nix/store/q4wq65gl3r8fy746v9bbwgx4gzn0r2kl-glibc-2.40-66
           Shown below are chains that lead to the forbidden path(s).
           /nix/store/hr8lmmjmd1jk6s3p5ymggyk4am7n2lmb-openssh-10.0p2
           └───/nix/store/ys91ywnwikm14xznwk3cdbprapv2m37z-libfido2-1.16.0
               └───/nix/store/p6r5awz3ywrz66symnrn0xb85xzmcysf-pcsclite-2.3.0-lib
           /nix/store/hr8lmmjmd1jk6s3p5ymggyk4am7n2lmb-openssh-10.0p2
           ├───/nix/store/q4wq65gl3r8fy746v9bbwgx4gzn0r2kl-glibc-2.40-66
           ├───/nix/store/6r4zqb04fq5l5l4zghq76wvcpz7dwd35-linux-pam-1.6.1
           │   ├───/nix/store/q4wq65gl3r8fy746v9bbwgx4gzn0r2kl-glibc-2.40-66
           [...]

[1] https://github.com/NixOS/nix/pull/10877

Co-authored-by: Robert Hensing <robert@roberthensing.nl>
Change-Id: Ib30024c0d9e45c1160bf0134f7d3ba17dbdeff47
2025-07-07 11:44:17 +00:00
Maximilian Bosch 114bc770e3 doc: remove ignoreSelfRefs from docs
While working on the LocalDerivationGoal code, I realized that this
attribute is only set to `false`/`true` depending on whether
`__structuredAttrs` is `true`/`false`.

Change-Id: I53868cd32cedd7e25cb6233bd93bc01111b56a07
2025-07-07 11:44:17 +00:00
Maximilian Bosch 205c59367c libstore: add genGraphString from why-depends
This will be useful for other things as well such as the
disallowedRequisites error in the builder code. Additionally, print the
dependencyPath in the tree bold to spot where a change terminates.

Also implemented some unit-tests for this code.

Change-Id: I8460f3f6c5095d5bfbe390f223bc0252800dca5e
2025-07-07 11:44:17 +00:00
Maximilian Bosch 5ab5f7d96c why-depends: add entrypoint for graph builder API
The now-introduced genGraphString will be the public API of the libstore
helper.

Change-Id: Id47d21230d1a30b880f298f307c30ddffdb0e9c7
2025-07-07 11:44:17 +00:00
Maximilian Bosch 49263f154a why-depends: initialize internal graph data from std::map<StorePath, StorePathSet>
The Node struct should become an implementation detail when moving this
into libstore. A map from a node to its direct references is more
intuitive here.

Change-Id: I9fddce6b398b8bb97834e5586bee72b244885fdd
2025-07-07 11:44:17 +00:00
Maximilian Bosch 353a87727b why-depends: refactor Node struct
* Better name for refs
* Use std::optional<T> for distance

Suggested-by: eldritch horrors <pennae@lix.systems>
Change-Id: Ie35c3f2a7ea1a90ce3a9807025d0af9ea73e2403
2025-07-07 11:44:17 +00:00
Maximilian Bosch 57e071e815 why-depends: asyncify printNode
Instead of logging directly, we now write into a `Strings` set that is
referenced by the caller.

While at it, added a test-case to ensure that self-reference invocations
and --all behave properly.

Change-Id: Ib183ab8e8e90436300e1c870fb3ae8f18730abbf
2025-07-07 11:44:17 +00:00
Maximilian Bosch 1d6c8bff77 why-depends: extract printNode
Change-Id: Ia0ba08745b9498fdbcc4231f91bf3d45afbe8373
2025-07-07 11:44:17 +00:00
Maximilian Bosch c2693327d9 why-depends: asyncify visitPath
Change-Id: If42e2a5ed6069817e66aeff3e2de1f69e5e526b1
2025-07-07 11:44:17 +00:00
Maximilian Bosch 7b84545ce1 why-depends: extract visitPath function
First step towards moving this code into libstore to re-use it
elsewhere.

Change-Id: I1daccd984f759b235c4c187d5c7783cb5a35f0b9
2025-07-07 11:44:17 +00:00
K900 4bc0bdc20b nix-eval-jobs: run tests with -v
That way we get a line of output per test completed,
which makes it more obvious it's actually doing things.

Change-Id: Ifbbe8bdf64e7178d3c59349cf071eb5a9d0fcd32
2025-07-06 21:31:48 +03:00
K900andeldritch horrors 378b360bf8 packaging: use structuredAttrs
staging-next banned !structuredAttrs && separateDebugInfo && disallowedRequisites
due to weird output interactions. Enable structuredAttrs so we can build again.

Also, fix type confusion that makes stdenv explode (https://github.com/NixOS/nixpkgs/issues/422989).

Co-authored-by: eldritch horrors <pennae@lix.systems>
Change-Id: Ic0c773394ee79e10d427f27750d59892d6d1f1d1
2025-07-06 21:11:41 +03:00
eldritch horrors 33784540c4 libutil: fix signaling handling on darwin
this partially reverts commit 0cc021ee15,
which for some reason is completely broken on darwin: there seems to be
no way to receive process-directed signals on a non-main thread. trying
to do it anyway will fail silently. since we only ever used kj for this
to get signal handling timeouts on darwin (which lacks sigtimedwait) to
print a nice message about retrying ^C again we can work around this by
moving the message printing into a fresh, unrelated, non-signal thread.

Change-Id: I5939c6ec62a7e1dc1b3f16067f77277533949fa0
2025-07-06 17:47:42 +02:00
Raito Bezarius df58bffc0f doc/rl-next: missed credit for the symbol reuse work
We missed xokdvium being author on cl/3300. This is something we
absolutely want to avoid.

We credit xokdvium in the RL and add a note on this problem.

Thanks to xokdvium for reaching out in private to us so we can repair
this mistake.

Change-Id: I094d0f95b6647104621d6b228e69a4529a300304
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-07-06 11:03:07 +00:00
eldritch horrors 29d9a88af6 libutil: handle sigint only once per thread
many a cleanup path has been broken by interruptions being thrown every
time checkInterrupt is called. we should only throw *once* though; more
than one Interrupted exception for the same event is not only confusing
but also breaks all cleanup paths at the first checkInterrupt call site
(e.g. #900, the cgroup cleanup saga, temp dirs not being removed, etc).

Change-Id: Ibfabf7f6af6ac2b78ad93582c254bbc48fcb3073
2025-07-06 10:45:55 +00:00
eldritch horrors b1544582af libutil: quit immediately on second sigint
we must be crash-safe *anyway*, and being unable to interrupt lix if it
gets stuck somewhere that never calls checkInterrupt is really annoying

Change-Id: I7c40271c3da7e69d8735e22b7b7c4751b5306ab6
2025-07-05 16:27:47 +02:00
eldritch horrors 0cc021ee15 libutil: asyncify signal handler thread
macos doesn't have sigtimedwait and we need signal wait timeouts in
order to print a "please hit ^C again" message with a bit of delay.

Change-Id: If574fb1a9de0b19975b34fc63662b089eaedc9d2
2025-07-04 22:59:13 +02:00
eldritch horrors 9dbf46f573 cli: asyncify daemonLoop
another checkInterrupt can be a makeInterruptible wrapper now. this is
also necessary to add a second daemon socket for the new rpc protocol.

Change-Id: I55055f975335a75708f1f73edb75f7bfe77a5938
2025-07-04 21:12:12 +02:00
812 changed files with 20515 additions and 10745 deletions
+2
View File
@@ -20,6 +20,8 @@ Checks:
- -bugprone-multi-level-implicit-pointer-conversion
# we don't compile out our asserts
- -bugprone-assert-side-effect
# FIXME(jade): figure out if this warning is any good
- -bugprone-exception-escape
# all thrown exceptions must derive from std::exception
- hicpp-exception-baseclass
# capturing async lambdas are dangerous
Generated
+3 -3
View File
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "countme"
@@ -47,9 +47,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "rnix"
version = "0.11.0"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb35cedbeb70e0ccabef2a31bcff0aebd114f19566086300b8f42c725fc2cb5f"
checksum = "6f15e00b0ab43abd70d50b6f8cd021290028f9b7fdd7cdfa6c35997173bc1ba9"
dependencies = [
"rowan",
]
+26
View File
@@ -0,0 +1,26 @@
# Docs
per-file README.md=*
per-file CONTRIBUTING.md=*
# DevX
per-file justfile=*
per-file .envrc=*
per-file .gitignore=*
per-file .github=*
per-file .mailmap=*
# Build
per-file meson.build=*
per-file meson.options=*
per-file flake.nix=*
per-file flake.lock=*
per-file *.nix=*
per-file Cargo.lock=*
per-file Cargo.toml=*
per-file version.json=*
# Code style
per-file .clang-tidy=*
per-file .clang-format=*
per-file .editorconfig=*
per-file treefmt.toml=*
+1
View File
@@ -0,0 +1 @@
*
+157 -36
View File
@@ -7,15 +7,44 @@ import os
import json
import tempfile
import platform
import shlex
import textwrap
import dataclasses
flake_args = ["--extra-experimental-features","'nix-command flakes'"]
# hyperfine has its own variable substitution, so we use that and pass build="{BUILD}" here.
# perf doesn't have variable substitution, so we call these with build being the actual build directory.
flake_args = ["--extra-experimental-features", "nix-command flakes"]
cases = {
"search": lambda build: [f"{build}/bin/nix", *flake_args, "search", "--no-eval-cache", "github:nixos/nixpkgs/e1fa12d4f6c6fe19ccb59cac54b5b3f25e160870", "hello"],
"rebuild": lambda build: [f"{build}/bin/nix", *flake_args, "eval", "--raw", "--impure", "--expr", "'with import <nixpkgs/nixos> {}; system'"],
"rebuild_lh": lambda build: ["GC_INITIAL_HEAP_SIZE=10g", f"{build}/bin/nix", *flake_args, "eval", "--raw", "--impure", "--expr", "'with import <nixpkgs/nixos> {}; system'"],
"parse": lambda build: [f"{build}/bin/nix", *flake_args, "eval", "-f", "bench/nixpkgs/pkgs/development/haskell-modules/hackage-packages.nix"],
"search": lambda build: [
f"{build}/bin/nix",
*flake_args,
"search",
"--no-eval-cache",
"github:nixos/nixpkgs/e1fa12d4f6c6fe19ccb59cac54b5b3f25e160870",
"hello",
],
"rebuild": lambda build: [
f"{build}/bin/nix",
*flake_args,
"eval",
"--raw",
"--impure",
"--expr",
textwrap.dedent("""
(import <nixpkgs/nixos> {
configuration = ./bench/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-plasma6.nix;
}).config.system.build.toplevel
""").replace("\n", " "),
],
"rebuild_lh": lambda build: [
"GC_INITIAL_HEAP_SIZE=10g",
*cases['rebuild'](build),
],
"parse": lambda build: [
f"{build}/bin/nix",
*flake_args,
"eval",
"-f",
"bench/nixpkgs/pkgs/development/haskell-modules/hackage-packages.nix",
],
}
arg_parser = argparse.ArgumentParser()
@@ -24,46 +53,81 @@ arg_parser = argparse.ArgumentParser()
# mode, we would have to combine the JSON ourselves to support that, which
# would probably be better done by writing a benchmarking script in
# not-bash.
arg_parser.add_argument('builds', nargs='+', help="At least two build directories to compare, containing bin/nix")
arg_parser.add_argument('--cases', type=str, help="A comma-separated list of cases you want to run. Defaults to running all")
available_modes = [ "walltime" ] + [ "icount" ] if platform.system() == 'Linux' else [] # perf doesn't run on Darwin
arg_parser.add_argument('--mode', choices=available_modes, default="walltime")
arg_parser.add_argument(
'builds',
nargs='+',
help="At least two build directories to compare, containing bin/nix",
)
arg_parser.add_argument(
'--cases',
type=str,
help="A comma-separated list of cases you want to run. Defaults to running all",
)
arg_parser.add_argument(
'--mode',
nargs='+',
choices=[ "walltime", "memory" ] + [ "icount" ] if platform.system() == 'Linux' else [], # perf doesn't run on Darwin
default=[ "walltime" ],
)
arg_parser.add_argument(
'--daemon',
action='store_true',
help='Run a temporary daemon for the benchmark instead of using a local store directly',
)
args = arg_parser.parse_args()
if len(args.builds) < 2:
raise ValueError("need at least two build directories to compare")
if len(args.builds) < 1:
raise ValueError("need at least one build directory to benchmark")
benchmarks: list[str] = []
if args.cases is None:
benchmarks = list(cases.keys())
else:
for case in args.cases.split(","):
if case not in cases: raise ValueError(f"no such case: {case}")
if case not in cases:
raise ValueError(f"no such case: {case}")
benchmarks.append(case)
def make_full_command(build, case):
cmd = " ".join(map(shlex.quote, cases[case](build)))
if args.daemon:
return " ".join([
f"{build}/bin/nix --extra-experimental-features nix-command daemon &",
"trap 'kill %1' EXIT;",
f"NIX_REMOTE=daemon {cmd}",
])
else:
return cmd
def bench_walltime(env):
hyperfine_args = ["--parameter-list", "BUILD", ','.join(args.builds), "--warmup", "2", "--runs", "10"]
for case in benchmarks:
case_command = cases[case]("{BUILD}") # see the comment on cases
subprocess.run([
"taskset", "-c", "2,3",
"chrt", "-f","50",
"hyperfine", *hyperfine_args, "--export-json", f"bench/bench-{case}.json", "--export-markdown", f"bench/bench-{case}.md", "--", " ".join(case_command)
], env=env, check=True)
for build in args.builds:
subprocess.run([
"taskset", "-c", "2,3",
"chrt", "-f","50",
*[
"hyperfine", "--warmup", "2", "--runs", "10",
"--export-json", f"bench/bench-{case}-{build}.json",
"--export-markdown", f"bench/bench-{case}-{build}.md",
"--", make_full_command(build, case),
],
], env=env, check=True)
print("Benchmarks summary\n---\n")
for case in benchmarks:
fd = open(f"bench/bench-{case}.json")
result_json = json.load(fd)
fd.close()
for result in result_json["results"]:
results = []
for build in args.builds:
with open(f"bench/bench-{case}-{build}.json") as fd:
results.append(json.load(fd)["results"][0])
for result in results:
print(result["command"])
print("-" * min(80,len(result["command"])))
attr_rounded = lambda attr: f"{result[attr]:.3f}"
def attr_rounded(attr):
return f"{result[attr]:.3f}"
print(" mean: ", attr_rounded("mean"), "±", attr_rounded("stddev"))
print(" user:", attr_rounded("user"), "| system", attr_rounded("system"))
print(" median: ", attr_rounded("median"))
print(" range: ", attr_rounded("min") + "s.." + attr_rounded("max")+"s")
print(" relative:", f"{result["mean"]/result_json["results"][0]["mean"]:.3f}")
print(" relative:", f"{result["mean"]/results[0]["mean"]:.3f}")
print("\n")
@@ -71,12 +135,14 @@ def bench_icount(env):
perf_results_for: dict[str, list[tuple[str, float]]] = {}
for case in benchmarks:
for build in args.builds:
case_command = cases[case](build)
# the perf stat -j output (incorrectly) localizes numbers, which will trip up the json parser.
env["LC_ALL"]="C"
case_command = make_full_command(build, case)
commandline = [
"perf", "stat", "-o", f"bench/perf-{case}.json", "-j", "sh", "-c", " ".join(case_command)
"perf", "stat", "-o", f"bench/perf-{case}.json", "-j",
"sh", "-c", case_command,
]
print("running", case_command)
subprocess.run(commandline, env=env, check=True, stdout=subprocess.DEVNULL) # warmup run
subprocess.run(commandline, env=env, check=True, stdout=subprocess.DEVNULL)
perf_fd = open(f"bench/perf-{case}.json")
@@ -84,8 +150,9 @@ def bench_icount(env):
perf_fd.close()
instr = next(x for x in perf_data if x["event"] in ["instructions", "instructions:u"]) # an implementation of a find_first iterator
if case not in perf_results_for: perf_results_for[case] = []
perf_results_for[case].append((" ".join(case_command), float(instr["counter-value"])))
if case not in perf_results_for:
perf_results_for[case] = []
perf_results_for[case].append((case_command, float(instr["counter-value"])))
print("Benchmarks summary\n---\n")
for (case, entries) in perf_results_for.items():
@@ -97,6 +164,54 @@ def bench_icount(env):
print(" relative instructions:", int(instr)/perf_results_for[case][0][1])
print("\n")
@dataclasses.dataclass
class MemoryStatistics:
envBytes: int
listBytes: int
setBytes: int
valueBytes: int
heapBytes: int
heapSize: int
def bench_memory(env):
path = "bench/bench-memory.json"
env = env | {
'NIX_SHOW_STATS': '1',
'NIX_SHOW_STATS_PATH': path,
}
results: dict[str, list[tuple[str, MemoryStatistics]]] = {}
for case in benchmarks:
for build in args.builds:
case_command = make_full_command(build, case)
commandline = [ "sh", "-c", case_command ]
print("running", case_command)
subprocess.run(commandline, env=env, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
with open(path) as fd:
stats = json.load(fd)
results.setdefault(case, []).append((case_command, MemoryStatistics(
envBytes=stats['envs']['bytes'],
listBytes=stats['list']['bytes'],
setBytes=stats['sets']['bytes'],
valueBytes=stats['values']['bytes'],
heapSize=stats['gc']['heapSize'],
heapBytes=stats['gc']['totalBytes'],
)))
print("Benchmarks summary\n---\n")
for (case, entries) in results.items():
for cmd, stats in entries:
print(cmd)
print("-" * min(80, len(cmd)))
print(f" env bytes: {stats.envBytes :15d} | {(stats.envBytes / entries[0][1].envBytes) :.3f}x")
print(f" list bytes: {stats.listBytes :15d} | {(stats.listBytes / entries[0][1].listBytes) :.3f}x")
print(f" set bytes: {stats.setBytes :15d} | {(stats.setBytes / entries[0][1].setBytes) :.3f}x")
if not entries[0][1].valueBytes:
print(f" value bytes: {0:15d}")
else:
print(f" value bytes: {stats.valueBytes:15d} | {(stats.valueBytes / entries[0][1].valueBytes):.3f}x")
print(f" heap alloc'd: {stats.heapBytes :15d} | {(stats.heapBytes / entries[0][1].heapBytes) :.3f}x")
print(f" heap size: {stats.heapSize :15d} | {(stats.heapSize / entries[0][1].heapSize) :.3f}x")
print("\n")
with tempfile.TemporaryDirectory() as tmp_dir:
subprocess.run([
@@ -108,9 +223,15 @@ with tempfile.TemporaryDirectory() as tmp_dir:
subenv = os.environ.copy()
subenv["NIX_CONF_DIR"] = "/var/empty"
subenv["NIX_REMOTE"] = tmp_dir
subenv["NIX_PATH"] = "nixpkgs=bench/nixpkgs:nixos-config=bench/configuration.nix"
subenv["NIX_PATH"] = ":".join([
"nixpkgs=bench/nixpkgs",
])
subenv["NIX_DAEMON_SOCKET_PATH"] = f"{tmp_dir}/daemon"
if args.mode == "walltime":
bench_walltime(subenv)
else:
bench_icount(subenv)
for mode in args.mode:
if mode == "walltime":
bench_walltime(subenv)
elif mode == "memory":
bench_memory(subenv)
else:
bench_icount(subenv)
-314
View File
@@ -1,314 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
boot = {
initrd = {
availableKernelModules = [
"xhci_pci"
"ahci"
];
kernelModules = [ "dm-snapshot" ];
luks.devices = {
croot = {
device = "/dev/sdb";
allowDiscards = true;
};
};
};
kernelModules = [ "kvm-intel" ];
kernelPackages = pkgs.linuxPackages_latest;
loader = {
systemd-boot.enable = true;
efi.canTouchEfiVariables = true;
};
};
hardware = {
enableRedistributableFirmware = true;
cpu.intel.updateMicrocode = true;
graphics.enable32Bit = true;
graphics.extraPackages = with pkgs; [
vaapiIntel
intel-media-driver
intel-compute-runtime
];
};
fileSystems = {
"/" = {
device = "/dev/sda2";
fsType = "xfs";
options = [ "noatime" ];
};
"/boot" = {
device = "/dev/sda1";
fsType = "vfat";
};
"/nas" = {
device = "nas:/";
fsType = "nfs4";
options = [
"ro"
"x-systemd.automount"
];
};
};
swapDevices = [ { device = "/dev/swap"; } ];
networking = {
useDHCP = false;
hostName = "host";
wireless = {
enable = true;
interfaces = [ "eth1" ];
};
interfaces = {
eth0.useDHCP = true;
eth1.useDHCP = true;
};
wg-quick.interfaces = {
wg0 = {
address = [ "2001:db8::1" ];
privateKeyFile = "/etc/secrets/wg0.key";
peers = [
{
publicKey = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
endpoint = "[2001:db8::2]:61021";
allowedIPs = [ "2001::db8:1::/64" ];
}
];
};
};
firewall.allowedUDPPorts = [ 4567 ];
};
i18n = {
defaultLocale = "en_US.UTF-8";
inputMethod.enable = true;
inputMethod.type = "ibus";
};
services = {
libinput.enable = true;
xserver = {
enable = true;
xkb.layout = "us";
xkb.variant = "altgr-intl";
xkb.options = "ctrl:nocaps";
wacom.enable = true;
videoDrivers = [ "modesetting" ];
modules = [ pkgs.xf86_input_wacom ];
displayManager.sx.enable = true;
windowManager.i3.enable = true;
};
udev.extraHwdb = ''
# not like this mattered at all
# we're not running udev from here
'';
udev.extraRules = ''
# ACTION=="add", SUBSYSTEM=="input", ...
'';
};
programs = {
light.enable = true;
wireshark = {
enable = true;
package = pkgs.wireshark-qt;
};
gnupg.agent = {
enable = true;
};
};
fonts.packages = with pkgs; [
font-awesome
noto-fonts
noto-fonts-cjk-sans
noto-fonts-emoji
noto-fonts-extra
dejavu_fonts
powerline-fonts
source-code-pro
cantarell-fonts
];
users = {
mutableUsers = false;
users = {
user = {
isNormalUser = true;
group = "user";
extraGroups = [
"wheel"
"video"
"audio"
"dialout"
"users"
"kvm"
"wireshark"
];
password = "unimportant";
};
};
groups = {
user = { };
};
};
security = {
pam.loginLimits = [
{
domain = "@audio";
item = "memlock";
type = "-";
value = "unlimited";
}
{
domain = "@audio";
item = "rtprio";
type = "-";
value = "99";
}
{
domain = "@audio";
item = "nofile";
type = "soft";
value = "99999";
}
{
domain = "@audio";
item = "nofile";
type = "hard";
value = "99999";
}
];
sudo.extraRules = [
{
users = [ "user" ];
commands = [
{
command = "${pkgs.linuxPackages.cpupower}/bin/cpupower";
options = [ "NOPASSWD" ];
}
];
}
];
};
environment.systemPackages = with pkgs; [
a2jmidid
age
ardour
bemenu
blender
breeze-icons
breeze-qt5
bubblewrap
calf
claws-mail
darktable
duperemove
emacs
feh
file
firefox
fluidsynth
adwaita-icon-theme
gnuplot
graphviz
helm
i3status-rust
inkscape
jack2
jq
krita
ldns
libqalculate
libreoffice
man-pages
nix-diff
nix-index
nix-output-monitor
open-music-kontrollers.patchmatrix
pamixer
pavucontrol
pciutils
picom
pwgen
redshift
ripgrep
rlwrap
silver-searcher
soundfont-fluid
whois
wol
xclip
xdot
xdotool
xorg.xkbcomp
yt-dlp
zathura
borgbackup
linuxPackages.cpupower
mtr
kitty
xf86_input_wacom
];
environment.pathsToLink = [ "/share/soundfonts" ];
systemd.user.services.run-python = {
after = [ "network-online.target" ];
script = ''
exec ${pkgs.python3}/bin/python
'';
serviceConfig = {
CapabilityBoundingSet = [ "" ];
KeyringMode = "private";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
PrivateUsers = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RestrictAddressFamilies = "AF_INET AF_INET6";
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~ @resources @privileged"
];
UMask = "077";
};
};
system.stateVersion = "23.11";
}
+1
View File
@@ -0,0 +1 @@
*
+1
View File
@@ -0,0 +1 @@
*
+21
View File
@@ -77,10 +77,20 @@ edolstra:
display_name: Eelco Dolstra
github: edolstra
emilazy:
display_name: Emily
forgejo: emilazy
github: emilazy
ericson:
display_name: John Ericson
github: ericson2314
getchoo:
display_name: Seth Flynn
forgejo: getchoo
github: getchoo
gilice:
forgejo: gilice
@@ -115,6 +125,11 @@ jade:
just1602:
forgejo: just1602
kasimeka:
display_name: ورد
forgejo: janw4ld
github: kasimeka
kfears:
display_name: KFears
forgejo: kfearsoff
@@ -153,6 +168,9 @@ ma27:
matthewbauer:
github: matthewbauer
mic92:
github: mic92
midnightveil:
display_name: julia
forgejo: midnightveil
@@ -252,6 +270,9 @@ winter:
xanderio:
github: xanderio
xokdvium:
github: xokdvium
yorickvp:
github: yorickvp
+1 -1
View File
@@ -225,7 +225,7 @@ let
showCategory = cat: ''
${optionalString (cat != "") "**${cat}:**"}
${listOptions (filterAttrs (n: v: v.category == cat) allOptions)}
${listOptions (filterAttrs (n: v: v.category == cat && !v.hidden) allOptions)}
'';
listOptions = opts: concatStringsSep "\n" (attrValues (mapAttrs showOption opts));
showOption =
-16
View File
@@ -1,16 +0,0 @@
---
synopsis: First argument to `--arg`/`--argstr` must be a valid Nix identifier
issues: [fj#496]
category: "Breaking Changes"
credits: [ma27]
---
The first argument to `--arg`/`--argstr` must be a valid Nix identifier, i.e.
`nix-build --arg config.allowUnfree true` is now rejected.
This is because that invocation is a false friend since it doesn't set
`{ config = { allowUnfree = true; }; }`, but `{ "config.allowUnfree" = true; }`.
The idea is to change the behavior to the latter in the long-term. For that,
non-identifiers started giving a warning since 2.92 and are now rejected to give people
who depend on that a chance to notice and potentially weigh in on the discussion.
-20
View File
@@ -1,20 +0,0 @@
---
synopsis: "Improved susbtituter query speed"
issues: []
cls: []
category: Improvements
credits: [horrors]
---
The code used to query substituters for derivations has been rewritten slightly
to take advantage of our asynchronous runtime. Such queries run for every build
that could download from substituters and processes every derivation that isn't
yet present on the local system. Previously Lix would use `http-connections` to
limit query concurrency, even for modern caches that support HTTP/2 and have no
limit on how many queries can be run concurrently on one single connection. Lix
no longer does this, resulting in approximately 60% reduction in query time for
medium-sized closures (e.g. NixOS system closures) during testing, although the
exact number depends greatly on local network latency and generally improves as
latency increases. Unlike previously setting `http-connections` to `1` or other
low values no longer brings a massive penalty in query performance if the cache
in use by the querying system supports HTTP/2 (as e.g. `cache.nixos.org` does).
-12
View File
@@ -1,12 +0,0 @@
---
synopsis: "`build-dir` no longer defaults to `temp-dir`"
cls: [3453]
category: "Fixes"
credits: [horrors]
---
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).
-69
View File
@@ -1,69 +0,0 @@
---
synopsis: New cgroup delegation model
issues: [fj#537, fj#77]
cls: [3230]
category: "Breaking Changes"
credits: [raito, horrors, lheckemann]
---
Builds using cgroups (i.e. `use-cgroups = true` and the experimental feature
`cgroups`) now always delegate a cgroup tree to the sandbox.
Compared to the original C++ Nix project, our delegation includes the
`subtree_control` file as well, which means that the sandbox can disable
certain controllers in its own cgroup tree.
This is a breaking change because this requires the Nix daemon to run with an
already delegated cgroup tree by the service manager.
## How to setup the cgroup tree with systemd?
systemd offers knobs to perform the required setup using:
```
[Service]
Delegate=yes
DelegateSubtree=supervisor
```
These directives are now included in our systemd packaging.
## What about using Nix as root without connecting to the daemon?
Builds run as `root` without connecting to the daemon relying on the cgroup
feature are now broken, i.e.
```console
# nix-build --use-cgroups --sandbox ... # will not work
```
Consider doing instead:
```console
# systemd-run --same-dir --wait -p Delegate=yes -p DelegateSubgroup=supervisor nix-build --use-cgroups ...
```
If you need to disable cgroups temporarily, remember that you can do
`NIX_CONF='include /etc/nix/nix.conf\nuse-cgroups = false' nix-build ...` or
`nix-build --no-use-cgroups ...`.
## What about other service managers than systemd?
systemd has a [documentation](https://systemd.io/CGROUP_DELEGATION/) on how to
handle cgroup delegation from service management perspective.
If your service manager adheres to systemd semantics, e.g. writing an extended
attribute `user.delegate=1` on the delegated cgroup tree directory and moving
the `nix-daemon` process inside a cgroup tree to respect the inner process
rule, then, the feature will work as well.
## Why is the cgroup feature still experimental?
While the cgroup feature unlocks many use cases, its behavior and integration (e.g. user experience), especially at scale on build farms or in multi-tenant environments, are not yet fully matured. Theres also potential for deeper systemd integration (e.g. using slices and scopes) that has not been fully explored.
To avoid locking in an unstable interface, were keeping the experimental flag until we have validated the feature across a broader range of scenarios, including but not limited to:
* Nix as root
* Hydra-style build farms
* Forgejo CI runners
* Shared remote builders
@@ -1,16 +0,0 @@
---
synopsis: Deprecation of CA derivations, dynamic derivations, and impure derivations
issues: [fj#815]
cls: []
significance: significant
category: Miscellany
credits: []
---
Content-addressed derivations are now deprecated and slated for removal in Lix 2.94.
We're doing this because the CA derivation system has been a known cause of problems
and inconsistencies, is unmaintained, habitually makes improving the store code very
difficult (or blocks such improvements outright), and is beset by a number of design
flaws that in our opinion cannot be fixed without a full reimplementation from zero.
Dynamic derivations and impure derivations are built on the CA derivation framework,
and owing to this they too are deprecated and slated for removal in another release.
-13
View File
@@ -1,13 +0,0 @@
---
synopsis: "nix-store --delete: always remove obsolete hardlinks"
issues: []
cls: [3188]
category: Fixes
credits: [lheckemann]
---
Deleting specific paths using `nix-store --delete` or `nix store
delete` previously did not delete hard links created by `nix-store
--optimise` even if they became obsolete, unless _all_ of the given
paths were deleted successfully. Now, hard links are always cleaned
up, even if some of the given paths could not be deleted.
-24
View File
@@ -1,24 +0,0 @@
---
synopsis: "Report GC statistics correctly"
issues: []
cls: [3188]
category: Fixes
credits: [lheckemann]
---
Deleting specific paths using `nix-store --delete` or `nix store delete` previously did
not report statistics correctly when some of the paths could not be deleted, even if
others were deleted:
```
$ nix store delete /nix/store/9bwryidal9q3g91cjm6xschfn4ikd82q-hello-2.12.1 --delete-closure -v
finding garbage collector roots...
deleting '/nix/store/9bwryidal9q3g91cjm6xschfn4ikd82q-hello-2.12.1'
0 store paths deleted, 0.00 MiB freed
error: Cannot delete some of the given paths because they are still alive. Paths not deleted:
k9bxzr1l92r5y6mihrkbpbr3fmc8qszx-libidn2-2.3.8
mbx9ii53lzjlrsnlrfmzpwm33ynljwdn-libunistring-1.3
rf8hcy6bldxdqc0g6q1dcka1vh47x69s-xgcc-14.2.1.20250322-libgcc
vbrdc5wgzn0w1zdp10xd2favkjn5fk7y-glibc-2.40-66
To find out why, use nix-store --query --roots and nix-store --query --referrers.
```
-24
View File
@@ -1,24 +0,0 @@
---
synopsis: Repl debugger uses `--ignore-try` by default
issues: [lix#666]
cls: [3488]
category: Breaking Changes
credits: [jade]
---
Previously, using the debugger meant that exceptions thrown in `builtins.tryEval` would trigger the debugger.
However, this caught nixpkgs initialization code, which is unhelpful in the majority of cases, so we changed the default.
To get the old behaviour, use `--no-ignore-try`.
```
$ nix repl --debugger --expr 'with import <nixpkgs> {}; pkgs.hello'
Lix 2.94.0-dev-pre20250625-9a59106
Type :? for help.
error: file 'nixpkgs-overlays' was not found in the Nix search path (add it using $NIX_PATH or -I)
This exception occurred in a 'tryEval' call. Use --ignore-try to skip these.
Added 13 variables.
nix-repl>
```
@@ -1,25 +0,0 @@
---
synopsis: "Fallback to safe temp dir when build-dir is unwritable"
issues: [fj#876]
cls: [3501]
category: "Fixes"
credits: ["raito", "horrors"]
---
Non-daemon builds started failing with a permission error after introducing the `build-dir` option:
```
$ nix build --store ~/scratch nixpkgs#hello --rebuild
error: creating directory '/nix/var/nix/builds/nix-build-hello-2.12.2.drv-0': Permission denied
```
This happens because:
1. These builds are not run via the daemon, which owns `/nix/var/nix/builds`.
2. The user lacks permissions for that path.
We considered making `build-dir` a store-level option and defaulting it to `<chroot-root>/nix/var/nix/builds` for chroot stores, but opted instead for a fallback: if the default fails, Nix now creates a safe build directory under `/tmp`.
To avoid CVE-2025-52991, the fallback uses an extra path component between `/tmp` and the build dir.
**Note**: this fallback clutters `/tmp` with build directories that are not cleaned up. To prevent this, explicitly set `build-dir` to a path managed by Lix, even for local workloads.
-47
View File
@@ -1,47 +0,0 @@
---
synopsis: Experimental integer coercion in interpolated strings
issues: []
cls: [3198]
category: "Features"
credits: [raito, delroth, horrors, winter]
---
Ever tried interpolating a port number in Lix and ended up with something like this?
```nix
"http://${config.network.host}:${builtins.toString config.network.port}/"
```
You're not alone. Thousands of Lix users suffer every day from excessive `builtins.toString` syndrome. Its 2025, and we still have to cast integers to use them in strings.
To address this, Lix introduces the **`coerce-integers`** experimental feature. When enabled, interpolated integers within `"${...}"` are automatically coerced to strings. This allows writing:
```nix
"http://${config.network.host}:${config.network.port}/"
```
without additional conversion.
To enable the feature, you need to add `coerce-integers` to your set of experimental features.
### Stabilization criteria
The `coerce-integers` feature is experimental and limited strictly to string interpolation (`"${...}"`). Before stabilization, the following must hold:
1. **Interpolation-only**
Coercion must not occur outside interpolation. Expressions like `"" + 42` must continue to fail.
2. **Expectation that no explicit cast are being observed**
Cases observing explicit coercion (e.g., via `tryEval` gadget or similar) are expected not to be load-bearing in actual production code.
### Timeline for stabilization
If the feature proves safe and is widely adopted across typical usage (e.g., actual configurations in the wild turning on the flag, non-trivial out-of-tree projects using it), the experimental flag will be removed **after six months of active use or two Lix releases**, whichever is longer.
This avoids locking the feature in experimental status indefinitely, as happened with Flakes, while allowing time for validation and ecosystem integration.
### What about coercing floats or more?
Coercion beyond integers -- such as for floats or other types -- is **not planned**, even under an experimental flag. Questions like "what is the canonical string representation of a float?" involve subtle and context-dependent trade-offs. Without a robust and principled mechanism to define and audit such behavior, introducing broader coercion risks setting unintended and hard-to-reverse precedents. The scope of `coerce-integers` is intentionally narrow and will remain so.
In terms of outlook, a proposal like https://git.lix.systems/lix-project/lix/issues/835 could pave the way for a better solution.
@@ -1,11 +0,0 @@
---
synopsis: Fix handling of OSC codes in terminal output
issues: [fj#160]
cls: [3143]
category: Fixes
credits: [lilyball]
---
OSC codes in terminal output are now handled correctly, where OSC 8 (hyperlink) is preserved any
time color codes are allowed and all other OSC codes are stripped out. This applies not only to
output from build commands but also to rendered documentation in the REPL.
-14
View File
@@ -1,14 +0,0 @@
---
synopsis: Better debuggability on fixed-output hash mismatches
issues: []
cls: []
category: Improvements
credits: [lheckemann]
---
Fixed-output derivation hash mismatch error messages will now include the path that was
produced unexpectedly, and this path will be registered as valid even if `--check`
(`nix-store`, `nix-build`) or `--rebuild` (`nix build`) was passed. This makes comparing
the expected path with the obtained path easier, and is useful for debugging when
upstreams modify previously-published releases or when changes in fixed-output
derivations' dependencies affect their output unexpectedly.
-12
View File
@@ -1,12 +0,0 @@
---
synopsis: "Add --raw flag to `nix-instantiate --eval` for unescaped output"
issues: []
prs: [gh#12119]
cls: [2886]
category: Improvements
credits: [not-my-profile, infinisil, raito]
---
The `nix-instantiate --eval` command now supports a `--raw` flag. When used,
the result must be coercible to a string (as with `${...}`) and is printed
verbatim, without quotes or escaping.
@@ -1,12 +0,0 @@
---
synopsis: Allow `nix store ls` to read nar listings from binary cache stores.
issues: []
cls: [3225]
category: Improvements
credits: [vlinkz]
---
The `nix store ls` command now supports reading `.ls` nar listings from binary cache stores.
If a listing is detected for the store path being queried, the nar is no longer downloaded.
These nar listings are available in binary cache stores where the `write-nar-listing` option is
enabled, such as cache.nixos.org.
-20
View File
@@ -1,20 +0,0 @@
---
synopsis: "Fixed output derivations can be run using `pasta` network isolation"
cls: [3452]
issues: [fj#285]
category: "Breaking Changes"
credits: [horrors, puck]
---
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/
@@ -1,21 +0,0 @@
---
synopsis: Remove reliance on Bash for remote stores via SSH
issues: [fj#830, fj#805, fj#304]
cls: [3159]
category: "Fixes"
credits: [raito]
---
The pre-flight `echo started` handshake -- added years ago to catch race conditions -- has been removed.
After removal of connection sharing in Lix 2.93, it required a Bash-compatible shell and a standard `echo`, so it failed on:
* builders protected by `ForceCommand` wrappers (e.g. `nix-remote-build`),
* BusyBox / initrd images with no Bash,
* hosts using non-POSIX shells such as Nushell.
The race the probe once addressed was tied to SSH connection-sharing -- since connection-sharing code has already been removed, the probe is now pointless.
Real connection or protocol errors are now left to SSH/Nix to report directly.
This is technically a breaking change if you had scripts that relied on the literal "started" which needs to be updated to rely on other signals, e.g., exit codes.
-20
View File
@@ -1,20 +0,0 @@
---
synopsis: Remove support for daemon protocols before 2.18
issues: []
cls: [3249]
significance: significant
category: "Breaking Changes"
credits: [horrors]
---
Support for daemon wire protocols belonging to Nix 2.18 or older have been
removed. This impacts clients connecting to the local daemon socket or any
remote builder configured using the `ssh-ng` protocol. Builders configured
with the `ssh` protocol are still accessible from clients such as Nix 2.3.
Additionally Lix will not be able to connect to an old daemon locally, and
remote build connections to old daemons is likewise limited to `ssh` urls.
We have decided to take this step because the old protocols are very badly
tested (if at all), maintenance overhead is high, and a number of problems
with their design makes it infeasible to remain backwards compatible while
we move Lix to a more modern RPC mechanism with better versioning support.
@@ -1,17 +0,0 @@
---
synopsis: Remove impure derivations and dynamic derivations
issues: [fj#815]
cls: [3210]
significance: significant
category: "Breaking Changes"
credits: [horrors]
---
The `impure-derivations` and `dynamic-derivations` experimental feature have
been removed.
New impure or dynamic derivations cannot be created from this point forward, and
any such pre-existing store derivations canot be read or built any more.
Derivation outputs created by building such a derivation are still valid
until garbage collected; existing store derivations can only be garbage
collected.
-20
View File
@@ -1,20 +0,0 @@
---
synopsis: "repl-overlays now work in the debugger for flakes"
issues: [fj#777]
cls: [3398]
category: Fixes
credits: [jade]
---
Due to a bug, it was previously not possible to use the debugger on flakes with repl-overlays, or with pure evaluation in general:
```
$ nix repl --pure-eval
Lix 2.94.0-dev-pre20250617-87d99da
Type :? for help.
Loading 'repl-overlays'...
error: access to absolute path '/Users/jade/.config/nix/repl.nix' is forbidden in pure eval mode (use '--impure' to override)
```
This is now fixed.
The contents of the repl-overlays file itself (i.e. most typically the top level lambda in it) will be evaluated in impure mode.
It may be necessary to use `builtins.seq` to force the impure operations to happen first if one wants to do impure operations inside a repl-overlays file in pure evaluation mode.
-32
View File
@@ -1,32 +0,0 @@
---
synopsis: Symbols reuses once-allocated Value to reduce garbage collected allocations
issues: []
cls: [3308, 3300, 3314, 3310, 3312, 3313]
category: Improvements
credits: [raito, horrors, thubrecht, nan-git]
---
In the Lix evaluator, **symbols** represent immutable strings, like those used
for attribute names.
In evaluator design, such strings are typically [**interned**](https://en.wikipedia.org/wiki/String_interning), stored uniquely
to save memory, and Lix inherits this approach from the original C++ codebase.
However, some builtins, like `builtins.attrNames`, must return a `Value` type
that can represent any Nix value (strings, integers, lists, etc.).
Before this change, these builtins would create lists of `Value` objects by
allocating them through the garbage collector, copying the symbols string
content each time.
This allocation is unnecessary if the interned symbols themselves also hold a
`Value` representation allocated outside the garbage collector, since these
live for the full duration of evaluation.
As a result, this reduces the number of allocations, leading to:
* A significant drop in maximum [resident set memory](https://en.wikipedia.org/wiki/Resident_set_size) (RSS), with some large-scale
tests showing up to 11% (about 500 MiB) savings in large colmena deployments.
* A slight decrease in CPU usage during Nix evaluations.
This change is inspired by https://github.com/NixOS/nix/pull/13258 but the approach is different.
@@ -1,15 +0,0 @@
---
synopsis: uid-range depends on cgroups
issues: []
cls: [3230]
category: "Breaking Changes"
credits: [raito, horrors]
---
`uid-range` builds now depends on `cgroups`, an experimental feature.
`uid-range` builds already depended upon `auto-allocate-uids`, another experimental feature.
The rationale for doing so is that `uid-range` provides a sandbox with many
UIDs, this is useful for re-mapping them into a nested namespace, e.g. a
container.
+4
View File
@@ -39,6 +39,9 @@
- [Tuning Cores and Jobs](advanced-topics/cores-vs-jobs.md)
- [Verifying Build Reproducibility](advanced-topics/diff-hook.md)
- [Using the `post-build-hook`](advanced-topics/post-build-hook.md)
- [Pasta](advanced-topics/pasta.md)
- [Known Issues](known-issues/known-issues.md)
- [Limitations around non-isolated builds](known-issues/non-isolated-build-limits.md)
- [Command Reference](command-ref/command-ref.md)
- [Common Options](command-ref/opt-common.md)
- [Common Environment Variables](command-ref/env-common.md)
@@ -197,6 +200,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.94 (2025-11-17)](release-notes/rl-2.94.md)
- [Lix 2.93 (2025-05-09)](release-notes/rl-2.93.md)
- [Lix 2.92 (2025-01-18)](release-notes/rl-2.92.md)
- [Lix 2.91 (2024-08-12)](release-notes/rl-2.91.md)
+19
View File
@@ -0,0 +1,19 @@
# [Pasta](https://passt.top/passt/about/): a network sandbox for fixed-output derivations
## Introduction
This section only applies to **Linux systems** as Pasta is a Linux-only measure.
Since [CVE-2025-46416](https://lix.systems/blog/2025-06-24-lix-cves/), the Lix project decided to adopt [Pasta](https://passt.top/passt/about/) for all fixed-output derivations, protecting against various attack vectors such as UNIX abstract domain sockets or more manipulation at the network layer from a malicious fixed-output derivation code.
Pasta acts as a translation layer between a layer-2 network interface and layer-4 sockets (TCP, UDP, ICMP/ICMPv6 echo) on the host. It requires no special privileges and can serve as a alternative to [SLiRP](https://en.wikipedia.org/wiki/Slirp) which was used [by Guix to mitigate the same problem](https://codeberg.org/guix/guix/commit/fb42611b8f27960304db5a1c0d33b8371dcde2a8).
## How to disable Pasta?
It's sufficient to pass `pasta-path = ""` in your `/etc/nix/nix.conf` or on the command line `--pasta-path ""` of a Lix invocation.
## Known issues surrounding Pasta
- Only the first DNS server in `/etc/resolv.conf` is considered: failover is not possible.
- [Reduced feature set compared to the Linux kernel](https://passt.top/passt/about/#features)
- [Performance overhead in multi-gigabits contexts and IMIX MTUs](https://passt.top/passt/about/#performance_1)
+1 -1
View File
@@ -58,7 +58,7 @@ $ nix-build flake:nixpkgs -A firefox
$ nix-build flake:github:NixOS/nixpkgs/release-23.11 -A firefox
```
Finally, for legacy reasons, if a path starts with `channel:`, the rest of the argument is interpreted as the name of a *nixpkgs* channel tarball to fetch from `https://nixos.org/channels/$CHANNEL_NAME/nixexprs.tar.xz`.
Finally, for legacy reasons, if a path starts with `channel:`, the rest of the argument is interpreted as the name of a *nixpkgs* channel tarball to fetch from `https://channels.nixos.org/$CHANNEL_NAME/nixexprs.tar.xz`.
This is a **hard coded URL** pattern and is *not* related to the subscribed channels managed by the [nix-channel](./nix-channel.md) command.
> **Note**: any of the special syntaxes may always be disambiguated by prefixing the path.
+3 -3
View File
@@ -11,7 +11,7 @@
Channels are a mechanism for referencing remote Nix expressions and conveniently retrieving their latest version.
The moving parts of channels are:
- The official channels listed at <https://nixos.org/channels>
- The official channels listed at <https://channels.nixos.org>
- The user-specific list of [subscribed channels](#subscribed-channels)
- The [downloaded channel contents](#channels)
- The [Nix expression search path](@docroot@/command-ref/conf-file.md#conf-nix-path), set with the [`-I` option](#opt-I) or the [`NIX_PATH` environment variable](#env-NIX_PATH)
@@ -77,9 +77,9 @@ This command has the following operations:
Subscribe to the Nixpkgs channel and run `hello` from the GNU Hello package:
```console
$ nix-channel --add https://nixos.org/channels/nixpkgs-unstable
$ nix-channel --add https://channels.nixos.org/nixpkgs-unstable
$ nix-channel --list
nixpkgs https://nixos.org/channels/nixpkgs
nixpkgs https://channels.nixos.org/nixpkgs
$ nix-channel --update
$ nix-shell -p hello --run hello
hello
+14 -7
View File
@@ -107,20 +107,27 @@ See that section for complete details (`nix-build --help`), but in summary, a pa
> This option can cause non-termination, because lazy data
> structures can be infinitely large.
- `--raw`
- `--raw`\
When used with `--eval`, the result must be coercible to a string, i.e.,
something that can be converted using `${...}`. The output is
printed exactly as-is, with no quotes, escaping, or trailing newline.
something that can be converted using `${...}`.
Integers will always generate an error when output via `--raw`, regardless of
[`coerce-integers`](../contributing/experimental-features.md#xp-feature-coerce-integers) being enabled, to avoid ambiguity.
The output is printed exactly as-is, with no quotes, escaping, or trailing
newline.
- `--json`\
When used with `--eval`, print the resulting value as an JSON
representation of the abstract syntax tree rather than as a Nix expression.
representation of the resulting value rather than as a Nix expression.
The conversion behaviour, if `--strict` is passed, is the same as
[`builtins.toJSON`](../language/builtins.md#builtins-toJSON).
- `--xml`\
When used with `--eval`, print the resulting value as an XML
representation of the abstract syntax tree rather than as a Nix expression.
The schema is the same as that used by the [`toXML`
built-in](../language/builtins.md).
representation of the resulting value rather than as a Nix expression.
The schema is the same as that used by [`builtins.toXML`](../language/builtins.md#builtins-toXML).
- `--read-write-mode`\
When used with `--eval`, perform evaluation in read/write mode so
+2 -2
View File
@@ -661,8 +661,8 @@ Verbosity levels are:
The default level that the command starts is `ERROR`. The simplest way to
increase the verbosity by stacking `-v` option (eg: `-vvv == level 3 == INFO`).
There are also two shortcuts, `--debug` to run in `DEBUG` verbosity level and
`--quiet` to run in `ERROR` verbosity level.
Use `--quiet` to decrease verbosity by one level.
There is one shortcut, `--debug` to run in `DEBUG` verbosity level.
----------
+73 -19
View File
@@ -11,7 +11,19 @@ The following instructions assume you already have some version of Nix or Lix in
[installation instructions]: ../installation/installation.md
## Building Lix in a development shell
A typical development flow for simple changes in Lix looks like:
- [Set up and build Lix](#building)
- For large changes, check in regarding design and possibly create an RFD issue on Forgejo
- Make the changes in your editor
- [Send the changes to Gerrit](#sending-to-gerrit)
- Once you have the number for the CL from Gerrit to put in the changelog, [write a changelog entry](#release-notes) and amend it into the commit
- Update the Gerrit change by submitting it with the same command as the first time
- Request and receive a code review
- Address feedback from the review
- Amend commits, send to Gerrit again
- Submit the approved change
## Building Lix in a development shell {#building}
### Setting up the development shell
@@ -48,7 +60,7 @@ $ just setup build test-unit
$ just install test-integration
```
Many targets have a `-custom` variant which pass extra arguments to `meson`.
Many justfile aliases have a `-custom` variant which pass extra arguments to `meson`.
For example, to work on both Lix and nix-eval-jobs you can run:
```
@@ -129,7 +141,44 @@ To inspect the canonical source of truth on what the state of the buildsystem co
$ meson introspect
```
## Building Lix outside of development shells
## Sending changes to Gerrit for review {#sending-to-gerrit}
We use Gerrit for all our code review in Lix.
Our instance is at <https://gerrit.lix.systems>.
There's much more information about how to use Gerrit in the [wiki section on Gerrit][wiki-gerrit] including how to use Jujutsu, how to use the UI and more.
The Snix project also has some Gerrit information [in their contributing docs][snix-gerrit].
[wiki-gerrit]: https://wiki.lix.systems/books/lix-contributors/chapter/gerrit
[snix-gerrit]: https://snix.dev/docs/guides/contributing/
The gist is that once you have your SSH key and git remote set up, you can send commits for review with:
```
$ git remote set-url origin ssh://YOURUSERNAME@gerrit.lix.systems:2022/lix
$ git push origin HEAD:refs/for/main
```
Then, you can request a review via the "Reply" button on the web UI.
If you click "Suggest Owners", it will try to suggest the maintainers of the area of the code change to send review requests to.
Requesting reviews from multiple people is normal.
We do our best to respond to directly sent reviews in a few days, so feel free to request another reviewer or ask on Matrix if you've not got a response for a while.
Keep in mind that Lix is a volunteer project and we have limited bandwidth, so some changes aren't feasible to shepherd through; please check in on Matrix at design time when doing large changes.
Once you get a `Code-Review+2` vote on your change, it's rebased on `main` and CI marks it `Verified+1`, you're able (and usually expected, so you can have a second chance to check it over) to hit the Submit button to merge it.
If the change appears as "Rebase Required", you need to rebase it on `main` locally or via the Gerrit UI and wait for `Verified+1` before the Submit button is made active
The `Code-Review+2` from before will stick around through trivial rebases so no need to re-request review for a mere rebase.
## Interacting with the CI, Buildkite
We use Buildkite for our CI, usually you will not have to interact directly with it other than reviewing any errors it produces, which are linked from Gerrit.
However in certain cases a CI run will fail due to transient issues not related to your code and you will need to rerun it by hand.
You can log in to the CI via [SSO](https://buildkite.com/sso/lix-project). On your job you can then hit the "Retry failed" button to rerun it, normally you will not have a repeat of the transient issue.
If the build still fails on CI issues or all builds are failing this should be reported via [Zulip on #T-infra](https://zulip.lix.systems/#narrow/channel/7-T-infra) or [Matrix on #dev](https://matrix.to/#/%23dev%3Alix.systems?via=lix.systems).
## Building Lix with `nix`
To build a release version of Lix for the current operating system and CPU architecture:
@@ -286,10 +335,10 @@ Configure your editor to use the `clangd` from the shell, either by running it i
> Some other editors (e.g. Emacs, Vim) need a plugin to support LSP servers in general (e.g. [lsp-mode](https://github.com/emacs-lsp/lsp-mode) for Emacs and [vim-lsp](https://github.com/prabirshrestha/vim-lsp) for vim).
> Editor-specific setup is typically opinionated, so we will not cover it here in more detail.
### Checking links in the manual
# Manual and documentation
## Building the manual
The build checks for broken internal links.
This happens late in the process, so `nix build` is not suitable for iterating.
To build the manual incrementally, run:
```console
@@ -301,15 +350,20 @@ meson compile -C build manual
[`mdbook-linkcheck`]: https://github.com/Michael-F-Bryan/mdbook-linkcheck
[URI fragments]: https://en.wikipedia.org/wiki/URI_fragment
#### `@docroot@` variable
The built manual is in `build/doc/manual/manual/index.html`.
`@docroot@` provides a base path for links that occur in reusable snippets or other documentation that doesn't have a base path of its own.
The build checks for broken internal links.
This happens late in the process, so `nix build` is not suitable for iterating and it's recommended to use the `meson` command above instead.
If a broken link occurs in a snippet that was inserted into multiple generated files in different directories, use `@docroot@` to reference the `doc/manual/src` directory.
### `@\docroot\@` variable
If the `@docroot@` literal appears in an error message from the `mdbook-linkcheck` tool, the `@docroot@` replacement needs to be applied to the generated source file that mentions it.
See existing `@docroot@` logic in the [Makefile].
Regular markdown files used for the manual have a base path of their own and they can use relative paths instead of `@docroot@`.
`@\docroot\@` provides a base path for links that occur in reusable snippets or other documentation that doesn't have a base path of its own.
If a broken link occurs in a snippet that was inserted into multiple generated files in different directories, use `@\docroot\@` to reference the `doc/manual/src` directory.
If the `@\docroot\@` literal appears in an error message from the `mdbook-linkcheck` tool, the `@\docroot\@` replacement needs to be applied to the generated source file that mentions it.
See existing `@\docroot\@` logic in `doc/manual/substitute.py`.
Regular markdown files used for the manual have a base path of their own and they can use relative paths instead of `@\docroot\@`.
## API documentation
@@ -341,7 +395,7 @@ You can build it yourself:
Metrics about the change in line/function coverage over time will be available in the future (FIXME(lix-hydra)).
## Add a release note
## Add a release note {#release-notes}
`doc/manual/rl-next` contains release notes entries for all unreleased changes.
@@ -410,15 +464,15 @@ The following properties are supported:
### Build process
Releases have a precomputed `rl-MAJOR.MINOR.md`, and no `rl-next.md`.
Set `buildUnreleasedNotes = true;` in `flake.nix` to build the release notes on the fly.
Development releases have a generated `rl-next.md`.
## Adding experimental or deprecated features, global settings, or builtins
# Adding experimental or deprecated features, global settings, or builtins
Experimental and deprecated features, global settings, and builtins are generally referenced both in the code and in the documentation.
To prevent duplication or divergence, they are defined in data files, and a script generates the necessary glue.
The data file format is similar to the release notes: it consists of a YAML metadata header, followed by the documentation in Markdown format.
### Experimental or deprecated features
## Experimental or deprecated features
Experimental and deprecated features support the following metadata properties:
* `name` (required): user-facing name of the feature, to be used in `nix.conf` options and on the command line.
@@ -428,7 +482,7 @@ Experimental and deprecated features support the following metadata properties:
Experimental feature data files should live in `lix/libutil/experimental-features`, and deprecated features in `lix/libutil/deprecated-features`.
They must be listed in the `experimental_feature_definitions` or `deprecated_feature_definitions` lists in `lix/libutil/meson.build` respectively to be considered by the build system.
### Global settings
## Global settings
Global settings support the following metadata properties:
* `name` (required): user-facing name of the setting, to be used as key in `nix.conf` and in the `--option` command line argument.
@@ -456,7 +510,7 @@ Settings are not collected in a single place in the source tree, so an appropria
Look for related setting definition files under second-level subdirectories of `lix` whose name includes `settings`.
Then add the new file there, and don't forget to register it in the appropriate `meson.build` file.
### Builtin functions
## Builtin functions
The following metadata properties are supported for builtin functions:
* `name` (required): the language-facing name (as a member of the `builtins` attribute set) of the function.
@@ -472,7 +526,7 @@ The following metadata properties are supported for builtin functions:
New builtin function definition files must be added to `lix/libexpr/builtins` and registered in the `builtin_definitions` list in `lix/libexpr/meson.build`.
### Builtin constants
## Builtin constants
The following metadata properties are supported for builtin constants:
* `name` (required): the language-facing name (as a member of the `builtins` attribute set) of the constant.
* `type` (required): the Nix language type of the constant; the C++ type is automatically derived.
+6
View File
@@ -62,6 +62,12 @@ For `installcheck` specifically, first run `just install` before running the tes
Finer-grained filtering within a test suite is also possible using the [--gtest_filter](https://google.github.io/googletest/advanced.html#running-a-subset-of-the-tests) command-line option to a test suite executable, or the `GTEST_FILTER` environment variable.
### Inspecting failures
The test suite emits logs in `build/meson-logs/`; the full textual failure logs are in `build/meson-logs/testlog.txt`.
If you want a much nicer experience of viewing the logs in a structured manner, use `xunit-viewer --results build/meson-logs/testlog.junit.xml --server` to view them in a web browser.
### Unit test support libraries
There are headers and code which are not just used to test the library in question, but also downstream libraries.
@@ -60,3 +60,10 @@ Then:
```console
$ docker run -ti lix
```
# Known issues
Lix in Docker is very sensitive to **functional** DNS resolution if you are running with [Pasta protections](../advanced-topics/pasta.md) which are enabled by default since Lix 2.93.0 on most distributions.
If you notice failure to download things, double check whether your **first** DNS entry in `/etc/resolv.conf` is functional.
Lix with [Pasta protections](../advanced-topics/pasta.md) does not support failing over the next entries.
@@ -54,11 +54,6 @@ The most current alternative to this section is to read `package.nix` and see wh
obtained from the its repository
<https://github.com/troglobit/editline>.
- The `libsodium` library for verifying cryptographic signatures
of contents fetched from binary caches.
It can be obtained from the official web site
<https://libsodium.org>.
- Recent versions of Bison and Flex to build the parser. (This is
because Nix needs GLR support in Bison and reentrancy support in
Flex.) For Bison, you need version 2.6, which can be obtained from
@@ -0,0 +1 @@
This section lists known issues around Lix.
@@ -0,0 +1,21 @@
# Limitations of non-isolated builds
## What are non-isolated builds?
In Lix, only builds done on Linux with `sandbox = true` and a functioning
`pasta-path` are isolated from the rest of the system, all other builds are
considered non-isolated to some degree.
For example, running Lix with [Pasta](@docroot@/advanced-topics/pasta.md)
disabled makes the host network visible to fixed-output derivations, reducing
isolation somewhat.
## Clean termination of non-isolated builds
Non-isolated builds may not terminate cleanly in all cases due to limitations in Lix's process management.
This occurs when a build keeps the build log file descriptor open past the end of the actual build. A common cause of this are background tasks that aren't properly terminated before the main build process exits, for example: HTTP servers run as part of a test suite.
See [issue #1018](https://git.lix.systems/lix-project/lix/issues/1018) for an example.
The only solution is to manually terminate leftover processes in your derivation, including during failure scenarios.
@@ -302,8 +302,6 @@ Derivations can declare some infrequently used optional attributes.
- `maxSize` defines the maximum size of the resulting [store object](../glossary.md#gloss-store-object).
- `maxClosureSize` defines the maximum size of the output's closure.
- `ignoreSelfRefs` controls whether self-references should be considered when
checking for allowed references/requisites.
Example:
@@ -41,7 +41,7 @@ install Lix. If this is not the case for some reason, you can add it
as follows:
```console
$ nix-channel --add https://nixos.org/channels/nixpkgs-unstable
$ nix-channel --add https://channels.nixos.org/nixpkgs-unstable
$ nix-channel --update
```
@@ -49,7 +49,7 @@ $ nix-channel --update
>
> On NixOS, youre automatically subscribed to a NixOS channel
> corresponding to your NixOS major release (e.g.
> <http://nixos.org/channels/nixos-21.11>). A NixOS channel is identical
> <https://channels.nixos.org/nixos-21.11>). A NixOS channel is identical
> to the Nixpkgs channel, except that it contains only Linux binaries
> and is updated only if a set of regression tests succeed.
+955
View File
@@ -0,0 +1,955 @@
# Lix 2.94 "Açaí na tigela" (2025-11-17)
# Lix 2.94.0 (2025-11-17)
## Breaking Changes
- Remove support for daemon protocols before 2.18 [fj#510](https://git.lix.systems/lix-project/lix/issues/510) [cl/3249](https://gerrit.lix.systems/c/lix/+/3249)
Support for daemon wire protocols belonging to Nix 2.17 or older have been
removed. This impacts clients connecting to the local daemon socket or any
remote builder configured using the `ssh-ng` protocol. Builders configured
with the `ssh` protocol are still accessible from clients such as Nix 2.3.
Additionally Lix will not be able to connect to an old daemon locally, and
remote build connections to old daemons is likewise limited to `ssh` urls.
We have decided to take this step because the old protocols are very badly
tested (if at all), maintenance overhead is high, and a number of problems
with their design makes it infeasible to remain backwards compatible while
we move Lix to a more modern RPC mechanism with better versioning support.
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
- Remove impure derivations and dynamic derivations [fj#815](https://git.lix.systems/lix-project/lix/issues/815) [cl/3210](https://gerrit.lix.systems/c/lix/+/3210)
The `impure-derivations` and `dynamic-derivations` experimental feature have
been removed.
New impure or dynamic derivations cannot be created from this point forward, and
any such pre-existing store derivations canot be read or built any more.
Derivation outputs created by building such a derivation are still valid
until garbage collected; existing store derivations can only be garbage
collected.
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
- First argument to `--arg`/`--argstr` must be a valid Nix identifier [fj#496](https://git.lix.systems/lix-project/lix/issues/496)
The first argument to `--arg`/`--argstr` must be a valid Nix identifier, i.e.
`nix-build --arg config.allowUnfree true` is now rejected.
This is because that invocation is a false friend since it doesn't set
`{ config = { allowUnfree = true; }; }`, but `{ "config.allowUnfree" = true; }`.
The idea is to change the behavior to the latter in the long-term. For that,
non-identifiers started giving a warning since 2.92 and are now rejected to give people
who depend on that a chance to notice and potentially weigh in on the discussion.
Many thanks to [ma27](https://git.lix.systems/ma27) for this.
- New cgroup delegation model [fj#537](https://git.lix.systems/lix-project/lix/issues/537) [fj#77](https://git.lix.systems/lix-project/lix/issues/77) [cl/3230](https://gerrit.lix.systems/c/lix/+/3230)
Builds using cgroups (i.e. `use-cgroups = true` and the experimental feature
`cgroups`) now always delegate a cgroup tree to the sandbox.
Compared to the original C++ Nix project, our delegation includes the
`subtree_control` file as well, which means that the sandbox can disable
certain controllers in its own cgroup tree.
This is a breaking change because this requires the Nix daemon to run with an
already delegated cgroup tree by the service manager.
## How to setup the cgroup tree with systemd?
systemd offers knobs to perform the required setup using:
```
[Service]
Delegate=yes
DelegateSubtree=supervisor
```
These directives are now included in our systemd packaging.
## What about using Nix as root without connecting to the daemon?
Builds run as `root` without connecting to the daemon relying on the cgroup
feature are now broken, i.e.
```console
# nix-build --use-cgroups --sandbox ... # will not work
```
Consider doing instead:
```console
# systemd-run --same-dir --wait -p Delegate=yes -p DelegateSubgroup=supervisor nix-build --use-cgroups ...
```
If you need to disable cgroups temporarily, remember that you can do
`NIX_CONF='include /etc/nix/nix.conf\nuse-cgroups = false' nix-build ...` or
`nix-build --no-use-cgroups ...`.
## What about other service managers than systemd?
systemd has a [documentation](https://systemd.io/CGROUP_DELEGATION/) on how to
handle cgroup delegation from service management perspective.
If your service manager adheres to systemd semantics, e.g. writing an extended
attribute `user.delegate=1` on the delegated cgroup tree directory and moving
the `nix-daemon` process inside a cgroup tree to respect the inner process
rule, then, the feature will work as well.
## Why is the cgroup feature still experimental?
While the cgroup feature unlocks many use cases, its behavior and integration (e.g. user experience), especially at scale on build farms or in multi-tenant environments, are not yet fully matured. Theres also potential for deeper systemd integration (e.g. using slices and scopes) that has not been fully explored.
To avoid locking in an unstable interface, were keeping the experimental flag until we have validated the feature across a broader range of scenarios, including but not limited to:
* Nix as root
* Hydra-style build farms
* Forgejo CI runners
* Shared remote builders
Many thanks to [Raito Bezarius](https://git.lix.systems/raito), [eldritch horrors](https://git.lix.systems/pennae), and [lheckemann](https://git.lix.systems/lheckemann) for this.
- Enable high compress ratio zstd compression by default for binary caches uploads [fj#945](https://git.lix.systems/lix-project/lix/issues/945) [cl/4503](https://gerrit.lix.systems/c/lix/+/4503)
The default compression method for binary cache uploads has been switched from
[`xz`](https://github.com/tukaani-project/xz) to
[`zstd`](https://github.com/facebook/zstd) to address performance and usability
issues related to modern hardware and high-speed connections.
## Why?
`xz` offers compression ratios but is single-threaded in our implementation and
very slow (~10-20 Mbps in our test), preventing full utilization of 100Mbps+
connections and significantly slowing decompression for end users.
Lix is a "compress once, decompress many" application: build farms can afford
to spend more time compressing to achieve a faster download transfer for the
end user. More importantly, it matters that all end users spend the least
amount of time decompressing.
## What about compression ratios?
`zstd` cannot achieve the same peaks as `xz`, nonetheless, `zstd` compression
level has been increased to level 12 by default to balance compression ratio
and performance.
## Synthetic test case data
* **xz** (default compression level) on a 4.4GB file: ~632MB (77s)
* **zstd** (level 12) on the same file: ~775MB (18s), 18% larger but 50% faster
* **zstd** (level 14): ~773MB (37s)
* **zstd** (level 16): ~735MB (66s)
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) and [Raito Bezarius](https://git.lix.systems/raito) for this.
- Repl debugger uses `--ignore-try` by default [lix#666](https://git.lix.systems/lix-project/lix/issues/666) [cl/3488](https://gerrit.lix.systems/c/lix/+/3488)
Previously, using the debugger meant that exceptions thrown in `builtins.tryEval` would trigger the debugger.
However, this caught nixpkgs initialization code, which is unhelpful in the majority of cases, so we changed the default.
To get the old behaviour, use `--no-ignore-try`.
```
$ nix repl --debugger --expr 'with import <nixpkgs> {}; pkgs.hello'
Lix 2.94.0-dev-pre20250625-9a59106
Type :? for help.
error: file 'nixpkgs-overlays' was not found in the Nix search path (add it using $NIX_PATH or -I)
This exception occurred in a 'tryEval' call. Use --ignore-try to skip these.
Added 13 variables.
nix-repl>
```
Many thanks to [jade](https://git.lix.systems/jade) for this.
- Strings may now contain NUL bytes [cl/3968](https://gerrit.lix.systems/c/lix/+/3968)
Lix now allows strings to contain NUL bytes instead of silently truncating the
string before the first such byte. Notably NUL-bearing strings were allowed as
attribute names—even though the corresponding strings were not representable!—
leading to very surprising and incorrect behavior in corner cases, for example
```
nix-repl> builtins.fromJSON ''{"a": 1, "a\u0000b": 2}''
{
a = 1;
"ab" = 2;
}
nix-repl> builtins.attrNames (builtins.fromJSON ''{"a": 1, "a\u0000b": 2}'')
[
"a"
"a"
]
```
rather than the more correct but still with the terminal eating NUL on display
```
nix-repl> builtins.fromJSON ''{"a": 1, "a\u0000b": 2}''
{
a = 1;
"ab" = 2;
}
nix-repl> builtins.attrNames (builtins.fromJSON ''{"a": 1, "a\u0000b": 2}'')
[
"a"
"ab"
]
```
We consider this a breaking change since eval results *will* change if strings
with embedded NUL bytes were used, but we also consider the old behavior to be
not intentional (seeing how inconsistent it was) but merely fallout from a old
and misguided implementation decision to be worked around, not actually fixed.
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
- Fixed output derivations can be run using `pasta` network isolation [fj#285](https://git.lix.systems/lix-project/lix/issues/285) [cl/3452](https://gerrit.lix.systems/c/lix/+/3452)
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.
- Function equality semantics are more consistent, but still bad [cl/4556](https://gerrit.lix.systems/c/lix/+/4556) [cl/4244](https://gerrit.lix.systems/c/lix/+/4244)
Lix has inherited a historic misfeature from CppNix in the form of pointer
equality checks built into the `==` operator. These checks were originally
meant to optimize comparison for large sets, but they have the unfortunate
side effect of producing unexpected results when sets containing functions
are compared. **Lix 2.93 and earlier** behave as shown in the repl session
```
Lix 2.93.3
Type :? for help.
nix-repl> f = x: x
Added f.
nix-repl> f == f
false
nix-repl> let s.f = f; in s.f == s.f
false
nix-repl> # however!
{ inherit f; } == { inherit f; }
true
nix-repl> [ f ] == [ f ]
true
nix-repl> # and, in another twist:
[ f ] == map f [ f ]
false
```
Nixpkgs relies on sets containing functions being comparable, so we cannot
simply deprecate this behavior. Due to changes to the object model used by
Lix ***all* comparisons above now evaluate to `true`**. This is considered
a breaking change because eval results may differ, but we also consider it
minor because the optimization is unsound (c.f. `let l = [NaN]; in l == l`
evaluates to `true` even though floating point `NaN` is incomparable). Lix
intends to remove this optimization altogether in the future, but until we
can do that we instead make it slightly less broken to allow other, *real*
optimizations. Function equality comparison remains **undefined behavior**
and should not be relied upon in Nixlang code that intends to be portable.
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
- `nix eval --write-to` has been removed [fj#974](https://git.lix.systems/lix-project/lix/issues/974) [fj#227](https://git.lix.systems/lix-project/lix/issues/227) [cl/4045](https://gerrit.lix.systems/c/lix/+/4045)
`nix eval --write-to` has been removed since it was underspecified, not widely
useful, and prone to security-sensitive misbehaviors. The feature was added in
Nix 2.4 purely for internal use in the build system. According to our research
it hasn't found any use outside of some distribution packaging scripts. Please
use structured outputs formats (such as JSON) instead as they have better type
fidelity, don't conflate attributes with paths, and are useful to other tools.
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
- Remove the `parse-toml-timestamps` experimental feature
The `parse-toml-timestamps` experimental feature has been removed.
This feature used inband signalling to mark timestamps, making it
impossible to unambiguously parse TOML documents. It also exposed
implementationdefined behaviour in the TOML specification that
changed in the toml11 parser library.
Any interface for parsing TOML timestamps suitable for future
stabilization would necessarily involve breaking changes, and there
is no evidence this experimental feature is being relied upon in the
wild, so it has been removed.
Many thanks to [Emily](https://git.lix.systems/emilazy) for this.
- Reject overflowing TOML integer literals [cl/3916](https://gerrit.lix.systems/c/lix/+/3916)
The toml11 library used by Lix was updated. The new
version aligns with the [TOML v1.0.0 specifications
requirement](https://toml.io/en/v1.0.0#integer) to reject integer
literals that cannot be losslessly parsed. This means that code like
`builtins.fromTOML "v=0x8000000000000000"` will now produce an error
rather than silently saturating the integer result.
Many thanks to [Emily](https://git.lix.systems/emilazy) for this.
- uid-range depends on cgroups [cl/3230](https://gerrit.lix.systems/c/lix/+/3230)
`uid-range` builds now depends on `cgroups`, an experimental feature.
`uid-range` builds already depended upon `auto-allocate-uids`, another experimental feature.
The rationale for doing so is that `uid-range` provides a sandbox with many
UIDs, this is useful for re-mapping them into a nested namespace, e.g. a
container.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) and [eldritch horrors](https://git.lix.systems/pennae) for this.
## Features
- Add `inputs.self.submodules` flake attribute [fj#942](https://git.lix.systems/lix-project/lix/issues/942) [cl/3839](https://gerrit.lix.systems/c/lix/+/3839)
A port of <https://github.com/NixOS/nix/pull/12421> to Lix, which:
- adds a general `inputs.self` flake attribute that retroactively applies
configurations to a flake after it's been fetched, then triggers a refetch of
the flake with the new config.
- implements `inputs.self.submodules` that allows a flake to declare its need
for submodules, which are then fetched automatically with no need to pass
`?submodules=1` anywhere.
Many thanks to [Eelco Dolstra](https://github.com/edolstra) and [ورد](https://git.lix.systems/janw4ld) for this.
- Lix supports HTTP/3 behind `--http3` [fj#1033](https://git.lix.systems/lix-project/lix/issues/1033)
Lix now supports HTTP/3 for file transfers when the linked curl version
supports it.
By default, HTTP/3 is disabled notably due to performance issues reported in
mid-2024. [More details
here](https://daniel.haxx.se/blog/2024/06/10/http-3-in-curl-mid-2024/).
As of 2025-11-14, [NixOS official cache](https://cache.nixos.org) supports
HTTP/3 via Fastly. [More info
here](https://github.com/NixOS/infra/commit/157fa70e46afbd6338a32407be461fce05c57bf8).
To enable HTTP/3:
* Use `--http3` for individual transfers.
* Add `http3 = true` in your Nix configuration for permanent activation.
To disable it, use `--no-http3`.
**Note**:
* `--no-http2 --http3` will still enable both HTTP/2 and HTTP/3.
* `--http2 --http3` will prioritize HTTP/3 and fall back to HTTP/2 (and then
HTTP/1.1).
These are current CLI limitations. In the future, we plan to replace `--httpX`
options with `--max-http-version [1,2,3]` for easier version selection in Lix
transfers.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) and [eldritch horrors](https://git.lix.systems/pennae) for this.
- Add hyperlinks in attr set printing [cl/3790](https://gerrit.lix.systems/c/lix/+/3790)
The attribute set printer, such as is seen in `nix repl` or in type errors, now prints hyperlinks on each attribute name to its definition site if it is known.
Example: all of the attributes shown here are hyperlinks to the exact definition site of the attribute in question:
```
$ nix eval -f '<nixpkgs>' lib.licenses.mit
{ deprecated = false; free = true; fullName = "MIT License"; redistributable = true; shortName = "mit"; spdxId = "MIT"; url = "https://spdx.org/licenses/MIT.html"; }
```
Many thanks to [jade](https://git.lix.systems/jade) for this.
- Experimental integer coercion in interpolated strings [cl/3198](https://gerrit.lix.systems/c/lix/+/3198)
Ever tried interpolating a port number in Lix and ended up with something like this?
```nix
"http://${config.network.host}:${builtins.toString config.network.port}/"
```
You're not alone. Thousands of Lix users suffer every day from excessive `builtins.toString` syndrome. Its 2025, and we still have to cast integers to use them in strings.
To address this, Lix introduces the **`coerce-integers`** experimental feature. When enabled, interpolated integers within `"${...}"` are automatically coerced to strings. This allows writing:
```nix
"http://${config.network.host}:${config.network.port}/"
```
without additional conversion.
To enable the feature, you need to add `coerce-integers` to your set of experimental features.
### Stabilization criteria
The `coerce-integers` feature is experimental and limited strictly to string interpolation (`"${...}"`). Before stabilization, the following must hold:
1. **Interpolation-only**
Coercion must not occur outside interpolation. Expressions like `"" + 42` must continue to fail.
2. **Expectation that no explicit cast are being observed**
Cases observing explicit coercion (e.g., via `tryEval` gadget or similar) are expected not to be load-bearing in actual production code.
### Timeline for stabilization
If the feature proves safe and is widely adopted across typical usage (e.g., actual configurations in the wild turning on the flag, non-trivial out-of-tree projects using it), the experimental flag will be removed **after six months of active use or two Lix releases**, whichever is longer.
This avoids locking the feature in experimental status indefinitely, as happened with Flakes, while allowing time for validation and ecosystem integration.
### What about coercing floats or more?
Coercion beyond integers -- such as for floats or other types -- is **not planned**, even under an experimental flag. Questions like "what is the canonical string representation of a float?" involve subtle and context-dependent trade-offs. Without a robust and principled mechanism to define and audit such behavior, introducing broader coercion risks setting unintended and hard-to-reverse precedents. The scope of `coerce-integers` is intentionally narrow and will remain so.
In terms of outlook, a proposal like https://git.lix.systems/lix-project/lix/issues/835 could pave the way for a better solution.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito), [delroth](https://github.com/delroth), [eldritch horrors](https://git.lix.systems/pennae), and [winter](https://git.lix.systems/winter) for this.
- nix-eval-jobs: support `--no-instantiate` flag [fj#987](https://git.lix.systems/lix-project/lix/issues/987)
`nix-eval-jobs` now supports a flag called `--no-instantiate`. With this enabled,
no write operations on the eval store are performed. That means, only evaluation is
performed, but derivations (and their gcroots) aren't created.
Many thanks to [mic92](https://github.com/mic92) and [ma27](https://git.lix.systems/ma27) for this.
## Improvements
- Assess current profile generations pointers in `nix doctor` [cl/3108](https://gerrit.lix.systems/c/lix/+/3108)
Added a new check to `nix doctor` that verifies whether the current generation of
a Nix profile can be resolved. This helps users diagnose issues with broken or
misconfigured profile symlinks.
This helps determining if you have broken symlinks or misconfigured packaging.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
- Improved susbtituter query speed
The code used to query substituters for derivations has been rewritten slightly
to take advantage of our asynchronous runtime. Such queries run for every build
that could download from substituters and processes every derivation that isn't
yet present on the local system. Previously Lix would use `http-connections` to
limit query concurrency, even for modern caches that support HTTP/2 and have no
limit on how many queries can be run concurrently on one single connection. Lix
no longer does this, resulting in approximately 60% reduction in query time for
medium-sized closures (e.g. NixOS system closures) during testing, although the
exact number depends greatly on local network latency and generally improves as
latency increases. Unlike previously setting `http-connections` to `1` or other
low values no longer brings a massive penalty in query performance if the cache
in use by the querying system supports HTTP/2 (as e.g. `cache.nixos.org` does).
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
- Hitting Control-C twice always terminates Lix [cl/3574](https://gerrit.lix.systems/c/lix/+/3574)
Hitting Control-C or sending `SIGINT` to Lix now prints an informational message
if it is still running after on second, the second Control-C/`SIGINT` terminates
Lix immediately without waiting for any shutdown code to finish running. Lix did
not treat the second such event differently from first in the past; this made it
impossible to easily terminate running Lix processes that got stuck in e.g. very
expensive Nixlang code that never interacted with the store. We now terminate as
soon as the user hits Control-C again without waiting any more, to much the same
effect as putting Lix into the background and killing it immediately afterwards.
This means you can now more conveniently break out of stuck Nixlang evaluations:
```
nix-instantiate --eval --expr 'let f = n: if n == 0 then 0 else f (n - 1) + f (n - 1); in f 32'
^CStill shutting down. Press ^C again to abort all operations immediately.
^C
❌130
```
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
- `--keep-failed` chowns the build directory to the user that request the build
Running a build with `--keep-failed` now chowns the temporary directory from the
builder user and group to the user that request the build if the build came from
a local user connected to the daemon. This makes inspecting failed derivations a
lot easier. On Linux the build directory made visible to the user will not be in
the same path as it was in the sandbox and continuing builds will usually break.
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
- Better debuggability on fixed-output hash mismatches
Fixed-output derivation hash mismatch error messages will now include the path that was
produced unexpectedly, and this path will be registered as valid even if `--check`
(`nix-store`, `nix-build`) or `--rebuild` (`nix build`) was passed. This makes comparing
the expected path with the obtained path easier, and is useful for debugging when
upstreams modify previously-published releases or when changes in fixed-output
derivations' dependencies affect their output unexpectedly.
Many thanks to [lheckemann](https://git.lix.systems/lheckemann) for this.
- Add --raw flag to `nix-instantiate --eval` for unescaped output [gh#12119](https://github.com/NixOS/nix/pull/12119) [cl/2886](https://gerrit.lix.systems/c/lix/+/2886)
The `nix-instantiate --eval` command now supports a `--raw` flag. When used,
the result must be coercible to a string (as with `${...}`) and is printed
verbatim, without quotes or escaping.
Many thanks to [Martin Fischer](https://github.com/not-my-profile), [infinisil](https://github.com/infinisil), and [Raito Bezarius](https://git.lix.systems/raito) for this.
- Allow `nix store ls` to read nar listings from binary cache stores. [cl/3225](https://gerrit.lix.systems/c/lix/+/3225)
The `nix store ls` command now supports reading `.ls` nar listings from binary cache stores.
If a listing is detected for the store path being queried, the nar is no longer downloaded.
These nar listings are available in binary cache stores where the `write-nar-listing` option is
enabled, such as cache.nixos.org.
Many thanks to [Victor Fuentes](https://git.lix.systems/vlinkz) for this.
- show tree with references that lead to an output cycle [fj#551](https://git.lix.systems/lix-project/lix/issues/551)
When Lix determines a cyclic dependency between several outputs of a derivation,
it now displays which files in which outputs lead to an output cycle:
```
error: cycle detected in build of '/nix/store/gc5h2whz3rylpf34n99nswvqgkjkigmy-demo.drv' in the references of output 'bar' from output 'foo'.
Shown below are the files inside the outputs leading to the cycle:
/nix/store/3lrgm74j85nzpnkz127rkwbx3fz5320q-demo-bar
└───lib/libfoo: …stuffbefore /nix/store/h680k7k53rjl9p15g6h7kpym33250w0y-demo-baz andafter.…
→ /nix/store/h680k7k53rjl9p15g6h7kpym33250w0y-demo-baz
└───share/snenskek: …???? /nix/store/dm24c76p9y2mrvmwgpmi64rryw6x5qmm-demo-foo ....…
→ /nix/store/dm24c76p9y2mrvmwgpmi64rryw6x5qmm-demo-foo
└───bin/alarm: …textexttext/nix/store/3lrgm74j85nzpnkz127rkwbx3fz5320q-demo-bar abcabcabc.…
→ /nix/store/3lrgm74j85nzpnkz127rkwbx3fz5320q-demo-bar
```
Please note that showing the files and its contents while displaying the cycles only works
on Linux.
Many thanks to [ma27](https://git.lix.systems/ma27) for this.
- Lix now enables parallel marking in boehm-gc [fj#983](https://git.lix.systems/lix-project/lix/issues/983) [cl/3880](https://gerrit.lix.systems/c/lix/+/3880)
This brings a fairly modest performance improvement (~38% for `nixpkgs search hello`) to evaluation, especially in scenarios that necessitate larger heap sizes.
Many thanks to [Eelco Dolstra](https://github.com/edolstra) and [Seth Flynn](https://git.lix.systems/getchoo) for this.
- `disallowedRequisites` now reports chains of disallowed requisites [fj#334](https://git.lix.systems/lix-project/lix/issues/334) [fj#626](https://git.lix.systems/lix-project/lix/issues/626) [gh#10877](https://github.com/NixOS/nix/issues/10877)
When a build fails because of [`disallowedRequisites`](@docroot@/language/advanced-attributes.md#adv-attr-disallowedRequisites), the error message now includes the chain of references that led to the failure. This makes it easier to see in which derivations the chain can be broken, to resolve the problem.
Example:
```
$ nix-build -A hello
error: output '/nix/store/0b7k85gg5r28gb54px9nq7iv5986mns9-hello-2.12.2' is not allowed to refer to the following paths:
/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-glibc-2.40-66
Shown below are chains that lead to the forbidden path(s).
/nix/store/0b7k85gg5r28gb54px9nq7iv5986mns9-hello-2.12.2
└───/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-glibc-2.40-66
```
Many thanks to [ma27](https://git.lix.systems/ma27) and [Robert Hensing](https://github.com/roberth) for this.
- Stack traces now summarize involved derivations at the bottom [cl/4493](https://gerrit.lix.systems/c/lix/+/4493)
When evaluation errors and a stack trace is printed,
For example, if I add Nheko to a NixOS `environment.systemPackages` without adding `olm-3.2.16` `nixpkgs.config.permittedInsecurePackages`, then without `--show-trace`, I previously got this:
```
error:
… while calling the 'head' builtin
at /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/lib/attrsets.nix:1701:13:
1700| if length values == 1 || pred here (elemAt values 1) (head values) then
1701| head values
| ^
1702| else
… while evaluating the attribute 'value'
at /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/lib/modules.nix:1118:7:
1117| // {
1118| value = addErrorContext "while evaluating the option `${showOption loc}':" value;
| ^
1119| inherit (res.defsFinal') highestPrio;
(stack trace truncated; use '--show-trace' to show the full trace)
error: Package olm-3.2.16 in /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/pkgs/by-name/ol/olm/package.nix:37 is marked as insecure, refusing to evaluate.
< -snip the whole explanation about olm's CVEs- >
```
This doesn't tell me anything about where `olm-3.2.16` came from.
With `--show-trace`, there's 1155 lines to sift through, but does contain lines like "while evaluating derivation 'nheko-0.12.1'".
With this change, those lines are summarized and collected at the bottom, regardless of `--show-trace`:
```
error:
… while calling the 'head' builtin
at /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/lib/attrsets.nix:1701:13:
1700| if length values == 1 || pred here (elemAt values 1) (head values) then
1701| head values
| ^
1702| else
… while evaluating the attribute 'value'
at /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/lib/modules.nix:1118:7:
1117| // {
1118| value = addErrorContext "while evaluating the option `${showOption loc}':" value;
| ^
1119| inherit (res.defsFinal') highestPrio;
(stack trace truncated; use '--show-trace' to show the full trace)
error: Package olm-3.2.16 in /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/pkgs/by-name/ol/olm/package.nix:37 is marked as insecure, refusing to evaluate.
< -snip the whole explanation about olm's CVEs- >
note: trace involved the following derivations:
derivation 'etc'
derivation 'dbus-1'
derivation 'system-path'
derivation 'nheko-0.12.1'
derivation 'mtxclient-0.10.1'
```
Now we finally know that olm was evaluated because of Nheko, without sifting through *thousands* of lines of error message.
Many thanks to [Qyriad](https://git.lix.systems/Qyriad) for this.
- Symbols reuses once-allocated Value to reduce garbage collected allocations [cl/3308](https://gerrit.lix.systems/c/lix/+/3308) [cl/3300](https://gerrit.lix.systems/c/lix/+/3300) [cl/3314](https://gerrit.lix.systems/c/lix/+/3314) [cl/3310](https://gerrit.lix.systems/c/lix/+/3310) [cl/3312](https://gerrit.lix.systems/c/lix/+/3312) [cl/3313](https://gerrit.lix.systems/c/lix/+/3313)
In the Lix evaluator, **symbols** represent immutable strings, like those used
for attribute names.
In evaluator design, such strings are typically [**interned**](https://en.wikipedia.org/wiki/String_interning), stored uniquely
to save memory, and Lix inherits this approach from the original C++ codebase.
However, some builtins, like `builtins.attrNames`, must return a `Value` type
that can represent any Nix value (strings, integers, lists, etc.).
Before this change, these builtins would create lists of `Value` objects by
allocating them through the garbage collector, copying the symbols string
content each time.
This allocation is unnecessary if the interned symbols themselves also hold a
`Value` representation allocated outside the garbage collector, since these
live for the full duration of evaluation.
As a result, this reduces the number of allocations, leading to:
* A significant drop in maximum [resident set memory](https://en.wikipedia.org/wiki/Resident_set_size) (RSS), with some large-scale
tests showing up to 11% (about 500 MiB) savings in large colmena deployments.
* A slight decrease in CPU usage during Nix evaluations.
This change is inspired by https://github.com/NixOS/nix/pull/13258 but the approach is different.
**Note** : [`xokdvium`](https://github.com/xokdvium) is the rightful author of https://gerrit.lix.systems/c/lix/+/3300 and the credit was missed on our end during the development process. We are deeply sorry for this mistake.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito), [eldritch horrors](https://git.lix.systems/pennae), [Tom Hubrecht](https://git.lix.systems/tom-hubrecht), [xokdvium](https://github.com/xokdvium), and [NaN-git](https://github.com/NaN-git) for this.
## Fixes
- `build-dir` no longer defaults to `temp-dir` [cl/3453](https://gerrit.lix.systems/c/lix/+/3453)
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/b` in the default configuration).
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
- Global certificate authorities are copied inside the builder's environment [gh#12698](https://github.com/NixOS/nix/issues/12698) [fj#885](https://git.lix.systems/lix-project/lix/issues/885) [cl/3765](https://gerrit.lix.systems/c/lix/+/3765)
Previously, CA certificates were only installed at
`/etc/ssl/certs/ca-certificates.crt` for sandboxed builds on Linux.
This setup was insufficient in light of recent changes in `nixpkgs`, which now
enforce HTTPS usage for `fetchurl`, even for fixed-output derivations, to
mitigate confidentiality risks such as `netrc` or credentials leakage.
`nixpkgs` still make use of a special package called `cacerts` which contains a
copy of the CA certificates maintained by Nixpkgs and added as a reference for
TLS-enabled fetchers.
As a result, having a consistent and trusted certificate authority in all
builder environments is becoming more essential.
On `nix-darwin`, the `NIX_SSL_CERT_FILE` environment variable is always
explicitly defined, but it is ignored by the sandbox setup.
Simultaneously, Nix evaluates and propagates impure environment variables via
`lib.proxyImpureEnvVars`, meaning that if `NIX_SSL_CERT_FILE` is set (which
influences the default value for `ssl-cert-file`), it will be forwarded
unchanged into the builder environment.
However, on Linux, Nix also *copies* the CA file into the sandbox, creating a
discrepancy between the value of `NIX_SSL_CERT_FILE` and the actual trusted
certificate path used during the build.
This divergence caused confusion and was partially addressed by attempts to
whitelist the CA path in the Darwin sandbox (see cl/2906), but that approach
involved a non-trivial path canonicalization step and is not as general as this one.
To address this properly, we now emit a warning and override
`NIX_SSL_CERT_FILE` inside the builder, explicitly pointing it to the CA file
copied into the sandbox.
This eliminates ambiguity between `NIX_SSL_CERT_FILE`
and `ssl-cert-file`, ensuring consistent trust anchors across platforms.
This warning might become a hard error as we figure out what to do regarding
`lib.proxyImpureEnvVars` in nixpkgs.
The behavior has been verified across sandboxed and unsandboxed builds on both
Linux and Darwin.
As a consequence of this change, approximately 500KB of CA certificate data is
now unconditionally copied into the build directory for fixed-output
derivations.
While this ensures consistent trust verification without having to restart the
daemon after system upgrades, it may introduce a slight overhead in build
performance. At present, no optimizations have been implemented to avoid this
copy, but if this overhead proves noticeable in your workflows, please open an
issue so we can evaluate and possibly implement different strategies to render
trust anchors visible.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) and [Emily](https://git.lix.systems/emilazy) for this.
- libstore: exponential backoff for downloads [lix#932](https://git.lix.systems/lix-project/lix/issues/932) [cl/3856](https://gerrit.lix.systems/c/lix/+/3856)
The connection timeout when downloading from e.g. a binary cache is exponentially
increased per failure. The option `connect-timeout` is now an alias to `max-connect-timeout`
which is the maximum value for a timeout. The start value is controlled
by `initial-connect-timeout` which is `5` by default.
Many thanks to [ma27](https://git.lix.systems/ma27) for this.
- Fix develop shells for derivations with escape codes [fj#991](https://git.lix.systems/lix-project/lix/issues/991) [cl/4154](https://gerrit.lix.systems/c/lix/+/4154) [cl/4155](https://gerrit.lix.systems/c/lix/+/4155)
ASCII control characters (including `\e`, used for ANSI escape codes) in derivation variables are now correctly escaped for `nix develop` and `nix print-dev-env`, instead of erroring.
Many thanks to [Qyriad](https://git.lix.systems/Qyriad) for this.
- nix-store --delete: always remove obsolete hardlinks [cl/3188](https://gerrit.lix.systems/c/lix/+/3188)
Deleting specific paths using `nix-store --delete` or `nix store
delete` previously did not delete hard links created by `nix-store
--optimise` even if they became obsolete, unless _all_ of the given
paths were deleted successfully. Now, hard links are always cleaned
up, even if some of the given paths could not be deleted.
Many thanks to [lheckemann](https://git.lix.systems/lheckemann) for this.
- Report GC statistics correctly [cl/3188](https://gerrit.lix.systems/c/lix/+/3188)
Deleting specific paths using `nix-store --delete` or `nix store delete` previously did
not report statistics correctly when some of the paths could not be deleted, even if
others were deleted:
```
$ nix store delete /nix/store/9bwryidal9q3g91cjm6xschfn4ikd82q-hello-2.12.1 --delete-closure -v
finding garbage collector roots...
deleting '/nix/store/9bwryidal9q3g91cjm6xschfn4ikd82q-hello-2.12.1'
0 store paths deleted, 0.00 MiB freed
error: Cannot delete some of the given paths because they are still alive. Paths not deleted:
k9bxzr1l92r5y6mihrkbpbr3fmc8qszx-libidn2-2.3.8
mbx9ii53lzjlrsnlrfmzpwm33ynljwdn-libunistring-1.3
rf8hcy6bldxdqc0g6q1dcka1vh47x69s-xgcc-14.2.1.20250322-libgcc
vbrdc5wgzn0w1zdp10xd2favkjn5fk7y-glibc-2.40-66
To find out why, use nix-store --query --roots and nix-store --query --referrers.
```
Many thanks to [lheckemann](https://git.lix.systems/lheckemann) for this.
- Fallback to safe temp dir when build-dir is unwritable [fj#876](https://git.lix.systems/lix-project/lix/issues/876) [cl/3501](https://gerrit.lix.systems/c/lix/+/3501)
Non-daemon builds started failing with a permission error after introducing the `build-dir` option:
```
$ nix build --store ~/scratch nixpkgs#hello --rebuild
error: creating directory '/nix/var/nix/builds/nix-build-hello-2.12.2.drv-0': Permission denied
```
This happens because:
1. These builds are not run via the daemon, which owns `/nix/var/nix/builds`.
2. The user lacks permissions for that path.
We considered making `build-dir` a store-level option and defaulting it to `<chroot-root>/nix/var/nix/builds` for chroot stores, but opted instead for a fallback: if the default fails, Nix now creates a safe build directory under `/tmp`.
To avoid CVE-2025-52991, the fallback uses an extra path component between `/tmp` and the build dir.
**Note**: this fallback clutters `/tmp` with build directories that are not cleaned up. To prevent this, explicitly set `build-dir` to a path managed by Lix, even for local workloads.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) and [eldritch horrors](https://git.lix.systems/pennae) for this.
- Parse overflowing JSON number literals as floatingpoint [cl/3919](https://gerrit.lix.systems/c/lix/+/3919)
Previously, `builtins.fromJSON "-9223372036854775809"` would
return a floatingpoint number, while `builtins.fromJSON
"9223372036854775808"` would cause an evaluation error. This was
introduced with the banning of integer overflow in Lix 2.91; previously
the latter would result in C++ undefined behaviour. These cases are
now treated consistently with JSONs model of a single numeric type,
and JSON number literals that do not fit in a Nixlanguage integer
will be parsed as floatingpoint numbers.
Many thanks to [Emily](https://git.lix.systems/emilazy) for this.
- Fix handling of OSC codes in terminal output [fj#160](https://git.lix.systems/lix-project/lix/issues/160) [cl/3143](https://gerrit.lix.systems/c/lix/+/3143)
OSC codes in terminal output are now handled correctly, where OSC 8 (hyperlink) is preserved any
time color codes are allowed and all other OSC codes are stripped out. This applies not only to
output from build commands but also to rendered documentation in the REPL.
Many thanks to [lilyball](https://git.lix.systems/lilyball) for this.
- Fix nix develop for derivations that rejects dependencies with structured attrs [fj#997](https://git.lix.systems/lix-project/lix/issues/997) [cl/4182](https://gerrit.lix.systems/c/lix/+/4182)
For the sake of concision, we refer to `disallowedReferences` in what follows,
but all output checks were equally fixed:
`{dis,}allowed{References,Requisites}`.
Derivations can define *output checks* to reject unwanted dependencies, such as
interpreters like `bash` or compilers like `gcc`. This can be done in two ways:
* **Legacy style**: `disallowedReferences = [ ... ]` in the environment.
* **Structured attrs**: `outputChecks.<output>.disallowedReferences = [ ... ]`,
typically used in `__json`.
Only the structured form supports derivations with multiple outputs.
`nix develop` internally rewrites derivations to create development shells. It
relied on the legacy `disallowedReferences`, and failed to honor the structured
variant. This led to broken shells in cases where `bashInteractive` was
explicitly disallowed using structured output checks, e.g. `nix develop
nixpkgs#systemd` after the "bash-less NixOS" changes.
This fix teaches `nix develop` to respect structured output checks, restoring
support for such derivations.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
- nix-eval-jobs: retain NIX_PATH [cl/3859](https://gerrit.lix.systems/c/lix/+/3859)
`nix-eval-jobs` doesn't clear the `NIX_PATH` from the environment anymore. This matches the behavior
of [upstream version `2.30`](https://github.com/nix-community/nix-eval-jobs/releases/tag/v2.30.0).
Many thanks to [ma27](https://git.lix.systems/ma27) and [mic92](https://github.com/mic92) for this.
- Remove reliance on Bash for remote stores via SSH [fj#830](https://git.lix.systems/lix-project/lix/issues/830) [fj#805](https://git.lix.systems/lix-project/lix/issues/805) [fj#304](https://git.lix.systems/lix-project/lix/issues/304) [cl/3159](https://gerrit.lix.systems/c/lix/+/3159)
The pre-flight `echo started` handshake -- added years ago to catch race conditions -- has been removed.
After removal of connection sharing in Lix 2.93, it required a Bash-compatible shell and a standard `echo`, so it failed on:
* builders protected by `ForceCommand` wrappers (e.g. `nix-remote-build`),
* BusyBox / initrd images with no Bash,
* hosts using non-POSIX shells such as Nushell.
The race the probe once addressed was tied to SSH connection-sharing -- since connection-sharing code has already been removed, the probe is now pointless.
Real connection or protocol errors are now left to SSH/Nix to report directly.
This is technically a breaking change if you had scripts that relied on the literal "started" which needs to be updated to rely on other signals, e.g., exit codes.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
- repl-overlays now work in the debugger for flakes [fj#777](https://git.lix.systems/lix-project/lix/issues/777) [cl/3398](https://gerrit.lix.systems/c/lix/+/3398)
Due to a bug, it was previously not possible to use the debugger on flakes with repl-overlays, or with pure evaluation in general:
```
$ nix repl --pure-eval
Lix 2.94.0-dev-pre20250617-87d99da
Type :? for help.
Loading 'repl-overlays'...
error: access to absolute path '/Users/jade/.config/nix/repl.nix' is forbidden in pure eval mode (use '--impure' to override)
```
This is now fixed.
The contents of the repl-overlays file itself (i.e. most typically the top level lambda in it) will be evaluated in impure mode.
It may be necessary to use `builtins.seq` to force the impure operations to happen first if one wants to do impure operations inside a repl-overlays file in pure evaluation mode.
Many thanks to [jade](https://git.lix.systems/jade) for this.
- `nix-shell` default shell directory is not `/tmp` anymore for `$NIX_BUILD_TOP` [fj#940](https://git.lix.systems/lix-project/lix/issues/940)
Previously, Lix `nix-shell`s could exit non-zero status when `stdenv`'s `dumpVars` phase failed to write to `$NIX_BUILD_TOP/env-vars`, despite `dumpVars` being intended as a debugging aid.
This happens when `TMPDIR` is not set and defaults therefore to `/tmp`, resulting in a `/tmp/env-vars` global file that every `nix-shell` wants to write.
We fix this issue by reusing a pre-created, unique, and writable location, as the build top directory, avoiding shell exiting from write failures silently.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
- libstore/binary-cache-store: don't cache narinfo on nix copy, remove negative entry [cl/3789](https://gerrit.lix.systems/c/lix/+/3789)
When using e.g. [Snix's nar-bridge](https://snix.dev/docs/components/overview/#nar-bridge) via
an `http`-store, Lix would create cache entries with a wrong URL to the NAR when uploading
a store-path.
This caused hard build failures for Hydra.
Lix doesn't create these entries on upload anymore. Instead, it only removes negative cache entries.
The cache entry for a narinfo is now created the first time, Lix queries the cache
for the previously uploaded store-path again.
Many thanks to [ma27](https://git.lix.systems/ma27) for this.
- Lix libraries can now be linked statically [fj#789](https://git.lix.systems/lix-project/lix/issues/789) [cl/3775](https://gerrit.lix.systems/c/lix/+/3775) [cl/3778](https://gerrit.lix.systems/c/lix/+/3778)
Previously the pkg-config files distributed with Lix were only suitable for dynamic linkage, causing "undefined reference to…" linker errors when trying to link statically.
Private dependency information has now been added to make static linkage work as expected without user intervention.
In addition, relevant static libraries are now prelinked to avoid strange failures due to missing static initializers.
Many thanks to [alois31](https://git.lix.systems/alois31) for this.
- add description to zsh completions [fj#910](https://git.lix.systems/lix-project/lix/issues/910) [cl/3632](https://gerrit.lix.systems/c/lix/+/3632)
Emit descriptions when completing args in zsh completions. This uses the descriptions we already
provided in NIX\_GET\_COMPLETIONS.
Many thanks to [matthewbauer](https://github.com/matthewbauer) for this.
## Miscellany
- Deprecation of CA derivations, dynamic derivations, and impure derivations [fj#815](https://git.lix.systems/lix-project/lix/issues/815)
Content-addressed derivations are now deprecated and slated for removal in Lix 2.94.
We're doing this because the CA derivation system has been a known cause of problems
and inconsistencies, is unmaintained, habitually makes improving the store code very
difficult (or blocks such improvements outright), and is beset by a number of design
flaws that in our opinion cannot be fixed without a full reimplementation from zero.
Dynamic derivations and impure derivations are built on the CA derivation framework,
and owing to this they too are deprecated and slated for removal in another release.
+5
View File
@@ -90,6 +90,11 @@ def recursive_replace(data, book_root, search_path):
).replace(
'@docroot@',
("../" * len(path_to_chapter.parent.parts) or "./")[:-1]
).replace(
# this replacement is to avoid corrupting the
# hacking.md manual section on docroot
'@\\docroot\\@',
'@docroot@',
),
sub_items = [
recursive_replace(sub_item, book_root, search_path)
+31 -31
View File
@@ -8,7 +8,7 @@
tag ? "latest",
bundleNixpkgs ? true,
channelName ? "nixpkgs",
channelURL ? "https://nixos.org/channels/nixpkgs-unstable",
channelURL ? "https://channels.nixos.org/nixpkgs-unstable",
extraPkgs ? [ ],
maxLayers ? 100,
nixConf ? { },
@@ -62,38 +62,37 @@ let
++ autoLayered
++ extraPkgs;
users =
{
users = {
root = {
uid = 0;
shell = "${pkgs.bashInteractive}/bin/bash";
home = "/root";
gid = 0;
groups = [ "root" ];
description = "System administrator";
};
root = {
uid = 0;
shell = "${pkgs.bashInteractive}/bin/bash";
home = "/root";
gid = 0;
groups = [ "root" ];
description = "System administrator";
};
nobody = {
uid = 65534;
shell = "${pkgs.shadow}/bin/nologin";
home = "/var/empty";
gid = 65534;
groups = [ "nobody" ];
description = "Unprivileged account (don't use!)";
nobody = {
uid = 65534;
shell = "${pkgs.shadow}/bin/nologin";
home = "/var/empty";
gid = 65534;
groups = [ "nobody" ];
description = "Unprivileged account (don't use!)";
};
}
// lib.listToAttrs (
map (n: {
name = "nixbld${toString n}";
value = {
uid = 30000 + n;
gid = 30000;
groups = [ "nixbld" ];
description = "Nix build user ${toString n}";
};
}
// lib.listToAttrs (
map (n: {
name = "nixbld${toString n}";
value = {
uid = 30000 + n;
gid = 30000;
groups = [ "nixbld" ];
description = "Nix build user ${toString n}";
};
}) (lib.lists.range 1 32)
);
}) (lib.lists.range 1 32)
);
groups = {
root.gid = 0;
@@ -361,7 +360,8 @@ let
"org.opencontainers.image.version" = pkgs.nix.version;
"org.opencontainers.image.description" =
"Minimal Lix container image, with some batteries included.";
} // lib.optionalAttrs (lixRevision != null) { "org.opencontainers.image.revision" = lixRevision; };
}
// lib.optionalAttrs (lixRevision != null) { "org.opencontainers.image.revision" = lixRevision; };
};
meta = {
Generated
+3 -3
View File
@@ -108,11 +108,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1749522908,
"narHash": "sha256-eWANkhWXFL1MmaxzsZ9bhLCNT8OVs7CC+OXaSDGlA8A=",
"lastModified": 1757198069,
"narHash": "sha256-m3VUcOD4rTs8J7S+3dOjWMrAjw6RcITC3XYQ98zhEFs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e5cb99555c45a13dcc5f1317462238530b0066b7",
"rev": "0747026fc57ecb9c28901c7f7a2b5dc40e8af43c",
"type": "github"
},
"original": {
+76 -12
View File
@@ -88,7 +88,7 @@
else
"pre${
builtins.substring 0 8 (self.lastModifiedDate or self.lastModified or "19700101")
}_${self.shortRev or "dirty"}";
}-dev_${self.shortRev or "dirty"}";
linux32BitSystems = [ "i686-linux" ];
linux64BitSystems = [
@@ -100,6 +100,7 @@
"x86_64-darwin"
"aarch64-darwin"
];
nonDarwinSystems = linuxSystems;
systems = linuxSystems ++ darwinSystems;
# If you add something here, please update the list in doc/manual/src/contributing/hacking.md.
@@ -175,8 +176,13 @@
{
nixStable = prev.nix;
# Nix 2.18 has been removed from Nixpkgs ≥ 25.05, so we need to reintroduce it ourselves for our tests.
nixVersions = prev.nixVersions // {
nix_2_3 = prev.nixVersions.nix_2_3.overrideAttrs (old: {
meta = old.meta // {
knownVulnerabilities = [ ];
};
});
# Nix 2.18 has been removed from Nixpkgs ≥ 25.05, so we need to reintroduce it ourselves for our tests.
nix_2_18 = nix_2_18.outputs.packages.${currentStdenv.hostPlatform.system}.default;
};
@@ -238,6 +244,35 @@
boehmgc-nix = final.nix.passthru.boehmgc-nix;
# And same thing for our build-release-notes package.
build-release-notes = final.nix.passthru.build-release-notes;
lowdown_1_3 =
# If the stable channel we are using ships lowdown >= 1.4, we need
# to swap this around, take the default lowdown from the stable
# channel and add an overridden one for the legacy version.
assert lib.versionOlder prev.lowdown.version "1.4.0";
prev.lowdown;
lowdown = prev.lowdown.overrideAttrs (prevAttrs: rec {
version = "2.0.2";
src = final.fetchurl {
url = "https://kristaps.bsd.lv/lowdown/snapshots/lowdown-${version}.tar.gz";
sha512 = "2a4d0rqh8gkw4ca3gkzddp0hjpmmw74cbks8k0inhh0vizmgbn188zdv6m1kgmr019b99g7insli8js3ci1ji7y4n5nk704bswf3z3i";
};
nativeBuildInputs = prevAttrs.nativeBuildInputs ++ [ final.buildPackages.bmake ];
postInstall = lib.replaceStrings [ "lowdown.so.1" ] [ "lowdown.so.2" ] prevAttrs.postInstall;
});
capnproto = prev.capnproto.overrideAttrs (old: {
patches =
old.patches or [ ]
++ [
# backport of https://github.com/capnproto/capnproto/pull/1810
./misc/capnproto-promise-nodiscard.patch
]
++ lib.optionals (lib.versionOlder old.version "1.2.0") [
# backport of https://github.com/capnproto/capnproto/pull/2296
./misc/capnproto-monotonic-clocks-are-a-lie.patch
];
});
};
in
{
@@ -252,6 +287,30 @@
# Binary package for various platforms.
build = forAllSystems (system: self.packages.${system}.nix);
# Building Lix twice in CI is expensive, but we can catch a lot of static
# build regressions by at least making sure it evals and configures.
configure-static = lib.genAttrs linux64BitSystems (
system:
self.packages.${system}.nix-static.overrideAttrs {
dontBuild = true;
installPhase = ''
runHook preInstall
echo "configure-static complete. exiting with success"
mkdir -p "$out"
exit 0
'';
}
);
# Ensure support for lowdown < 1.4 doesn't regress
build-lowdown_1_3 = forAllSystems (
system:
self.packages.${system}.nix.override {
lowdown = nixpkgsFor.${system}.native.lowdown_1_3;
}
);
devShell = forAllSystems (system: {
default = self.devShells.${system}.default;
clang = self.devShells.${system}.native-clangStdenvPackages;
@@ -326,7 +385,10 @@
;
}
// {
nix-eval-jobs = forAllSystems (system: self.packages.${system}.nix-eval-jobs.tests.nix-eval-jobs);
# the n-e-j test suite is unusably slow in darwin ci. disbled until anywho fixes this.
nix-eval-jobs = (lib.genAttrs nonDarwinSystems) (
system: self.packages.${system}.nix-eval-jobs.tests.nix-eval-jobs
);
# This is x86_64-linux only, just because we have significantly
# cheaper x86_64-linux compute in CI.
@@ -398,15 +460,16 @@
in
pkgs.symlinkJoin {
name = "nixpkgs-lib-tests";
paths =
[ testWithNix ]
# NOTE: nixpkgs 25.05 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 [
((pkgs.callPackage "${nixpkgs}/ci/eval" { nixVersions.latest = nix; }).attrpathsSuperset {
evalSystem = system;
})
];
paths = [
testWithNix
]
# NOTE: nixpkgs 25.05 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 [
((pkgs.callPackage "${nixpkgs}/ci/eval" { inherit nix; }).attrpathsSuperset {
evalSystem = system;
})
];
}
);
};
@@ -455,6 +518,7 @@
# devShells and packages already get checked by nix flake check, so
# this is just jobs that are special
build-lowdown_1_3 = self.hydraJobs.build-lowdown_1_3.${system};
binaryTarball = self.hydraJobs.binaryTarball.${system};
perlBindings = self.hydraJobs.perlBindings.${system};
nix-eval-jobs = self.hydraJobs.nix-eval-jobs.${system};
+10
View File
@@ -0,0 +1,10 @@
# This reproduces the Lix Approvers plus Lix groups to yield the status quo
alois1@gmx-topmail.de
jade@lix.systems
lunaphied@lunaphied.me
maximilian@mbosch.me
me@0upti.me
pennae@lix.systems
qyriad@qyriad.me
raito@lix.systems
rbt@sent.as
+64
View File
@@ -0,0 +1,64 @@
#!@python@
import argparse
import capnp
from pathlib import Path
import os
import subprocess
import sys
if lang := os.environ.get('lix_capnp_lang'):
outputs = os.environ['lix_capnp_outputs'].split()
old_cwd = os.environ['lix_capnp_old_cwd']
schema = capnp.load('@capnp_include@/capnp/schema.capnp', imports=['@capnp_include@'])
request = schema.CodeGeneratorRequest.read(sys.stdin)
subprocess.run([lang], input=request.as_builder().to_bytes()).check_returncode()
base_dir = os.getcwd()
os.chdir(old_cwd)
include = [ str(Path(p).resolve()) for p in os.environ['lix_capnp_include'].split(':') ]
if depfile := os.environ['lix_capnp_depfile']:
deps = ""
for input in request.requestedFiles:
deps += " ".join(f"{input.filename}.{o}" for o in outputs)
deps += ":"
for dep in input.imports:
if dep.name.startswith("/"):
for candidate in (Path(i + dep.name) for i in include):
if candidate.exists():
deps += " " + str(candidate)
break
else:
raise RuntimeError("not handling relative includes")
deps += "\n\n"
Path(depfile).write_text(deps)
else:
parser = argparse.ArgumentParser()
parser.add_argument('--language')
parser.add_argument('--outdir')
parser.add_argument('--src-prefix')
parser.add_argument('--depfile', default="")
parser.add_argument('-I', '--include', action='append', default=['@capnp_include@'])
parser.add_argument('inputs', nargs='+')
args = parser.parse_args()
for infile in args.inputs:
os.environ['lix_capnp_lang'] = f"capnpc-{args.language}"
os.environ['lix_capnp_include'] = ':'.join(args.include)
os.environ['lix_capnp_depfile'] = args.depfile
os.environ['lix_capnp_old_cwd'] = os.getcwd()
if args.language == "c++":
os.environ['lix_capnp_outputs'] = "c++ h"
else:
raise RuntimeError("unknown language " + args.language)
subprocess.run([
'@capnp@',
'compile',
f'-o{sys.argv[0]}:{args.outdir}',
f'--src-prefix={args.src_prefix}',
*(f"-I{i}" for i in args.include),
infile
]).check_returncode()
+470 -216
View File
@@ -1,7 +1,22 @@
#include "lix/libstore/path.hh"
#include "lix/libutil/async.hh"
#include "lix/libutil/c-calls.hh"
#include "lix/libutil/error.hh"
#include "lix/libutil/file-descriptor.hh"
#include "lix/libutil/logging-rpc.hh"
#include "lix/libutil/logging.hh"
#include "lix/libutil/rpc.hh"
#include "lix/libutil/types-rpc.hh" // IWYU pragma: keep
#include "lix/libutil/types.hh"
#include <algorithm>
#include <chrono>
#include <capnp/rpc-twoparty.h>
#include <cstring>
#include <exception>
#include <kj/async.h>
#include <kj/time.h>
#include <set>
#include <memory>
#include <string>
#include <tuple>
#if __APPLE__
#include <sys/time.h>
@@ -17,13 +32,29 @@
#include "lix/libstore/derivations.hh"
#include "lix/libutil/strings.hh"
#include "lix/libstore/local-store.hh"
#include "lix/libstore/types-rpc.hh"
#include "lix/libcmd/legacy.hh"
#include "lix/libutil/experimental-features.hh"
#include "lix/libutil/hash.hh"
#include "build-remote.hh"
#include "lix/libstore/build/hook-instance.capnp.h"
namespace nix {
namespace {
struct Instance final : rpc::build_remote::HookInstance::Server
{
unsigned int maxBuildJobs;
bool initialized = false, used = false;
kj::Promise<void> init(InitContext context) override;
kj::Promise<void> buildImpl(BuildContext context);
kj::Promise<void> build(BuildContext context) override;
};
}
std::string escapeUri(std::string uri)
{
std::replace(uri.begin(), uri.end(), '/', '_');
@@ -53,11 +84,278 @@ static bool allSupportedLocally(Store & store, const std::set<std::string>& requ
return true;
}
static std::tuple<bool, Machine *, AutoCloseFD> selectBestMachine(
Machines & machines,
const std::string & neededSystem,
const std::set<std::string> & requiredFeatures
)
{
bool rightType = false;
Machine * bestMachine = nullptr;
AutoCloseFD bestSlotLock;
uint64_t bestLoad = 0;
for (auto & m : machines) {
debug("considering building on remote machine '%s'", m.storeUri);
if (m.enabled && m.systemSupported(neededSystem) && m.allSupported(requiredFeatures)
&& m.mandatoryMet(requiredFeatures))
{
rightType = true;
AutoCloseFD free;
uint64_t load = 0;
for (uint64_t slot = 0; slot < m.maxJobs; ++slot) {
auto slotLock = openSlotLock(m, slot);
if (tryLockFile(slotLock.get(), ltWrite)) {
if (!free) {
free = std::move(slotLock);
}
} else {
++load;
}
}
if (!free) {
continue;
}
bool best = false;
if (!bestSlotLock) {
best = true;
} else if (load / m.speedFactor < bestLoad / bestMachine->speedFactor) {
best = true;
} else if (load / m.speedFactor == bestLoad / bestMachine->speedFactor) {
if (m.speedFactor > bestMachine->speedFactor) {
best = true;
} else if (m.speedFactor == bestMachine->speedFactor) {
if (load < bestLoad) {
best = true;
}
}
}
if (best) {
bestLoad = load;
bestSlotLock = std::move(free);
bestMachine = &m;
}
}
}
return {rightType, bestMachine, std::move(bestSlotLock)};
}
static void printSelectionFailureMessage(
Verbosity level,
const std::string_view drvstr,
const Machines & machines,
const std::string & neededSystem,
const std::set<std::string> & requiredFeatures
)
{
std::string machinesFormatted;
for (auto & m : machines) {
machinesFormatted += HintFmt(
"\n([%s], %s, [%s], [%s])",
concatStringsSep<StringSet>(", ", m.systemTypes),
m.maxJobs,
concatStringsSep<StringSet>(", ", m.supportedFeatures),
concatStringsSep<StringSet>(", ", m.mandatoryFeatures)
)
.str();
}
printMsg(
level,
"Failed to find a machine for remote build!\n"
"derivation: %s\n"
"required (system, features): (%s, [%s])\n"
"%s available machines:\n"
"(systems, maxjobs, supportedFeatures, mandatoryFeatures)%s",
drvstr,
neededSystem,
concatStringsSep<StringSet>(", ", requiredFeatures),
machines.size(),
Uncolored(machinesFormatted)
);
}
namespace {
struct BuilderConnection
{
AutoCloseFD slotLock;
std::shared_ptr<Store> sshStore;
std::string storeUri;
Pipe logPipe;
// start the thread that reads ssh stderr and turns it into log items.
// this future *must* outlive sshStore, otherwise it will never finish
kj::Promise<Result<void>> startLogThread(std::string buildDescription, std::string drvPath)
try {
if (!logPipe.readSide) {
co_return result::success();
}
logPipe.writeSide.close();
// NOTE this is very similar to handleBuilderOutput in DerivationGoal, but unlike
// the derivation goal we do not need to handle EIO from a pty here. we also have
// no timeouts or limits to keep track of, which makes deduplication less useful.
auto act = logger->startActivity(
lvlInfo, actBuild, buildDescription, Logger::Fields{drvPath, storeUri, 1, 1}
);
std::map<ActivityId, Activity> activities;
auto reader = AIO().lowLevelProvider.wrapInputFd(logPipe.readSide.get());
LogLineSplitter splitter;
auto flushLine = [&](const std::string & line) {
if (const auto state =
handleJSONLogMessage(line, act, activities, "the derivation builder"))
{
return *state;
} else {
return act.result(resBuildLogLine, line);
}
};
auto buf = kj::heapArray<char>(4096);
while (true) {
const auto got = co_await reader->tryRead(buf.begin(), 1, buf.size());
if (got == 0) {
break;
}
std::string_view data{buf.begin(), got};
while (!data.empty()) {
if (auto line = splitter.feed(data)) {
if (flushLine(*line) == Logger::BufferState::NeedsFlush) {
TRY_AWAIT(act.getLogger().flush());
}
}
}
}
if (auto line = splitter.finish(); !line.empty()) {
(void) flushLine(line);
TRY_AWAIT(act.getLogger().flush());
}
co_return result::success();
} catch (...) {
co_return result::current_exception();
}
};
struct AcceptedBuild final : rpc::build_remote::HookInstance::AcceptedBuild::Server
{
ref<Store> store;
StorePath drvPath;
BuilderConnection builder;
bool used = false;
AcceptedBuild(ref<Store> store, StorePath drvPath, BuilderConnection builder)
: store(store)
, drvPath(drvPath)
, builder(std::move(builder))
{
}
kj::Promise<void> runImpl(RunContext context);
kj::Promise<void> run(RunContext context) override;
};
enum class BuildRejected { Temporarily, Permanently };
}
static kj::Promise<Result<std::variant<BuildRejected, BuilderConnection>>> connectToBuilder(
const ref<Store> & store,
const StorePath & drvPath,
Machines & machines,
const unsigned int maxBuildJobs,
const bool amWilling,
const std::string & neededSystem,
const std::set<std::string> & requiredFeatures
)
try {
AutoCloseFD bestSlotLock;
/* It would be possible to build locally after some builds clear out,
so don't show the warning now: */
bool couldBuildLocally = maxBuildJobs > 0
&& (neededSystem == settings.thisSystem
|| settings.extraPlatforms.get().count(neededSystem) > 0)
&& allSupportedLocally(*store, requiredFeatures);
/* It's possible to build this locally right now: */
bool canBuildLocally = amWilling && couldBuildLocally;
/* Error ignored here, will be caught later */
(void) sys::mkdir(currentLoad, 0777);
while (true) {
bestSlotLock.reset();
AutoCloseFD lock = openLockFile(currentLoad + "/main-lock", true);
TRY_AWAIT(lockFileAsync(lock.get(), ltWrite));
auto [rightType, bestMachine, slotLock] =
selectBestMachine(machines, neededSystem, requiredFeatures);
bestSlotLock = std::move(slotLock);
if (!bestSlotLock) {
if (rightType && !canBuildLocally) {
co_return BuildRejected::Temporarily;
} else {
printSelectionFailureMessage(
couldBuildLocally ? lvlChatty : lvlWarn,
drvPath.to_string(),
machines,
neededSystem,
requiredFeatures
);
co_return BuildRejected::Permanently;
}
}
#if __APPLE__
futimes(bestSlotLock.get(), nullptr);
#else
futimens(bestSlotLock.get(), nullptr);
#endif
lock.reset();
std::shared_ptr<Store> sshStore;
Pipe logPipe;
try {
auto act = logger->startActivity(
lvlTalkative, actUnknown, fmt("connecting to '%s'", bestMachine->storeUri)
);
std::tie(sshStore, logPipe) = TRY_AWAIT(bestMachine->openStore());
TRY_AWAIT(sshStore->connect());
co_return BuilderConnection{
std::move(bestSlotLock), sshStore, bestMachine->storeUri, std::move(logPipe)
};
} catch (std::exception & e) { // NOLINT(lix-foreign-exceptions)
std::string msg = logPipe.readSide ? chomp(drainFD(logPipe.readSide.get(), false)) : "";
printError(
"cannot build on '%s': %s%s",
bestMachine->storeUri,
e.what(),
msg.empty() ? "" : ": " + msg
);
bestMachine->enabled = false;
}
}
} catch (...) {
co_return result::current_exception();
}
static int main_build_remote(AsyncIoRoot & aio, std::string programName, Strings argv)
{
{
logger = makeJSONLogger(*logger);
/* Ensure we don't get any SSH passphrase or host key popups. */
unsetenv("DISPLAY");
unsetenv("SSH_ASKPASS");
@@ -70,240 +368,181 @@ static int main_build_remote(AsyncIoRoot & aio, std::string programName, Strings
verbosity = (Verbosity) std::stoll(argv.front());
FdSource source(STDIN_FILENO);
auto conn = aio.kj.lowLevelProvider->wrapUnixSocketFd(1);
capnp::TwoPartyServer srv(kj::heap<Instance>());
srv.accept(*conn, 1).wait(aio.kj.waitScope);
return 0;
}
}
kj::Promise<void> Instance::init(InitContext context)
{
try {
if (initialized) {
throw Error("build hook can only be initialized once");
}
logger = rpc::log::makeRpcLoggerClient(context.getParams().getLogger());
/* Read the parent's settings. */
while (readInt(source)) {
auto name = readString(source);
auto value = readString(source);
for (const auto & [name, value] : rpc::to<StringMap>(context.getParams().getSettings())) {
settings.set(name, value);
}
auto maxBuildJobs = settings.maxBuildJobs;
maxBuildJobs = settings.maxBuildJobs;
settings.maxBuildJobs.set("1"); // hack to make tests with local?root= work
initPlugins();
// FIXME this does not open a daemon connection for historical reasons.
// we may create a lot of build hook instances, and having each of them
// also create a daemon instance is inefficient and wasteful. in future
// versions of the build hook (where we don't need one hook process per
// build) we should change this to using a daemon connection, ideally a
// daemon connection provided by the parent via file descriptor passing
auto store = aio.blockOn(openStore(settings.storeUri, {}, AllowDaemon::Disallow));
initialized = true;
/* It would be more appropriate to use $XDG_RUNTIME_DIR, since
that gets cleared on reboot, but it wouldn't work on macOS. */
auto currentLoadName = "/current-load";
if (auto localStore = store.try_cast_shared<LocalFSStore>())
currentLoad = std::string { localStore->config().stateDir } + currentLoadName;
else
currentLoad = settings.nixStateDir + currentLoadName;
context.getResults().initResult().setGood();
} catch (...) {
RPC_FILL(context.getResults(), initResult, std::current_exception());
}
std::shared_ptr<Store> sshStore;
AutoCloseFD bestSlotLock;
return kj::READY_NOW;
}
auto machines = getMachines();
debug("got %d remote builders", machines.size());
kj::Promise<void> Instance::buildImpl(BuildContext context)
{
if (!initialized) {
throw Error("build hook not fully initialized");
}
if (machines.empty()) {
std::cerr << "# decline-permanently\n";
return 0;
// FIXME this does not open a daemon connection for historical reasons.
// we may create a lot of build hook instances, and having each of them
// also create a daemon instance is inefficient and wasteful. in future
// versions of the build hook (where we don't need one hook process per
// build) we should change this to using a daemon connection, ideally a
// daemon connection provided by the parent via file descriptor passing
auto store = TRY_AWAIT(openStore(settings.storeUri, {}, AllowDaemon::Disallow));
/* It would be more appropriate to use $XDG_RUNTIME_DIR, since
that gets cleared on reboot, but it wouldn't work on macOS. */
auto currentLoadName = "/current-load";
if (auto localStore = store.try_cast_shared<LocalFSStore>()) {
currentLoad = std::string{localStore->config().stateDir} + currentLoadName;
} else {
currentLoad = settings.nixStateDir + currentLoadName;
}
auto machines = getMachines();
debug("got %d remote builders", machines.size());
if (machines.empty()) {
context.getResults().initResult().initGood().setDeclinePermanently();
co_return;
}
auto amWilling = context.getParams().getAmWilling();
auto neededSystem = rpc::to<std::string>(context.getParams().getNeededSystem());
auto drvPath = from(context.getParams().getDrvPath(), *store);
auto requiredFeatures =
rpc::to<std::set<std::string>>(context.getParams().getRequiredFeatures());
auto result = TRY_AWAIT(connectToBuilder(
store, drvPath, machines, maxBuildJobs, amWilling, neededSystem, requiredFeatures
));
if (auto immediateResponse = std::get_if<BuildRejected>(&result)) {
switch (*immediateResponse) {
case BuildRejected::Temporarily:
context.getResults().initResult().initGood().setPostpone();
co_return;
case BuildRejected::Permanently:
context.getResults().initResult().initGood().setDecline();
co_return;
}
}
std::optional<StorePath> drvPath;
std::string storeUri;
auto builder = std::get_if<BuilderConnection>(&result);
assert(builder);
while (true) {
auto ac = context.getResults().initResult().initGood().initAccept();
ac.setMachine(kj::heap<AcceptedBuild>(store, drvPath, std::move(*builder)));
}
try {
auto s = readString(source);
if (s != "try") return 0;
} catch (EndOfFile &) { return 0; }
kj::Promise<void> Instance::build(BuildContext context)
try {
if (used) {
throw Error("build hooks can only accept a single job");
}
used = true; // lock out other rpc calls during processing
co_await buildImpl(context);
TRY_AWAIT(logger->flush());
used = context.getResults().getResult().getGood().isAccept();
} catch (...) {
RPC_FILL(context.getResults(), getResult, std::current_exception());
}
auto amWilling = readInt(source);
auto neededSystem = readString(source);
drvPath = store->parseStorePath(readString(source));
auto requiredFeatures = readStrings<std::set<std::string>>(source);
kj::Promise<void> AcceptedBuild::run(RunContext context)
{
try {
auto oldLogger = logger;
logger = rpc::log::makeRpcLoggerClient(context.getParams().getLogger());
TRY_AWAIT(oldLogger->flush());
KJ_DEFER({
delete logger;
logger = oldLogger;
});
/* It would be possible to build locally after some builds clear out,
so don't show the warning now: */
bool couldBuildLocally = maxBuildJobs > 0
&& ( neededSystem == settings.thisSystem
|| settings.extraPlatforms.get().count(neededSystem) > 0)
&& allSupportedLocally(*store, requiredFeatures);
/* It's possible to build this locally right now: */
bool canBuildLocally = amWilling && couldBuildLocally;
/* Error ignored here, will be caught later */
mkdir(currentLoad.c_str(), 0777);
while (true) {
bestSlotLock.reset();
AutoCloseFD lock = openLockFile(currentLoad + "/main-lock", true);
lockFile(lock.get(), ltWrite);
bool rightType = false;
Machine * bestMachine = nullptr;
uint64_t bestLoad = 0;
for (auto & m : machines) {
debug("considering building on remote machine '%s'", m.storeUri);
if (m.enabled &&
m.systemSupported(neededSystem) &&
m.allSupported(requiredFeatures) &&
m.mandatoryMet(requiredFeatures))
{
rightType = true;
AutoCloseFD free;
uint64_t load = 0;
for (uint64_t slot = 0; slot < m.maxJobs; ++slot) {
auto slotLock = openSlotLock(m, slot);
if (tryLockFile(slotLock.get(), ltWrite)) {
if (!free) {
free = std::move(slotLock);
}
} else {
++load;
}
}
if (!free) {
continue;
}
bool best = false;
if (!bestSlotLock) {
best = true;
} else if (load / m.speedFactor < bestLoad / bestMachine->speedFactor) {
best = true;
} else if (load / m.speedFactor == bestLoad / bestMachine->speedFactor) {
if (m.speedFactor > bestMachine->speedFactor) {
best = true;
} else if (m.speedFactor == bestMachine->speedFactor) {
if (load < bestLoad) {
best = true;
}
}
}
if (best) {
bestLoad = load;
bestSlotLock = std::move(free);
bestMachine = &m;
}
}
}
if (!bestSlotLock) {
if (rightType && !canBuildLocally)
std::cerr << "# postpone\n";
else
{
// add the template values.
std::string drvstr;
if (drvPath.has_value())
drvstr = drvPath->to_string();
else
drvstr = "<unknown>";
std::string machinesFormatted;
for (auto & m : machines) {
machinesFormatted += HintFmt(
"\n([%s], %s, [%s], [%s])",
concatStringsSep<StringSet>(", ", m.systemTypes),
m.maxJobs,
concatStringsSep<StringSet>(", ", m.supportedFeatures),
concatStringsSep<StringSet>(", ", m.mandatoryFeatures)
).str();
}
auto error = HintFmt(
"Failed to find a machine for remote build!\n"
"derivation: %s\n"
"required (system, features): (%s, [%s])\n"
"%s available machines:\n"
"(systems, maxjobs, supportedFeatures, mandatoryFeatures)%s",
drvstr,
neededSystem,
concatStringsSep<StringSet>(", ", requiredFeatures),
machines.size(),
Uncolored(machinesFormatted)
);
printMsg(couldBuildLocally ? lvlChatty : lvlWarn, error.str());
std::cerr << "# decline\n";
}
break;
}
#if __APPLE__
futimes(bestSlotLock.get(), nullptr);
#else
futimens(bestSlotLock.get(), nullptr);
#endif
lock.reset();
try {
Activity act(*logger, lvlTalkative, actUnknown, fmt("connecting to '%s'", bestMachine->storeUri));
sshStore = aio.blockOn(bestMachine->openStore());
aio.blockOn(sshStore->connect());
storeUri = bestMachine->storeUri;
} catch (std::exception & e) { // NOLINT(lix-foreign-exceptions)
auto msg = chomp(drainFD(5, false));
printError("cannot build on '%s': %s%s",
bestMachine->storeUri, e.what(),
msg.empty() ? "" : ": " + msg);
bestMachine->enabled = false;
continue;
}
goto connected;
}
if (used) {
throw Error("build hooks builds are single-use items");
}
used = true;
co_await runImpl(context);
TRY_AWAIT(logger->flush());
} catch (...) {
RPC_FILL(context.getResults(), getResult, std::current_exception());
}
}
connected:
close(5);
kj::Promise<void> AcceptedBuild::runImpl(RunContext context)
{
try {
auto logHandler = builder.startLogThread(
fmt("%s on '%s'",
rpc::to<std::string_view>(context.getParams().getDescription()),
builder.storeUri),
store->printStorePath(drvPath)
);
assert(sshStore);
auto & sshStore = builder.sshStore;
auto & storeUri = builder.storeUri;
std::cerr << "# accept\n" << storeUri << "\n";
auto inputs = readStrings<PathSet>(source);
auto wantedOutputs = readStrings<StringSet>(source);
auto inputs = rpc::to<std::set<StorePath>>(context.getParams().getInputs(), *store);
auto wantedOutputs = rpc::to<std::set<std::string>>(context.getParams().getWantedOutputs());
auto lockFileName = currentLoad + "/" + makeLockFilename(storeUri) + ".upload-lock";
AutoCloseFD uploadLock = openLockFile(lockFileName, true);
{
Activity act(*logger, lvlTalkative, actUnknown, fmt("waiting for the upload lock to '%s'", storeUri));
auto act = logger->startActivity(
lvlTalkative, actUnknown, fmt("waiting for the upload lock to '%s'", storeUri)
);
if (!unsafeLockFileSingleThreaded(uploadLock.get(), ltWrite, std::chrono::minutes(15)))
auto result = TRY_AWAIT(
AIO().timeoutAfter(15 * kj::MINUTES, lockFileAsync(uploadLock.get(), ltWrite))
);
if (!result) {
printError("somebody is hogging the upload lock for '%s', continuing...");
}
}
auto substitute = settings.buildersUseSubstitutes ? Substitute : NoSubstitute;
{
Activity act(*logger, lvlTalkative, actUnknown, fmt("copying dependencies to '%s'", storeUri));
aio.blockOn(copyPaths(
*store,
*sshStore,
store->parseStorePathSet(inputs),
NoRepair,
NoCheckSigs,
substitute
));
auto act = logger->startActivity(
lvlTalkative, actUnknown, fmt("copying dependencies to '%s'", storeUri)
);
TRY_AWAIT(copyPaths(*store, *sshStore, inputs, NoRepair, NoCheckSigs, substitute));
}
uploadLock.reset();
auto drv = aio.blockOn(store->readDerivation(*drvPath));
auto drv = TRY_AWAIT(store->readDerivation(drvPath));
std::optional<BuildResult> optResult;
@@ -311,7 +550,7 @@ connected:
// stores), we assume we are. This is necessary for backwards
// compat.
bool trustedOrLegacy = ({
std::optional trusted = aio.blockOn(sshStore->isTrustedClient());
std::optional trusted = TRY_AWAIT(sshStore->isTrustedClient());
!trusted || *trusted;
});
@@ -330,46 +569,61 @@ connected:
//
// 2. Changing the `inputSrcs` set changes the associated
// output ids, which break CA derivations
if (!drv.inputDrvs.empty())
drv.inputSrcs = store->parseStorePathSet(inputs);
optResult = aio.blockOn(sshStore->buildDerivation(*drvPath, (const BasicDerivation &) drv));
auto & result = *optResult;
if (!result.success())
throw Error("build of '%s' on '%s' failed: %s", store->printStorePath(*drvPath), storeUri, result.errorMsg);
if (!drv.inputDrvs.empty()) {
drv.inputSrcs = inputs;
}
optResult =
TRY_AWAIT(sshStore->buildDerivation(drvPath, (const BasicDerivation &) drv));
} else {
aio.blockOn(copyClosure(
*store, *sshStore, StorePathSet{*drvPath}, NoRepair, NoCheckSigs, substitute
TRY_AWAIT(copyClosure(
*store, *sshStore, StorePathSet{drvPath}, NoRepair, NoCheckSigs, substitute
));
auto res = aio.blockOn(sshStore->buildPathsWithResults({
DerivedPath::Built {
.drvPath = makeConstantStorePath(*drvPath),
.outputs = OutputsSpec::All {},
}
}));
auto res = TRY_AWAIT(sshStore->buildPathsWithResults({DerivedPath::Built{
.drvPath = makeConstantStorePath(drvPath),
.outputs = OutputsSpec::All{},
}}));
// One path to build should produce exactly one build result
assert(res.size() == 1);
optResult = std::move(res[0]);
}
auto & result = *optResult;
if (!result.success()) {
throw Error(
"build of '%s' on '%s' failed: %s",
store->printStorePath(drvPath),
storeUri,
result.errorMsg
);
}
StorePathSet missingPaths;
auto outputPaths = drv.outputsAndPaths(*store);
for (auto & [outputName, outputPath] : outputPaths) {
if (!aio.blockOn(store->isValidPath(outputPath.second)))
if (!TRY_AWAIT(store->isValidPath(outputPath.second))) {
missingPaths.insert(outputPath.second);
}
}
if (!missingPaths.empty()) {
Activity act(*logger, lvlTalkative, actUnknown, fmt("copying outputs from '%s'", storeUri));
auto act = logger->startActivity(
lvlTalkative, actUnknown, fmt("copying outputs from '%s'", storeUri)
);
if (auto localStore = store.try_cast_shared<LocalStore>())
for (auto & path : missingPaths)
localStore->locksHeld.insert(store->printStorePath(path)); /* FIXME: ugly */
aio.blockOn(
TRY_AWAIT(
copyPaths(*sshStore, *store, missingPaths, NoRepair, NoCheckSigs, NoSubstitute)
);
}
return 0;
// drop store connection, let log handler process any remaining input
builder.sshStore = nullptr;
TRY_AWAIT(logHandler);
context.getResults().initResult().setGood();
} catch (...) {
RPC_FILL(context.getResults(), initResult, std::current_exception());
}
}
+63 -53
View File
@@ -9,6 +9,7 @@
#include "lix/libstore/store-api.hh"
#include "lix/libstore/local-fs-store.hh"
#include "lix/libstore/globals.hh"
#include "lix/libutil/c-calls.hh"
#include "lix/libutil/current-process.hh"
#include "lix/libstore/derivations.hh"
#include "lix/libmain/shared.hh"
@@ -18,6 +19,8 @@
#include "lix/libcmd/common-eval-args.hh"
#include "lix/libexpr/attr-path.hh"
#include "lix/libcmd/legacy.hh"
#include "lix/libutil/finally.hh"
#include "lix/libutil/processes.hh"
#include "lix/libutil/regex.hh"
#include "lix/libutil/shlex.hh"
#include "nix-build.hh"
@@ -29,7 +32,7 @@ namespace nix {
using namespace std::string_literals;
static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings argv)
static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings argv)
{
auto dryRun = false;
auto runEnv = std::regex_search(programName, regex::parse("nix-shell$"));
@@ -187,7 +190,8 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
if (packages && fromArgs)
throw UsageError("'-p' and '-E' are mutually exclusive");
AutoDelete tmpDir(createTempDir("", myName));
AutoDelete tmpDir(createTempDir(myName));
AutoDelete buildTopTmpDir(createTempSubdir(tmpDir, "build-top"));
if (outLink.empty())
outLink = (Path) tmpDir + "/result";
@@ -224,8 +228,9 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
left = {"default.nix"};
}
if (runEnv)
setenv("IN_NIX_SHELL", pure ? "pure" : "impure", 1);
if (runEnv) {
(void) sys::setenv("IN_NIX_SHELL", pure ? "pure" : "impure", 1);
}
DrvInfos drvs;
@@ -272,7 +277,7 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
}
bool add = false;
if (v.type() == nFunction) {
if (auto pattern = dynamic_cast<AttrsPattern *>(v.lambda.fun->pattern.get())) {
if (auto pattern = dynamic_cast<AttrsPattern *>(v.lambda().fun->pattern.get())) {
for (auto & i : pattern->formals) {
if (evaluator->symbols[i.name] == "inNixShell") {
add = true;
@@ -285,12 +290,12 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
};
for (auto & i : attrPaths) {
Value & v(*findAlongAttrPath(
*state,
i,
takesNixShellAttr(vRoot) ? *autoArgsWithInNixShell : *autoArgs,
vRoot
).first);
Value v(
findAlongAttrPath(
*state, i, takesNixShellAttr(vRoot) ? *autoArgsWithInNixShell : *autoArgs, vRoot
)
.first
);
state->forceValue(v, noPos);
getDerivations(
*state,
@@ -397,7 +402,9 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
buildPaths(pathsToBuild);
if (dryRun) return;
if (dryRun) {
return 0;
}
if (shellDrv) {
auto shellDrvOutputs =
@@ -419,24 +426,24 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
}
// Don't use defaultTempDir() here! We want to preserve the user's TMPDIR for the shell
env["NIX_BUILD_TOP"] = env["TMPDIR"] = env["TEMPDIR"] = env["TMP"] = env["TEMP"] = getEnvNonEmpty("TMPDIR").value_or("/tmp");
env["NIX_BUILD_TOP"] = env["TMPDIR"] = env["TEMPDIR"] = env["TMP"] = env["TEMP"] =
getEnvNonEmpty("TMPDIR").value_or(buildTopTmpDir);
env["NIX_STORE"] = store->config().storeDir;
env["NIX_BUILD_CORES"] = std::to_string(settings.buildCores);
auto passAsFile = tokenizeString<StringSet>(getOr(drv.env, "passAsFile", ""));
bool keepTmp = false;
int fileNr = 0;
for (auto & var : drv.env)
if (passAsFile.count(var.first)) {
keepTmp = true;
auto fn = ".attr-" + std::to_string(fileNr++);
Path p = (Path) tmpDir + "/" + fn;
writeFile(p, var.second);
env[var.first + "Path"] = p;
} else
} else {
env[var.first] = var.second;
}
std::string structuredAttrsRC;
@@ -469,7 +476,6 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
env["NIX_ATTRS_SH_FILE"] = attrsSH;
env["NIX_ATTRS_JSON_FILE"] = attrsJSON;
keepTmp = true;
}
}
@@ -479,24 +485,13 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
lose the current $PATH directories. */
auto rcfile = (Path) tmpDir + "/rc";
auto tz = getEnv("TZ");
std::string rc = fmt(
R"(_nix_shell_clean_tmpdir() { command rm -rf %1%; }; )"
"%2%"
"%3%"
// always clear PATH.
// when nix-shell is run impure, we rehydrate it with the `p=$PATH` above
"unset PATH;"
"dontAddDisableDepTrack=1;\n",
shellEscape(tmpDir),
(keepTmp
? "trap _nix_shell_clean_tmpdir EXIT; "
"exitHooks+=(_nix_shell_clean_tmpdir); "
"failureHooks+=(_nix_shell_clean_tmpdir); "
: "_nix_shell_clean_tmpdir; "),
(pure
? ""
: "[ -n \"$PS1\" ] && [ -e ~/.bashrc ] && source ~/.bashrc; p=$PATH; ")
);
std::string rc =
fmt("%1%"
// always clear PATH.
// when nix-shell is run impure, we rehydrate it with the `p=$PATH` above
"unset PATH;"
"dontAddDisableDepTrack=1;\n",
(pure ? "" : "[ -n \"$PS1\" ] && [ -e ~/.bashrc ] && source ~/.bashrc; p=$PATH; "));
rc += structuredAttrsRC;
rc += fmt(
"\n[ -e $stdenv/setup ] && source $stdenv/setup; "
@@ -526,27 +521,38 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
vomit("Sourcing nix-shell with file %s and contents:\n%s", rcfile, rc);
writeFile(rcfile, rc);
Strings envStrs;
for (auto & i : env)
envStrs.push_back(i.first + "=" + i.second);
auto args = interactive
? Strings{"bash", "--rcfile", rcfile}
: Strings{"bash", rcfile};
auto envPtrs = stringsToCharPtrs(envStrs);
environ = envPtrs.data();
auto argPtrs = stringsToCharPtrs(args);
restoreProcessContext();
auto args = interactive ? Strings{"--rcfile", rcfile} : Strings{rcfile};
// We are going to run an interactive command, do not let the logger send a line.
logger->pause();
execvp(shell->c_str(), argPtrs.data());
printMsg(lvlChatty, "running shell: %s", concatMapStringsSep(" ", args, shellEscape));
throw SysError("executing shell '%s'", *shell);
RunningProgram proc = runProgram2(
{.program = *shell,
.searchPath = true,
.args = args,
.environment = env,
.dieWithParent = true}
);
// NOTE: we wait and return the status check immediately.
// If there's interruption, we will swallow it and wait again for termination.
auto toExitStatus = [](int waitRes) {
if (WIFEXITED(waitRes)) {
return WEXITSTATUS(waitRes);
} else if (WIFSIGNALED(waitRes)) {
return 128 + WTERMSIG(waitRes);
} else {
return 255;
}
};
try {
return toExitStatus(proc.wait());
} catch (Interrupted &) {
return toExitStatus(proc.wait());
}
}
else {
@@ -580,7 +586,9 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
buildPaths(pathsToBuild);
if (dryRun) return;
if (dryRun) {
return 0;
}
std::vector<StorePath> outPaths;
@@ -609,6 +617,8 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
for (auto & path : outPaths)
std::cout << store->printStorePath(path) << '\n';
}
return 0;
}
void registerLegacyNixBuildAndNixShell() {
+30 -12
View File
@@ -8,7 +8,9 @@
#include "lix/libexpr/eval-settings.hh" // for defexpr
#include "lix/libstore/temporary-dir.hh"
#include "lix/libutil/async.hh"
#include "lix/libutil/c-calls.hh"
#include "lix/libutil/regex.hh"
#include "lix/libutil/result.hh"
#include "lix/libutil/users.hh"
#include "nix-channel.hh"
@@ -43,7 +45,7 @@ static void readChannels()
// Writes the list of channels.
static void writeChannels()
{
auto channelsFD = AutoCloseFD{open(channelsList.c_str(), O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, 0644)};
auto channelsFD = sys::open(channelsList, O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, 0644);
if (!channelsFD)
throw SysError("opening '%1%' for writing", channelsList);
for (const auto & channel : channels)
@@ -65,13 +67,18 @@ static void addChannel(const std::string & url, const std::string & name)
static Path profile;
// Remove a channel.
static void removeChannel(const std::string & name)
{
static kj::Promise<Result<void>> removeChannel(const std::string & name)
try {
readChannels();
channels.erase(name);
writeChannels();
runProgram(settings.nixBinDir + "/nix-env", true, { "--profile", profile, "--uninstall", name });
TRY_AWAIT(runProgram(
settings.nixBinDir + "/nix-env", true, {"--profile", profile, "--uninstall", name}
));
co_return result::success();
} catch (...) {
co_return result::current_exception();
}
static Path nixDefExpr;
@@ -127,8 +134,14 @@ static void update(AsyncIoRoot & aio, const StringSet & channelNames)
bool unpacked = false;
if (std::regex_search(filename, regex::parse("\\.tar\\.(gz|bz2|xz)$"))) {
runProgram(settings.nixBinDir + "/nix-build", false, { "--no-out-link", "--expr", "import " + unpackChannelPath +
"{ name = \"" + cname + "\"; channelName = \"" + name + "\"; src = builtins.storePath \"" + filename + "\"; }" });
aio.blockOn(runProgram(
settings.nixBinDir + "/nix-build",
false,
{"--no-out-link",
"--expr",
"import " + unpackChannelPath + "{ name = \"" + cname + "\"; channelName = \""
+ name + "\"; src = builtins.storePath \"" + filename + "\"; }"}
));
unpacked = true;
}
@@ -158,15 +171,16 @@ static void update(AsyncIoRoot & aio, const StringSet & channelNames)
for (auto & expr : exprs)
envArgs.push_back(std::move(expr));
envArgs.push_back("--quiet");
runProgram(settings.nixBinDir + "/nix-env", false, envArgs);
aio.blockOn(runProgram(settings.nixBinDir + "/nix-env", false, envArgs));
// Make the channels appear in nix-env.
struct stat st;
if (lstat(nixDefExpr.c_str(), &st) == 0) {
if (sys::lstat(nixDefExpr, &st) == 0) {
if (S_ISLNK(st.st_mode))
// old-skool ~/.nix-defexpr
if (unlink(nixDefExpr.c_str()) == -1)
if (sys::unlink(nixDefExpr) == -1) {
throw SysError("unlinking %1%", nixDefExpr);
}
} else if (errno != ENOENT) {
throw SysError("getting status of %1%", nixDefExpr);
}
@@ -244,7 +258,7 @@ static int main_nix_channel(AsyncIoRoot & aio, std::string programName, Strings
case cRemove:
if (args.size() != 1)
throw UsageError("'--remove' requires one argument");
removeChannel(args[0]);
aio.blockOn(removeChannel(args[0]));
break;
case cList:
if (!args.empty())
@@ -259,7 +273,11 @@ static int main_nix_channel(AsyncIoRoot & aio, std::string programName, Strings
case cListGenerations:
if (!args.empty())
throw UsageError("'--list-generations' expects no arguments");
std::cout << runProgram(settings.nixBinDir + "/nix-env", false, {"--profile", profile, "--list-generations"}) << std::flush;
std::cout << aio.blockOn(runProgram(
settings.nixBinDir + "/nix-env",
false,
{"--profile", profile, "--list-generations"}
)) << std::flush;
break;
case cRollback:
if (args.size() > 1)
@@ -271,7 +289,7 @@ static int main_nix_channel(AsyncIoRoot & aio, std::string programName, Strings
} else {
envArgs.push_back("--rollback");
}
runProgram(settings.nixBinDir + "/nix-env", false, envArgs);
aio.blockOn(runProgram(settings.nixBinDir + "/nix-env", false, envArgs));
break;
}
+6 -3
View File
@@ -1,3 +1,4 @@
#include "lix/libutil/c-calls.hh"
#include "lix/libutil/file-system.hh"
#include "lix/libstore/store-api.hh"
#include "lix/libstore/store-cast.hh"
@@ -24,9 +25,11 @@ bool dryRun = false;
static void removeOldGenerations(std::string dir, NeverAsync = {})
{
if (access(dir.c_str(), R_OK) != 0) return;
if (sys::access(dir, R_OK) != 0) {
return;
}
bool canWrite = access(dir.c_str(), W_OK) == 0;
bool canWrite = sys::access(dir, W_OK) == 0;
for (auto & i : readDirectory(dir)) {
checkInterrupt();
@@ -103,7 +106,7 @@ static int main_nix_collect_garbage(AsyncIoRoot & aio, std::string programName,
if (dryRun) {
// Only print results for dry run; when !dryRun, paths will be printed as they're deleted.
for (auto & i : results.paths) {
printInfo("%s", i);
printInfo("%s", Uncolored(i));
}
}
+1 -1
View File
@@ -23,7 +23,7 @@ static int main_nix_copy_closure(AsyncIoRoot & aio, std::string programName, Str
printVersion("nix-copy-closure");
else if (*arg == "--gzip" || *arg == "--bzip2" || *arg == "--xz") {
if (*arg != "--gzip")
warn("'%1%' is not implemented, falling back to gzip", *arg);
printTaggedWarning("'%1%' is not implemented, falling back to gzip", *arg);
gzip = true;
} else if (*arg == "--from")
toMode = false;
+36 -26
View File
@@ -1,6 +1,7 @@
#include "lix/libcmd/cmd-profiles.hh"
#include "lix/libexpr/attr-path.hh"
#include "lix/libcmd/common-eval-args.hh"
#include "lix/libexpr/value.hh"
#include "lix/libstore/derivations.hh"
#include "lix/libutil/terminal.hh"
#include "lix/libexpr/eval.hh"
@@ -150,11 +151,12 @@ static void getAllExprs(Evaluator & state,
continue;
}
/* Load the expression on demand. */
auto vArg = state.mem.allocValue();
vArg->mkString(path2.canonical().abs());
Value vArg;
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);
attrs.alloc(attrName
) = {NewValueAs::app, state.mem, state.builtins.get("import"), vArg};
}
else if (st.type == InputAccessor::tDirectory)
/* `path2' is a directory (with no default.nix in it);
@@ -181,7 +183,7 @@ static void loadSourceExpr(EvalState & state, const SourcePath & path_, Value &
directory). */
else if (st.type == InputAccessor::tDirectory) {
auto attrs = state.ctx.buildBindings(maxAttrs);
attrs.alloc("_combineChannels").mkList(0);
attrs.alloc("_combineChannels") = Value::EMPTY_LIST;
StringSet seen;
getAllExprs(state.ctx, path, seen, attrs);
v.mkAttrs(attrs);
@@ -198,7 +200,7 @@ static void loadDerivations(EvalState & state, const SourcePath & nixExprPath,
Value vRoot;
loadSourceExpr(state, nixExprPath, vRoot);
Value & v(*findAlongAttrPath(state, pathPrefix, autoArgs, vRoot).first);
Value v(findAlongAttrPath(state, pathPrefix, autoArgs, vRoot).first);
getDerivations(state, v, pathPrefix, autoArgs, elems, true);
@@ -317,9 +319,9 @@ std::vector<Match> pickNewestOnly(EvalState & state, std::vector<Match> matches)
matches.clear();
for (auto & [name, match] : newest) {
if (multiple.find(name) != multiple.end())
warn(
"there are multiple derivations named '%1%'; using the first one",
name);
printTaggedWarning(
"there are multiple derivations named '%1%'; using the first one", name
);
matches.push_back(match);
}
@@ -425,7 +427,7 @@ static void queryInstSources(EvalState & state,
Expr & eFun = state.ctx.parseExprFromString(i, CanonPath::fromCwd());
Value vFun, vTmp;
state.eval(eFun, vFun);
vTmp.mkApp(&vFun, &vArg);
vTmp = {NewValueAs::app, state.ctx.mem, vFun, vArg};
getDerivations(state, vTmp, "", *instSource.autoArgs, elems, true);
}
@@ -480,7 +482,7 @@ static void queryInstSources(EvalState & state,
Value vRoot;
loadSourceExpr(state, *instSource.nixExprPath, vRoot);
for (auto & i : args) {
Value & v(*findAlongAttrPath(state, i, *instSource.autoArgs, vRoot).first);
Value v(findAlongAttrPath(state, i, *instSource.autoArgs, vRoot).first);
getDerivations(state, v, "", *instSource.autoArgs, elems, true);
}
break;
@@ -515,8 +517,8 @@ static bool keep(EvalState & state, DrvInfo & drv)
static void setMetaFlag(EvalState & state, DrvInfo & drv,
const std::string & name, const std::string & value)
{
auto v = state.ctx.mem.allocValue();
v->mkString(value);
Value v;
v.mkString(value);
drv.setMeta(state, name, v);
}
@@ -686,8 +688,12 @@ static void upgradeDerivations(Globals & globals,
{
const char * action = compareVersions(drvName.version, bestVersion) <= 0
? "upgrading" : "downgrading";
printInfo("%1% '%2%' to '%3%'",
action, i.queryName(*state), bestElem->queryName(*state));
printInfo(
"%1% '%2%' to '%3%'",
Uncolored(action),
i.queryName(*state),
bestElem->queryName(*state)
);
newElems.push_back(*bestElem);
} else newElems.push_back(i);
@@ -845,7 +851,7 @@ static void uninstallDerivations(Globals & globals, Strings & selectors,
);
}
if (split == workingElems.end())
warn("selector '%s' matched no installed derivations", selector);
printTaggedWarning("selector '%s' matched no installed derivations", selector);
for (auto removedElem = split; removedElem != workingElems.end(); removedElem++) {
printInfo("uninstalling '%s'", removedElem->queryName(*state));
}
@@ -1267,39 +1273,43 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
else {
if (v->type() == nString) {
attrs2["type"] = "string";
attrs2["value"] = v->string.s;
attrs2["value"] = v->str();
xml.writeEmptyElement("meta", attrs2);
} else if (v->type() == nInt) {
attrs2["type"] = "int";
attrs2["value"] = fmt("%1%", v->integer);
attrs2["value"] = fmt("%1%", v->integer());
xml.writeEmptyElement("meta", attrs2);
} else if (v->type() == nFloat) {
attrs2["type"] = "float";
attrs2["value"] = fmt("%1%", v->fpoint);
attrs2["value"] = fmt("%1%", v->fpoint());
xml.writeEmptyElement("meta", attrs2);
} else if (v->type() == nBool) {
attrs2["type"] = "bool";
attrs2["value"] = v->boolean ? "true" : "false";
attrs2["value"] = v->boolean() ? "true" : "false";
xml.writeEmptyElement("meta", attrs2);
} else if (v->type() == nList) {
attrs2["type"] = "strings";
XMLOpenElement m(xml, "meta", attrs2);
for (auto elem : v->listItems()) {
if (elem->type() != nString) continue;
for (auto & elem : v->listItems()) {
if (elem.type() != nString) {
continue;
}
XMLAttrs attrs3;
attrs3["value"] = elem->string.s;
attrs3["value"] = elem.str();
xml.writeEmptyElement("string", attrs3);
}
} else if (v->type() == nAttrs) {
attrs2["type"] = "strings";
XMLOpenElement m(xml, "meta", attrs2);
Bindings & attrs = *v->attrs;
Bindings & attrs = *v->attrs();
for (auto &i : attrs) {
Attr & a(*attrs.find(i.name));
if(a.value->type() != nString) continue;
const Attr & a(*attrs.get(i.name));
if (a.value.type() != nString) {
continue;
}
XMLAttrs attrs3;
attrs3["type"] = globals.state->symbols[i.name];
attrs3["value"] = a.value->string.s;
attrs3["value"] = a.value.str();
xml.writeEmptyElement("string", attrs3);
}
}
+1 -1
View File
@@ -38,7 +38,7 @@ void processExpr(EvalState & state, const Strings & attrPaths,
state.eval(e, vRoot);
for (auto & i : attrPaths) {
Value & v(*findAlongAttrPath(state, i, autoArgs, vRoot).first);
Value v(findAlongAttrPath(state, i, autoArgs, vRoot).first);
state.forceValue(v, noPos);
NixStringContext context;
+58 -14
View File
@@ -17,11 +17,15 @@
#include "graphml.hh"
#include "lix/libcmd/legacy.hh"
#include "lix/libstore/path-with-outputs.hh"
#include "lix/libutil/serialise.hh"
#include "nix-store.hh"
#include <cstdint>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <ranges>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
@@ -85,6 +89,16 @@ try {
if (i == drv.outputs.end())
throw Error("derivation '%s' does not have an output named '%s'",
store2->printStorePath(path.path), j);
if (!outputPaths.contains(i->first)) {
throw Error(
"Possible SQLite database corruption: derivation '%s' output map contains only "
"outputs '{%s}', not '%s'\n"
"Note: derivation output maps are stored in the SQLite database.",
store2->printStorePath(path.path),
concatStringsSep(", ", std::views::keys(outputPaths)),
i->first
);
}
auto outPath = outputPaths.at(i->first);
auto retPath = store->printStorePath(outPath);
if (store2) {
@@ -823,7 +837,7 @@ opVerify(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strin
else throw UsageError("unknown flag '%1%'", i);
if (aio.blockOn(store->verifyStore(checkContents, repair))) {
warn("not all store errors were fixed");
printTaggedWarning("not all store errors were fixed");
throw Exit(1);
}
}
@@ -896,11 +910,11 @@ opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
FdSink out(STDOUT_FILENO);
/* Exchange the greeting. */
unsigned int magic = readInt(in);
unsigned int magic = readNum<unsigned>(in);
if (magic != SERVE_MAGIC_1) throw Error("protocol mismatch");
out << SERVE_MAGIC_2 << SERVE_PROTOCOL_VERSION;
out.flush();
ServeProto::Version clientVersion = readInt(in);
ServeProto::Version clientVersion = readNum<unsigned>(in);
ServeProto::ReadConn rconn {
.from = in,
@@ -918,12 +932,12 @@ opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
verbosity = lvlError;
settings.keepLog.override(false);
settings.useSubstitutes.override(false);
settings.maxSilentTime.override(readInt(in));
settings.buildTimeout.override(readInt(in));
settings.maxSilentTime.override(readNum<unsigned>(in));
settings.buildTimeout.override(readNum<unsigned>(in));
if (GET_PROTOCOL_MINOR(clientVersion) >= 2)
settings.maxLogSize.override(readNum<unsigned long>(in));
if (GET_PROTOCOL_MINOR(clientVersion) >= 3) {
auto nrRepeats = readInt(in);
auto nrRepeats = readNum<unsigned>(in);
if (nrRepeats != 0) {
throw Error("client requested repeating builds, but this is not currently implemented");
}
@@ -933,19 +947,19 @@ opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
// `nrRepeats` in fact is 0, so we can safely ignore this
// without doing something other than what the client
// asked for.
readInt(in);
readNum<unsigned>(in);
settings.runDiffHook.override(true);
}
if (GET_PROTOCOL_MINOR(clientVersion) >= 7) {
settings.keepFailed.override((bool) readInt(in));
settings.keepFailed.override((bool) readNum<unsigned>(in));
}
};
while (true) {
ServeProto::Command cmd;
try {
cmd = (ServeProto::Command) readInt(in);
cmd = (ServeProto::Command) readNum<unsigned>(in);
} catch (EndOfFile & e) {
break;
}
@@ -953,8 +967,8 @@ opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
switch (cmd) {
case ServeProto::Command::QueryValidPaths: {
bool lock = readInt(in);
bool substitute = readInt(in);
bool lock = readNum<unsigned>(in);
bool substitute = readNum<unsigned>(in);
auto paths = ServeProto::Serialise<StorePathSet>::read(rconn);
if (lock && writeAllowed)
for (auto & path : paths)
@@ -997,7 +1011,7 @@ opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
}
case ServeProto::Command::ExportPaths: {
readInt(in); // obsolete
readNum<unsigned>(in); // obsolete
aio.blockOn(store->exportPaths(
ServeProto::Serialise<StorePathSet>::read(rconn), out
));
@@ -1043,7 +1057,7 @@ opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
}
case ServeProto::Command::QueryClosure: {
bool includeOutputs = readInt(in);
bool includeOutputs = readNum<unsigned>(in);
StorePathSet closure;
aio.blockOn(store->computeFSClosure(
ServeProto::Serialise<StorePathSet>::read(rconn),
@@ -1067,13 +1081,43 @@ opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
if (deriver != "")
info.deriver = store->parseStorePath(deriver);
info.references = ServeProto::Serialise<StorePathSet>::read(rconn);
in >> info.registrationTime >> info.narSize >> info.ultimate;
info.registrationTime = readNum<time_t>(in);
info.narSize = readNum<uint64_t>(in);
info.ultimate = readBool(in);
info.sigs = readStrings<StringSet>(in);
info.ca = ContentAddress::parseOpt(readString(in));
if (info.narSize == 0)
throw Error("narInfo is too old and missing the narSize field");
struct SizedSource : Source
{
Source & orig;
size_t remain;
SizedSource(Source & orig, size_t size) : orig(orig), remain(size) {}
size_t read(char * data, size_t len) override
{
if (this->remain <= 0) {
throw EndOfFile("sized: unexpected end-of-file");
}
len = std::min(len, this->remain);
size_t n = this->orig.read(data, len);
this->remain -= n;
return n;
}
size_t drainAll()
{
std::vector<char> buf(8192);
size_t sum = 0;
while (this->remain > 0) {
size_t n = read(buf.data(), buf.size());
sum += n;
}
return sum;
}
};
SizedSource sizedSource(in, info.narSize);
AsyncSourceInputStream stream{sizedSource};
+15 -13
View File
@@ -1,4 +1,5 @@
#include "user-env.hh"
#include "lix/libexpr/value.hh"
#include "lix/libstore/derivations.hh"
#include "lix/libstore/store-api.hh"
#include "lix/libstore/path-with-outputs.hh"
@@ -32,7 +33,8 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
/* Construct the whole top level derivation. */
StorePathSet references;
Value manifest = state.ctx.mem.newList(elems.size());
auto manifest = state.ctx.mem.newList(elems.size());
Value vManifest{NewValueAs::list, manifest};
size_t n = 0;
for (auto & i : elems) {
/* Create a pseudo-derivation containing the name, system,
@@ -55,9 +57,10 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
// Copy each output meant for installation.
auto & vOutputs = attrs.alloc(state.ctx.s.outputs);
vOutputs = state.ctx.mem.newList(outputs.size());
auto outputsList = state.ctx.mem.newList(outputs.size());
vOutputs = {NewValueAs::list, outputsList};
for (const auto & [m, j] : enumerate(outputs)) {
(vOutputs.listElems()[m] = state.ctx.mem.allocValue())->mkString(j.first);
outputsList->elems[m].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);
@@ -75,12 +78,12 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
for (auto & j : metaNames) {
Value * v = i.queryMeta(state, j);
if (!v) continue;
meta.insert(state.ctx.symbols.create(j), v);
meta.insert(state.ctx.symbols.create(j), *v);
}
attrs.alloc(state.ctx.s.meta).mkAttrs(meta);
(manifest.listElems()[n++] = state.ctx.mem.allocValue())->mkAttrs(attrs);
manifest->elems[n++].mkAttrs(attrs);
if (drvPath) references.insert(*drvPath);
}
@@ -89,7 +92,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
the store; we need it for future modifications of the
environment. */
std::ostringstream str;
printAmbiguous(manifest, state.ctx.symbols, str, nullptr, std::numeric_limits<int>::max());
printAmbiguous(vManifest, state.ctx.symbols, str, nullptr, std::numeric_limits<int>::max());
auto manifestFile = state.aio.blockOn(state.ctx.store->addTextToStore("env-manifest.nix",
str.str(), references));
@@ -103,21 +106,20 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
builder with the manifest as argument. */
auto attrs = state.ctx.buildBindings(3);
state.ctx.paths.mkStorePathString(manifestFile, attrs.alloc("manifest"));
attrs.insert(state.ctx.symbols.create("derivations"), &manifest);
attrs.insert(state.ctx.symbols.create("derivations"), vManifest);
Value args;
args.mkAttrs(attrs);
Value topLevel;
topLevel.mkApp(&envBuilder, &args);
Value topLevel{NewValueAs::app, state.ctx.mem, envBuilder, args};
/* Evaluate it. */
debug("evaluating user environment builder");
state.forceValue(topLevel, noPos);
NixStringContext context;
Attr & aDrvPath(*topLevel.attrs->find(state.ctx.s.drvPath));
auto topLevelDrv = state.coerceToStorePath(aDrvPath.pos, *aDrvPath.value, context, "");
Attr & aOutPath(*topLevel.attrs->find(state.ctx.s.outPath));
auto topLevelOut = state.coerceToStorePath(aOutPath.pos, *aOutPath.value, context, "");
const Attr & aDrvPath(*topLevel.attrs()->get(state.ctx.s.drvPath));
auto topLevelDrv = state.coerceToStorePath(aDrvPath.pos, aDrvPath.value, context, "");
const Attr & aOutPath(*topLevel.attrs()->get(state.ctx.s.outPath));
auto topLevelOut = state.coerceToStorePath(aOutPath.pos, aOutPath.value, context, "");
/* Realise the resulting store expression. */
debug("building user environment");
+15 -9
View File
@@ -4,6 +4,9 @@
#include "lix/libstore/derivations.hh"
#include "lix/libstore/profiles.hh"
#include "lix/libcmd/repl.hh"
#include "lix/libutil/async.hh"
#include "lix/libutil/c-calls.hh"
#include "lix/libutil/error.hh"
extern char * * environ __attribute__((weak));
@@ -39,14 +42,15 @@ StoreCommand::StoreCommand()
ref<Store> StoreCommand::getStore()
{
if (!_store)
_store = createStore();
if (!_store) {
_store = createStore(aio());
}
return *_store;
}
ref<Store> StoreCommand::createStore()
ref<Store> StoreCommand::createStore(AsyncIoRoot & in)
{
return aio().blockOn(openStore());
return in.blockOn(openStore());
}
void StoreCommand::run()
@@ -71,9 +75,9 @@ CopyCommand::CopyCommand()
});
}
ref<Store> CopyCommand::createStore()
ref<Store> CopyCommand::createStore(AsyncIoRoot & in)
{
return srcUri.empty() ? StoreCommand::createStore() : aio().blockOn(openStore(srcUri));
return srcUri.empty() ? StoreCommand::createStore(in) : in.blockOn(openStore(srcUri));
}
ref<Store> CopyCommand::getDstStore()
@@ -301,8 +305,10 @@ void MixEnvironment::setEnviron() {
throw UsageError("--unset does not make sense with --ignore-environment");
for (const auto & var : keep) {
auto val = getenv(var.c_str());
if (val) stringsEnv.emplace_back(fmt("%s=%s", var.c_str(), val));
auto val = sys::getenv(var);
if (val) {
stringsEnv.emplace_back(fmt("%s=%s", var, val));
}
}
vectorEnv = stringsToCharPtrs(stringsEnv);
@@ -312,7 +318,7 @@ void MixEnvironment::setEnviron() {
throw UsageError("--keep does not make sense without --ignore-environment");
for (const auto & var : unset)
unsetenv(var.c_str());
(void) sys::unsetenv(var);
}
}
+2 -2
View File
@@ -39,7 +39,7 @@ struct StoreCommand : virtual Command
StoreCommand();
void run() override;
ref<Store> getStore();
virtual ref<Store> createStore();
virtual ref<Store> createStore(AsyncIoRoot & in);
/**
* Main entry point, with a `Store` provided
*/
@@ -59,7 +59,7 @@ struct CopyCommand : virtual StoreCommand
CopyCommand();
ref<Store> createStore() override;
ref<Store> createStore(AsyncIoRoot & in) override;
ref<Store> getDstStore();
};
+4 -4
View File
@@ -113,7 +113,7 @@ MixEvalArgs::MixEvalArgs()
```
-I nixpkgs=channel:nixos-21.05
-I nixpkgs=https://nixos.org/channels/nixos-21.05/nixexprs.tar.xz
-I nixpkgs=https://channels.nixos.org/nixos-21.05/nixexprs.tar.xz
```
You can also fetch source trees using [flake URLs](./nix3-flake.md#url-like-syntax) and add them to the
@@ -183,13 +183,13 @@ Bindings * MixEvalArgs::getAutoArgs(Evaluator & state)
{
auto res = state.buildBindings(autoArgs.size());
for (auto & i : autoArgs) {
auto v = state.mem.allocValue();
Value v;
if (i.second[0] == 'E')
state.evalLazily(
state.parseExprFromString(i.second.substr(1), CanonPath::fromCwd()), *v
state.parseExprFromString(i.second.substr(1), CanonPath::fromCwd()), v
);
else
v->mkString(((std::string_view) i.second).substr(1));
v.mkString(((std::string_view) i.second).substr(1));
res.insert(state.symbols.create(i.first), v);
}
return res.finish();
+11 -9
View File
@@ -12,9 +12,10 @@ namespace nix {
InstallableAttrPath::InstallableAttrPath(
ref<eval_cache::CachingEvaluator> state,
SourceExprCommand & cmd,
Value * v,
Value & v,
const std::string & attrPath,
ExtendedOutputsSpec extendedOutputsSpec)
ExtendedOutputsSpec extendedOutputsSpec
)
: InstallableValue(state)
, cmd(cmd)
, v(allocRootValue(v))
@@ -22,10 +23,10 @@ InstallableAttrPath::InstallableAttrPath(
, extendedOutputsSpec(std::move(extendedOutputsSpec))
{ }
std::pair<Value *, PosIdx> InstallableAttrPath::toValue(EvalState & state)
std::pair<Value, PosIdx> InstallableAttrPath::toValue(EvalState & state)
{
auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(*evaluator), **v);
state.forceValue(*vRes, pos);
auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(*evaluator), *v);
state.forceValue(vRes, pos);
return {vRes, pos};
}
@@ -34,7 +35,7 @@ DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths(EvalState & state)
auto [v, pos] = toValue(state);
if (std::optional derivedPathWithInfo = trySinglePathToDerivedPaths(
state, *v, pos, fmt("while evaluating the attribute '%s'", attrPath)
state, v, pos, fmt("while evaluating the attribute '%s'", attrPath)
))
{
return { *derivedPathWithInfo };
@@ -43,7 +44,7 @@ DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths(EvalState & 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.
@@ -92,9 +93,10 @@ DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths(EvalState & state)
InstallableAttrPath InstallableAttrPath::parse(
ref<eval_cache::CachingEvaluator> state,
SourceExprCommand & cmd,
Value * v,
Value & v,
std::string_view prefix,
ExtendedOutputsSpec extendedOutputsSpec)
ExtendedOutputsSpec extendedOutputsSpec
)
{
return {
state, cmd, v,
+7 -5
View File
@@ -20,13 +20,14 @@ class InstallableAttrPath : public InstallableValue
InstallableAttrPath(
ref<eval_cache::CachingEvaluator> state,
SourceExprCommand & cmd,
Value * v,
Value & v,
const std::string & attrPath,
ExtendedOutputsSpec extendedOutputsSpec);
ExtendedOutputsSpec extendedOutputsSpec
);
std::string what() const override { return attrPath; };
std::pair<Value *, PosIdx> toValue(EvalState & state) override;
std::pair<Value, PosIdx> toValue(EvalState & state) override;
DerivedPathsWithInfo toDerivedPaths(EvalState & state) override;
@@ -35,9 +36,10 @@ public:
static InstallableAttrPath parse(
ref<eval_cache::CachingEvaluator> state,
SourceExprCommand & cmd,
Value * v,
Value & v,
std::string_view prefix,
ExtendedOutputsSpec extendedOutputsSpec);
ExtendedOutputsSpec extendedOutputsSpec
);
};
}
+19 -27
View File
@@ -26,35 +26,27 @@ InstallableDerivedPath InstallableDerivedPath::parse(
std::string_view prefix,
ExtendedOutputsSpec extendedOutputsSpec)
{
auto derivedPath = std::visit(overloaded {
// If the user did not use ^, we treat the output more
// liberally: we accept a symlink chain or an actual
// store path.
[&](const ExtendedOutputsSpec::Default &) -> DerivedPath {
auto storePath = store->followLinksToStorePath(prefix);
// Remove this prior to stabilizing the new CLI.
if (storePath.isDerivation()) {
auto oldDerivedPath = DerivedPath::Built {
.drvPath = makeConstantStorePath(storePath),
.outputs = OutputsSpec::All { },
auto derivedPath = std::visit(
overloaded{
// If the user did not use ^, we treat the output more
// liberally: we accept a symlink chain or an actual
// store path.
[&](const ExtendedOutputsSpec::Default &) -> DerivedPath {
return DerivedPath::Opaque{
.path = store->followLinksToStorePath(prefix),
};
warn(
"The interpretation of store paths arguments ending in `.drv` recently changed. If this command is now failing try again with '%s'",
oldDerivedPath.to_string(*store));
};
return DerivedPath::Opaque {
.path = std::move(storePath),
};
},
// If the user did use ^, we just do exactly what is written.
[&](const ExtendedOutputsSpec::Explicit & outputSpec) -> DerivedPath {
auto drv = DerivedPathOpaque::parse(*store, prefix);
return DerivedPath::Built{
.drvPath = std::move(drv),
.outputs = outputSpec,
};
},
},
// If the user did use ^, we just do exactly what is written.
[&](const ExtendedOutputsSpec::Explicit & outputSpec) -> DerivedPath {
auto drv = DerivedPathOpaque::parse(*store, prefix);
return DerivedPath::Built {
.drvPath = std::move(drv),
.outputs = outputSpec,
};
},
}, extendedOutputsSpec.raw);
extendedOutputsSpec.raw
);
return InstallableDerivedPath {
store,
std::move(derivedPath),
+4 -3
View File
@@ -60,7 +60,8 @@ InstallableFlake::InstallableFlake(
DerivedPathsWithInfo InstallableFlake::toDerivedPaths(EvalState & state)
{
Activity act(*logger, lvlTalkative, actUnknown, fmt("evaluating derivation '%s'", what()));
auto act =
logger->startActivity(lvlTalkative, actUnknown, fmt("evaluating derivation '%s'", what()));
auto attr = getCursor(state);
@@ -136,9 +137,9 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths(EvalState & state)
}};
}
std::pair<Value *, PosIdx> InstallableFlake::toValue(EvalState & state)
std::pair<Value, PosIdx> InstallableFlake::toValue(EvalState & state)
{
return {&getCursor(state)->forceValue(state), noPos};
return {getCursor(state)->forceValue(state), noPos};
}
std::vector<ref<eval_cache::AttrCursor>>
+1 -1
View File
@@ -55,7 +55,7 @@ struct InstallableFlake : InstallableValue
DerivedPathsWithInfo toDerivedPaths(EvalState & state) override;
std::pair<Value *, PosIdx> toValue(EvalState & state) override;
std::pair<Value, PosIdx> toValue(EvalState & state) override;
/**
* Get a cursor to every attrpath in getActualAttrPaths() that
+3 -2
View File
@@ -9,8 +9,9 @@ std::vector<ref<eval_cache::AttrCursor>>
InstallableValue::getCursors(EvalState & state)
{
auto evalCache =
std::make_shared<nix::eval_cache::EvalCache>(std::nullopt,
[&](EvalState & state) { return toValue(state).first; });
std::make_shared<nix::eval_cache::EvalCache>(std::nullopt, [&](EvalState & state) {
return toValue(state).first;
});
return {evalCache->getRoot()};
}
+1 -1
View File
@@ -77,7 +77,7 @@ struct InstallableValue : Installable
virtual ~InstallableValue() { }
virtual std::pair<Value *, PosIdx> toValue(EvalState & state) = 0;
virtual std::pair<Value, PosIdx> toValue(EvalState & state) = 0;
/**
* Get a cursor to each value this Installable could refer to.
+16 -18
View File
@@ -61,7 +61,7 @@ MixFlakeOptions::MixFlakeOptions()
.category = category,
.handler = {[&]() {
lockFlags.useRegistries = false;
warn("'--no-registries' is deprecated; use '--no-use-registries'");
printTaggedWarning("'--no-registries' is deprecated; use '--no-use-registries'");
}}
});
@@ -235,15 +235,14 @@ void SourceExprCommand::completeInstallable(EvalState & state, AddCompletions &
prefix_ = "";
}
auto [v, pos] = findAlongAttrPath(state, prefix_, *autoArgs, root);
Value &v1(*v);
auto [v1, pos] = findAlongAttrPath(state, prefix_, *autoArgs, root);
state.forceValue(v1, pos);
Value v2;
state.autoCallFunction(*autoArgs, v1, v2, pos);
if (v2.type() == nAttrs) {
for (auto & i : *v2.attrs) {
std::string name = evaluator->symbols[i.name];
for (auto & i : *v2.attrs()) {
std::string name{evaluator->symbols[i.name]};
if (name.find(searchWord) == 0) {
if (prefix_ == "")
completions.add(name);
@@ -344,7 +343,7 @@ void completeFlakeRefWithFragment(
}
}
} catch (Error & e) {
warn(e.msg());
printTaggedWarning("%1%", Uncolored(e.msg()));
}
}
@@ -412,12 +411,12 @@ ref<eval_cache::EvalCache> openEvalCache(
if (getEnv("NIX_ALLOW_EVAL").value_or("1") == "0")
throw Error("not everything is cached, but evaluation is not allowed");
auto vFlake = state.ctx.mem.allocValue();
flake::callFlake(state, *lockedFlake, *vFlake);
Value vFlake;
flake::callFlake(state, *lockedFlake, vFlake);
state.forceAttrs(*vFlake, noPos, "while parsing cached flake data");
state.forceAttrs(vFlake, noPos, "while parsing cached flake data");
auto aOutputs = vFlake->attrs->get(state.ctx.symbols.create("outputs"));
auto aOutputs = vFlake.attrs()->get(state.ctx.symbols.create("outputs"));
assert(aOutputs);
return aOutputs->value;
@@ -450,25 +449,24 @@ Installables SourceExprCommand::parseInstallables(
throw UsageError("'--file' and '--expr' are exclusive");
auto evaluator = getEvaluator();
auto vFile = evaluator->mem.allocValue();
Value vFile;
if (file == "-") {
auto & e = evaluator->parseStdin();
state.eval(e, *vFile);
state.eval(e, vFile);
}
else if (file)
state.evalFile(state.aio.blockOn(lookupFileArg(*evaluator, *file)).unwrap(), *vFile);
state.evalFile(state.aio.blockOn(lookupFileArg(*evaluator, *file)).unwrap(), vFile);
else {
auto & e = evaluator->parseExprFromString(*expr, CanonPath::fromCwd());
state.eval(e, *vFile);
state.eval(e, vFile);
}
for (auto & s : ss) {
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(s);
result.push_back(
make_ref<InstallableAttrPath>(
InstallableAttrPath::parse(
evaluator, *this, vFile, std::move(prefix), std::move(extendedOutputsSpec))));
result.push_back(make_ref<InstallableAttrPath>(InstallableAttrPath::parse(
evaluator, *this, vFile, std::move(prefix), std::move(extendedOutputsSpec)
)));
}
} else {
+1 -1
View File
@@ -9,7 +9,7 @@
namespace nix {
typedef std::function<void(AsyncIoRoot &, std::string, std::list<std::string>)> MainFunction;
typedef std::function<int(AsyncIoRoot &, std::string, std::list<std::string>)> MainFunction;
struct LegacyCommandRegistry
{
+3 -2
View File
@@ -5,5 +5,6 @@ includedir=@includedir@
Name: Lix (libcmd)
Description: Lix Package Manager (libcmd)
Version: @PACKAGE_VERSION@
Requires: lix-base lix-util
Libs: -L${libdir} -llixcmd
Requires: lix-base lix-util lix-store
Requires.private: lix-fetchers lix-expr lix-main @BOEHM_IF_FOUND@ libeditline lowdown ncurses
Libs: -L${libdir} @LIBLIX_DOC_IF_STATIC@ -llixcmd
+17 -2
View File
@@ -51,13 +51,28 @@ static void processLinks(struct lowdown_node * node)
std::string renderMarkdownToTerminal(std::string_view markdown, StandardOutputStream fileno)
{
int windowWidth = getWindowSize().second;
size_t lowdown_cols = std::max(windowWidth - 5, 60);
struct lowdown_opts opts {
struct lowdown_opts opts{
.type = LOWDOWN_TERM,
#ifdef LOWDOWN_SEPARATE_TERM_OPTS
.term =
{
.cols = lowdown_cols,
.width = 0,
.hmargin = 0,
.hpadding = 4,
.vmargin = 0,
.centre = 0,
},
// maxdepth needs to be part of the ifdefs to match declaration order
.maxdepth = 20,
.cols = (size_t) std::max(windowWidth - 5, 60),
#else
.maxdepth = 20,
.cols = lowdown_cols,
.hmargin = 0,
.vmargin = 0,
#endif /* LOWDOWN_SEPARATE_TERM_OPTS */
.feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES,
.oflags = LOWDOWN_TERM_NOLINK,
};
+13 -5
View File
@@ -43,15 +43,16 @@ libcmd = library(
dependencies : [
liblixutil,
liblixstore,
liblixexpr,
liblixfetchers,
liblixexpr,
liblixmain,
ncurses,
editline,
lowdown,
nlohmann_json,
liblix_doc,
boehm,
editline,
kj,
lowdown,
ncurses,
nlohmann_json,
],
# '../..' for self references like "lix/libcmd/*.hh"
include_directories : [ '../..' ],
@@ -72,6 +73,11 @@ custom_target(
liblixcmd = declare_dependency(
include_directories : include_directories('../..'),
dependencies : [
liblixutil,
liblixstore,
kj,
],
link_with : libcmd,
)
meson.override_dependency('lix-cmd', liblixcmd)
@@ -87,5 +93,7 @@ configure_file(
'libdir' : libdir,
'includedir' : includedir,
'PACKAGE_VERSION' : meson.project_version(),
'BOEHM_IF_FOUND' : boehm.found() ? 'bdw-gc' : '',
'LIBLIX_DOC_IF_STATIC' : is_static ? '-llix_doc' : '',
},
)
+7 -7
View File
@@ -1,3 +1,4 @@
#include "lix/libutil/c-calls.hh"
#include "lix/libutil/error.hh"
#include "lix/libutil/file-system.hh"
#include "lix/libutil/logging.hh"
@@ -110,7 +111,7 @@ static el_status_t doCompletion() {
if (possible.size() == 1) {
const auto completion = *possible.cbegin();
if (completion.size() > s.size()) {
rl_insert_text(completion.c_str() + s.size());
rl_insert_text(requireCString(completion.substr(s.size())));
return redisplay();
}
@@ -134,7 +135,7 @@ static el_status_t doCompletion() {
}
if (len > 0) {
auto commonPrefix = possible.begin()->substr(start, len);
rl_insert_text(commonPrefix.c_str());
rl_insert_text(requireCString(commonPrefix));
el_ring_bell();
return redisplay();
}
@@ -154,7 +155,7 @@ ReadlineLikeInteracter::Guard ReadlineLikeInteracter::init(detail::ReplCompleter
logWarning(e.info());
}
el_hist_size = 1000;
read_history(historyFile.c_str());
read_history(requireCString(historyFile));
auto oldRepl = curRepl;
curRepl = repl;
Guard restoreRepl([oldRepl] { curRepl = oldRepl; });
@@ -202,7 +203,7 @@ bool ReadlineLikeInteracter::getLine(std::string & input, ReplPromptType promptT
};
setupSignals();
char * s = readline(promptForType(promptType));
char * s = readline(promptForType(promptType)); // NOLINT(lix-unsafe-c-calls)
Finally doFree([&]() { free(s); });
restoreSignals();
@@ -223,7 +224,7 @@ bool ReadlineLikeInteracter::getLine(std::string & input, ReplPromptType promptT
void ReadlineLikeInteracter::writeHistory()
{
int ret = write_history(historyFile.c_str());
int ret = write_history(requireCString(historyFile));
int writeHistErr = errno;
if (ret == 0) {
@@ -244,8 +245,7 @@ void ReadlineLikeInteracter::writeHistory()
// them so the user isn't confused why their history is getting eaten.
std::string_view const errMsg(std::strerror(writeHistErr));
warn("ignoring error writing repl history to %s: %s", this->historyFile, errMsg);
printTaggedWarning("ignoring error writing repl history to %s: %s", this->historyFile, errMsg);
}
ReadlineLikeInteracter::~ReadlineLikeInteracter()
+76 -65
View File
@@ -5,6 +5,7 @@
#include <cstring>
#include <string_view>
#include "lix/libexpr/value.hh"
#include "lix/libutil/box_ptr.hh"
#include "lix/libcmd/repl-interacter.hh"
#include "lix/libcmd/repl.hh"
@@ -55,6 +56,7 @@ using NdString = std::unique_ptr<const char, decltype(&lixdoc_free_string)>;
*/
NdString lambdaDocsForPos(SourcePath const path, nix::Pos const &pos) {
std::string const file = path.to_string();
// NOLINTNEXTLINE(lix-unsafe-c-calls): paths are safe
return NdString{lixdoc_get_function_docs(file.c_str(), pos.line, pos.column), &lixdoc_free_string};
}
@@ -173,35 +175,37 @@ struct NixRepl
/**
* Get a list of each of the `repl-overlays` (parsed and evaluated).
*/
Value * replOverlays();
Value replOverlays();
/**
* Get the Nix function that composes the `repl-overlays` together.
*/
Value * getReplOverlaysEvalFunction();
Value getReplOverlaysEvalFunction();
/**
* Cached return value of `getReplOverlaysEvalFunction`.
*
* Note: This is `shared_ptr` to avoid garbage collection.
*/
std::shared_ptr<Value *> replOverlaysEvalFunction =
std::allocate_shared<Value *>(TraceableAllocator<Value *>(), nullptr);
std::shared_ptr<std::optional<Value>> replOverlaysEvalFunction =
std::allocate_shared<std::optional<Value>>(
TraceableAllocator<std::optional<Value>>(), std::nullopt
);
/**
* Get the `info` AttrSet that's passed as the first argument to each
* of the `repl-overlays`.
*/
Value * replInitInfo();
Value replInitInfo();
/**
* Get the current top-level bindings as an AttrSet.
*/
Value * bindingsToAttrs();
Value bindingsToAttrs();
/**
* Parse a file, evaluate its result, and force the resulting value.
*/
Value * evalFile(SourcePath & path);
Value evalFile(SourcePath & path);
void printValue(std::ostream & str,
Value & v,
@@ -290,7 +294,7 @@ ReplExitStatus NixRepl::mainLoop()
if (evaluator.debug && evaluator.debug->inDebugger) {
debuggerNotice = " debugger";
}
notice("Lix %1%%2%\nType :? for help.", nixVersion, debuggerNotice);
notice("Lix %1%%2%\nType :? for help.", Uncolored(nixVersion), debuggerNotice);
}
isFirstRepl = false;
@@ -306,7 +310,7 @@ ReplExitStatus NixRepl::mainLoop()
std::string input;
while (true) {
_isInterrupted = false;
unsetUserInterruptRequest();
// When continuing input from previous lines, don't print a prompt, just align to the same
// number of chars as the prompt.
@@ -337,14 +341,14 @@ ReplExitStatus NixRepl::mainLoop()
// input without clearing the input so far.
continue;
} else {
printMsg(lvlError, e.msg());
printMsg(lvlError, "%1%", Uncolored(e.msg()));
}
} catch (EvalError & e) {
printMsg(lvlError, e.msg());
printMsg(lvlError, "%1%", Uncolored(e.msg()));
} catch (Error & e) {
printMsg(lvlError, e.msg());
printMsg(lvlError, "%1%", Uncolored(e.msg()));
} catch (Interrupted & e) {
printMsg(lvlError, e.msg());
printMsg(lvlError, "%1%", Uncolored(e.msg()));
}
// We handled the current input fully, so we should clear it
@@ -450,7 +454,7 @@ StringSet NixRepl::completePrefix(const std::string &prefix)
e.eval(state, *env, v);
state.forceAttrs(v, noPos, "while evaluating an attrset for the purpose of completion (this error should not be displayed; file an issue?)");
for (auto & i : *v.attrs) {
for (auto & i : *v.attrs()) {
std::ostringstream output;
printAttributeName(output, evaluator.symbols[i.name]);
std::string name = output.str();
@@ -653,7 +657,7 @@ 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 = evaluator.positions[v.lambda.fun->pos];
auto pos = evaluator.positions[v.lambda().fun->pos];
if (auto path = std::get_if<CheckedSourcePath>(&pos.origin))
return {*path, pos.line};
else
@@ -788,7 +792,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
Value v;
evalString(arg, v);
if (v.type() == nString) {
std::cout << v.string.s;
std::cout << v.str();
} else {
printValue(std::cout, v);
}
@@ -822,7 +826,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
logger->cout(trim(renderMarkdownToTerminal(markdown)));
} else if (v.isLambda()) {
auto pos = evaluator.positions[v.lambda.fun->pos];
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);
@@ -862,10 +866,10 @@ ProcessLineResult NixRepl::processLine(std::string line)
std::visit(overloaded {
[&](ExprReplBindings & b) {
for (auto & [name, e] : b.symbols) {
Value * v = state.ctx.mem.allocValue();
e->eval(state, *env, *v);
Value v;
e->eval(state, *env, v);
(void) e.release(); // NOLINT(bugprone-unused-return-value): leak because of thunk references
addVarToScope(name, *v);
addVarToScope(name, v);
}
},
[&](std::unique_ptr<Expr> & e) {
@@ -948,7 +952,7 @@ void NixRepl::loadFiles()
for (auto & [i, what] : getValues()) {
notice("Loading installable '%1%'...", Magenta(what));
addAttrsToScope(*i);
addAttrsToScope(i);
}
loadReplOverlays();
@@ -960,12 +964,12 @@ void NixRepl::loadReplOverlays()
return;
}
notice("Loading '%1%'...", Magenta("repl-overlays"));
notice("Loading '%1%'...", "repl-overlays");
auto replInitFilesFunction = getReplOverlaysEvalFunction();
Value &newAttrs(*evaluator.mem.allocValue());
SmallValueVector<3> args = {replInitInfo(), bindingsToAttrs(), replOverlays()};
state.callFunction(*replInitFilesFunction, args.size(), args.data(), newAttrs, noPos);
Value newAttrs;
Value args[] = {replInitInfo(), bindingsToAttrs(), replOverlays()};
state.callFunction(replInitFilesFunction, args, newAttrs, noPos);
// n.b. this does in fact load the stuff into the environment twice (once
// from the superset of the environment returned by repl-overlays and once
@@ -975,14 +979,14 @@ void NixRepl::loadReplOverlays()
addAttrsToScope(newAttrs);
}
Value * NixRepl::getReplOverlaysEvalFunction()
Value NixRepl::getReplOverlaysEvalFunction()
{
if (replOverlaysEvalFunction && *replOverlaysEvalFunction) {
return *replOverlaysEvalFunction;
return **replOverlaysEvalFunction;
}
auto evalReplInitFilesPath = CanonPath::root + "repl-overlays.nix";
*replOverlaysEvalFunction = evaluator.mem.allocValue();
*replOverlaysEvalFunction = Value{};
auto code =
#include "repl-overlays.nix.gen.hh"
;
@@ -994,14 +998,14 @@ Value * NixRepl::getReplOverlaysEvalFunction()
state.eval(expr, **replOverlaysEvalFunction);
return *replOverlaysEvalFunction;
return **replOverlaysEvalFunction;
}
Value * NixRepl::replOverlays()
Value NixRepl::replOverlays()
{
Value * replInits(evaluator.mem.allocValue());
*replInits = evaluator.mem.newList(evalSettings.replOverlays.get().size());
Value ** replInitElems = replInits->listElems();
Value replInits;
auto replInitStorage = evaluator.mem.newList(evalSettings.replOverlays.get().size());
replInits = {NewValueAs::list, replInitStorage};
size_t i = 0;
for (auto path : evalSettings.replOverlays.get()) {
@@ -1017,27 +1021,32 @@ Value * NixRepl::replOverlays()
auto replInit = evalFile(sourcePath);
evalSettings.pureEval.setDefault(prevPureEval);
if (!replInit->isLambda()) {
evaluator.errors.make<TypeError>(
"Expected `repl-overlays` entry %s to be a lambda but found %s: %s",
path,
showType(*replInit),
ValuePrinter(state, *replInit, errorPrintOptions)
)
.debugThrow();
}
if (auto attrs = dynamic_cast<AttrsPattern *>(replInit->lambda.fun->pattern.get()); attrs && !attrs->ellipsis) {
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",
"..."
)
.atPos(replInit->lambda.fun->pos)
if (!replInit.isLambda()) {
evaluator.errors
.make<TypeError>(
"Expected `repl-overlays` entry %s to be a lambda but found %s: %s",
path,
showType(replInit),
ValuePrinter(state, replInit, errorPrintOptions)
)
.debugThrow();
}
replInitElems[i] = replInit;
if (auto attrs = dynamic_cast<AttrsPattern *>(replInit.lambda().fun->pattern.get());
attrs && !attrs->ellipsis)
{
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",
"..."
)
.atPos(replInit.lambda().fun->pos)
.debugThrow();
}
replInitStorage->elems[i] = replInit;
i++;
}
@@ -1045,16 +1054,16 @@ Value * NixRepl::replOverlays()
return replInits;
}
Value * NixRepl::replInitInfo()
Value NixRepl::replInitInfo()
{
auto builder = evaluator.buildBindings(2);
Value * currentSystem(evaluator.mem.allocValue());
currentSystem->mkString(evalSettings.getCurrentSystem());
Value currentSystem;
currentSystem.mkString(evalSettings.getCurrentSystem());
builder.insert(evaluator.symbols.create("currentSystem"), currentSystem);
Value * info(evaluator.mem.allocValue());
info->mkAttrs(builder.finish());
Value info;
info.mkAttrs(builder.finish());
return info;
}
@@ -1089,7 +1098,9 @@ void NixRepl::addToScope(T && things, NameFn nameFn, ValueFn valueFn)
void NixRepl::addAttrsToScope(Value & attrs)
{
state.forceAttrs(attrs, noPos, "while evaluating an attribute set to be merged in the global scope");
addToScope(*attrs.attrs, [](Attr & a) { return a.name; }, [](Attr & a) { return a.value; });
addToScope(
*attrs.attrs(), [](const Attr & a) { return a.name; }, [](const Attr & a) { return a.value; }
);
}
void NixRepl::addValMapToScope(const ValMap & attrs)
@@ -1110,19 +1121,19 @@ void NixRepl::addVarToScope(const Symbol name, Value & v)
} else {
notice("Added %s.", evaluator.symbols[name]);
}
env->values[displ++] = &v;
env->values[displ++] = v;
varNames.emplace(evaluator.symbols[name]);
}
Value * NixRepl::bindingsToAttrs()
Value NixRepl::bindingsToAttrs()
{
auto builder = evaluator.buildBindings(staticEnv->vars.size());
for (auto & [symbol, displacement] : staticEnv->vars) {
builder.insert(symbol, env->values[displacement]);
}
Value * attrs(evaluator.mem.allocValue());
attrs->mkAttrs(builder.finish());
Value attrs;
attrs.mkAttrs(builder.finish());
return attrs;
}
@@ -1145,12 +1156,12 @@ void NixRepl::evalString(std::string s, Value & v)
state.forceValue(v, noPos);
}
Value * NixRepl::evalFile(SourcePath & path)
Value NixRepl::evalFile(SourcePath & path)
{
auto & expr = evaluator.parseExprFromFile(evaluator.paths.checkSourcePath(path), staticEnv);
Value * result(evaluator.mem.allocValue());
expr.eval(state, *env, *result);
state.forceValue(*result, noPos);
Value result;
expr.eval(state, *env, result);
state.forceValue(result, noPos);
return result;
}
+1 -1
View File
@@ -8,7 +8,7 @@ namespace nix {
struct AbstractNixRepl : NeverAsync
{
typedef std::vector<std::pair<Value*,std::string>> AnnotatedValues;
typedef std::vector<std::pair<Value, std::string>> AnnotatedValues;
static ReplExitStatus
run(const SearchPath & searchPath,
+26 -24
View File
@@ -69,13 +69,12 @@ std::string unparseAttrPath(std::vector<std::string> const & attrPath)
return ret.str();
}
std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::string & attrPath,
Bindings & autoArgs, Value & vIn)
std::pair<Value, PosIdx>
findAlongAttrPath(EvalState & state, const std::string & attrPath, Bindings & autoArgs, Value & vIn)
{
auto tokens = parseAttrPath(attrPath);
Value * v = &vIn;
Value v = vIn;
PosIdx pos = noPos;
for (auto [attrPathIdx, attr] : enumerate(tokens)) {
@@ -84,10 +83,10 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
auto attrIndex = string2Int<unsigned int>(attr);
/* Evaluate the expression. */
Value * vNew = state.ctx.mem.allocValue();
state.autoCallFunction(autoArgs, *v, *vNew, pos);
Value vNew;
state.autoCallFunction(autoArgs, v, vNew, pos);
v = vNew;
state.forceValue(*v, noPos);
state.forceValue(v, noPos);
/* It should evaluate to either a set or an expression,
according to what is specified in the attrPath. */
@@ -96,7 +95,7 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
if (attr.empty())
throw Error("empty attribute name in selection path '%1%'", attrPath);
if (v->type() != nAttrs) {
if (v.type() != nAttrs) {
auto pathPart =
std::vector<std::string>(tokens.begin(), tokens.begin() + attrPathIdx);
state.ctx.errors
@@ -105,17 +104,18 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
"set but is %3%: %4%",
attrPath,
unparseAttrPath(pathPart),
showType(*v),
ValuePrinter(state, *v, errorPrintOptions)
showType(v),
ValuePrinter(state, v, errorPrintOptions)
)
.debugThrow();
}
Bindings::iterator a = v->attrs->find(state.ctx.symbols.create(attr));
if (a == v->attrs->end()) {
auto a = v.attrs()->get(state.ctx.symbols.create(attr));
if (!a) {
std::set<std::string> attrNames;
for (auto & attr : *v->attrs)
attrNames.insert(state.ctx.symbols[attr.name]);
for (auto & attr : *v.attrs()) {
attrNames.emplace(state.ctx.symbols[attr.name]);
}
auto suggestions = Suggestions::bestMatches(attrNames, attr);
auto pathPart =
@@ -127,33 +127,33 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
attr,
attrPath,
unparseAttrPath(pathPart),
ValuePrinter(state, *v, errorPrintOptions)
ValuePrinter(state, v, errorPrintOptions)
);
}
v = &*a->value;
v = a->value;
pos = a->pos;
} else {
if (!v->isList()) {
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)
showType(v),
ValuePrinter(state, v, errorPrintOptions)
)
.debugThrow();
}
if (*attrIndex >= v->listSize()) {
if (*attrIndex >= v.listSize()) {
throw AttrPathNotFound(
"list index %1% in selection path '%2%' is out of range for list %3%",
*attrIndex,
attrPath,
ValuePrinter(state, *v, errorPrintOptions)
ValuePrinter(state, v, errorPrintOptions)
);
}
v = v->listElems()[*attrIndex];
v = v.listElems()[*attrIndex];
pos = noPos;
}
@@ -165,7 +165,7 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what)
{
Value * v2;
Value v2;
try {
auto dummyArgs = state.ctx.mem.allocBindings(0);
v2 = findAlongAttrPath(state, "meta.position", *dummyArgs, v).first;
@@ -176,7 +176,9 @@ std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v
// FIXME: is it possible to extract the Pos object instead of doing this
// toString + parsing?
NixStringContext context;
auto path = state.coerceToPath(noPos, *v2, context, "while evaluating the 'meta.position' attribute of a derivation");
auto path = state.coerceToPath(
noPos, v2, context, "while evaluating the 'meta.position' attribute of a derivation"
);
auto fn = path.canonical().abs();
+3 -5
View File
@@ -10,11 +10,9 @@ namespace nix {
MakeError(AttrPathNotFound, Error);
MakeError(NoPositionInfo, Error);
std::pair<Value *, PosIdx> findAlongAttrPath(
EvalState & state,
const std::string & attrPath,
Bindings & autoArgs,
Value & vIn);
std::pair<Value, PosIdx> findAlongAttrPath(
EvalState & state, const std::string & attrPath, Bindings & autoArgs, Value & vIn
);
/**
* Heuristic to find the filename and lineno or a nix value.
+4 -6
View File
@@ -7,8 +7,7 @@
namespace nix {
Bindings Bindings::EMPTY{0};
Bindings Bindings::EMPTY{};
/* Allocate a new array of attributes for an attribute set with a specific
capacity. The space is implicitly reserved after the Bindings
@@ -21,15 +20,14 @@ Bindings * EvalMemory::allocBindings(size_t capacity)
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) capacity);
return new (allocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) Bindings();
}
Value & BindingsBuilder::alloc(Symbol name, PosIdx pos)
{
auto value = mem.allocValue();
bindings->push_back(Attr(name, value, pos));
return *value;
bindings->push_back(Attr(name, {}, pos));
return (bindings->end() - 1)->value;
}
+19 -22
View File
@@ -23,9 +23,8 @@ struct Attr
way we keep Attr size at two words with no wasted space. */
Symbol name;
PosIdx pos;
Value * value;
Attr(Symbol name, Value * value, PosIdx pos = noPos)
: name(name), pos(pos), value(value) { };
mutable Value value;
Attr(Symbol name, Value value, PosIdx pos = noPos) : name(name), pos(pos), value(value) {}
Attr() { };
bool operator < (const Attr & a) const
{
@@ -44,7 +43,7 @@ static_assert(sizeof(Attr) == 2 * sizeof(uint32_t) + sizeof(Value *),
* elements allocated after this structure, while the size corresponds to
* the number of elements already inserted in this structure.
*/
class Bindings
class alignas(Value::TAG_ALIGN) Bindings
{
public:
using Size = uint32_t;
@@ -53,10 +52,10 @@ public:
static Bindings EMPTY;
private:
Size size_, capacity_;
Size size_ = 0;
Attr attrs[0];
Bindings(Size capacity) : size_(0), capacity_(capacity) { }
Bindings() = default;
Bindings(const Bindings & bindings) = delete;
public:
@@ -68,21 +67,12 @@ public:
void push_back(const Attr & attr)
{
assert(size_ < capacity_);
attrs[size_++] = attr;
}
iterator find(Symbol name)
const Attr * get(Symbol name)
{
Attr key(name, 0);
iterator i = std::lower_bound(begin(), end(), key);
if (i != end() && i->name == name) return i;
return end();
}
Attr * get(Symbol name)
{
Attr key(name, 0);
Attr key(name, {});
iterator i = std::lower_bound(begin(), end(), key);
if (i != end() && i->name == name) return &*i;
return nullptr;
@@ -98,8 +88,6 @@ public:
void sort();
Size capacity() { return capacity_; }
/**
* Returns the attributes in lexicographically sorted order.
*/
@@ -126,21 +114,28 @@ public:
*/
class BindingsBuilder
{
public:
using Size = Bindings::Size;
private:
Bindings * bindings;
EvalMemory & mem;
SymbolTable & symbols;
Size capacity;
public:
// needed by std::back_inserter
using value_type = Attr;
BindingsBuilder(EvalMemory & mem, SymbolTable & symbols, Bindings * bindings)
BindingsBuilder(EvalMemory & mem, SymbolTable & symbols, Bindings * bindings, Size capacity)
: bindings(bindings)
, mem(mem)
, symbols(symbols)
{ }
, capacity(capacity)
{
}
void insert(Symbol name, Value * value, PosIdx pos = noPos)
void insert(Symbol name, Value value, PosIdx pos = noPos)
{
insert(Attr(name, value, pos));
}
@@ -152,6 +147,7 @@ public:
void push_back(const Attr & attr)
{
assert(bindings->size() < capacity);
bindings->push_back(attr);
}
@@ -159,6 +155,7 @@ public:
Value & alloc(std::string_view name, PosIdx pos = noPos);
[[nodiscard("must use created bindings")]]
Bindings * finish()
{
bindings->sort();
+27 -20
View File
@@ -341,7 +341,7 @@ EvalCache::EvalCache(
{
}
Value * EvalCache::getRootValue(EvalState & state)
Value & EvalCache::getRootValue(EvalState & state)
{
if (!value) {
debug("getting root value");
@@ -362,8 +362,9 @@ AttrCursor::AttrCursor(
std::optional<std::pair<AttrId, AttrValue>> && cachedValue)
: root(root), parent(parent), cachedValue(std::move(cachedValue))
{
if (value)
_value = allocRootValue(value);
if (value) {
_value = allocRootValue(*value);
}
}
AttrKey AttrCursor::getKey()
@@ -383,14 +384,14 @@ Value & AttrCursor::getValue(EvalState & state)
if (parent) {
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));
auto attr = vParent.attrs()->get(state.ctx.symbols.create(parent->second));
if (!attr)
throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr(state));
_value = allocRootValue(attr->value);
} else
_value = allocRootValue(root->getRootValue(state));
}
return **_value;
return *_value;
}
std::vector<std::string> AttrCursor::getAttrPath(EvalState & state) const
@@ -437,16 +438,17 @@ Value & AttrCursor::forceValue(EvalState & state)
if (root->db && (!cachedValue || std::get_if<placeholder_t>(&cachedValue->second))) {
if (v.type() == nString)
cachedValue = {root->db->setString(getKey(), v.string.s, v.string.context),
string_t{v.string.s, {}}};
cachedValue = {
root->db->setString(getKey(), v.str(), v.string().context), string_t{v.str(), {}}
};
else if (v.type() == nPath) {
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};
cachedValue = {root->db->setBool(getKey(), v.boolean()), v.boolean()};
else if (v.type() == nInt)
cachedValue = {root->db->setInt(getKey(), v.integer.value), int_t{v.integer}};
cachedValue = {root->db->setInt(getKey(), v.integer().value), int_t{v.integer()}};
else if (v.type() == nAttrs)
; // FIXME: do something?
else
@@ -499,7 +501,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(EvalState & state, const st
return nullptr;
//errors.make<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
auto attr = v.attrs->get(state.ctx.symbols.create(name));
auto attr = v.attrs()->get(state.ctx.symbols.create(name));
if (!attr) {
if (root->db) {
@@ -518,7 +520,8 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(EvalState & state, const st
}
return make_ref<AttrCursor>(
root, std::make_pair(shared_from_this(), name), attr->value, std::move(cachedValue2));
root, std::make_pair(shared_from_this(), name), &attr->value, std::move(cachedValue2)
);
}
ref<AttrCursor> AttrCursor::getAttr(EvalState & state, const std::string & name)
@@ -563,7 +566,7 @@ std::string AttrCursor::getString(EvalState & state)
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();
return v.type() == nString ? std::string(v.str()) : v.path().to_string();
}
string_t AttrCursor::getStringWithContext(EvalState & state)
@@ -605,7 +608,7 @@ string_t AttrCursor::getStringWithContext(EvalState & state)
if (v.type() == nString) {
NixStringContext context;
copyContext(v, context);
return {v.string.s, std::move(context)};
return {std::string(v.str()), std::move(context)};
} else if (v.type() == nPath) {
return {v.path().to_string(), {}};
} else {
@@ -632,7 +635,7 @@ bool AttrCursor::getBool(EvalState & state)
if (v.type() != nBool)
state.ctx.errors.make<TypeError>("'%s' is not a Boolean", getAttrPathStr(state)).debugThrow();
return v.boolean;
return v.boolean();
}
NixInt AttrCursor::getInt(EvalState & state)
@@ -654,7 +657,7 @@ NixInt AttrCursor::getInt(EvalState & state)
if (v.type() != nInt)
state.ctx.errors.make<TypeError>("'%s' is not an integer", getAttrPathStr(state)).debugThrow();
return v.integer;
return v.integer();
}
std::vector<std::string> AttrCursor::getListOfStrings(EvalState & state)
@@ -681,11 +684,15 @@ std::vector<std::string> AttrCursor::getListOfStrings(EvalState & state)
std::vector<std::string> res;
for (auto & elem : v.listItems())
res.push_back(std::string(state.forceStringNoCtx(*elem, noPos, "while evaluating an attribute for caching")));
for (auto & elem : v.listItems()) {
res.push_back(std::string(
state.forceStringNoCtx(elem, noPos, "while evaluating an attribute for caching")
));
}
if (root->db)
if (root->db) {
cachedValue = {root->db->setListOfStrings(getKey(), res), res};
}
return res;
}
@@ -710,8 +717,8 @@ std::vector<std::string> AttrCursor::getAttrs(EvalState & state)
state.ctx.errors.make<TypeError>("'%s' is not an attribute set", getAttrPathStr(state)).debugThrow();
fullattr_t attrs;
for (auto & attr : *getValue(state).attrs)
attrs.p.push_back(state.ctx.symbols[attr.name]);
for (auto & attr : *getValue(state).attrs())
attrs.p.emplace_back(state.ctx.symbols[attr.name]);
std::sort(attrs.p.begin(), attrs.p.end());
if (root->db)
+2 -2
View File
@@ -12,7 +12,7 @@ namespace nix::eval_cache {
struct AttrDb;
class AttrCursor;
typedef std::function<Value *(EvalState &)> RootLoader;
typedef std::function<Value(EvalState &)> RootLoader;
/**
* EvalState with caching support. Historically this was part of EvalState,
@@ -42,7 +42,7 @@ class EvalCache : public std::enable_shared_from_this<EvalCache>
RootLoader rootLoader;
RootValue value;
Value * getRootValue(EvalState & state);
Value & getRootValue(EvalState & state);
public:
+95 -43
View File
@@ -5,59 +5,95 @@
#include "lix/libexpr/eval.hh"
#include "lix/libexpr/eval-error.hh"
#include "lix/libexpr/gc-alloc.hh"
#include "value.hh"
#include <cstdint>
namespace nix {
inline Value::Value(app_t, EvalMemory & mem, Value & lhs, Value & rhs)
{
auto app = static_cast<Value::App *>(mem.allocBytes(sizeof(Value::App) + sizeof(Value *)));
app->_left = lhs;
app->_n = 1;
app->_args[0] = rhs;
raw = tag(tApp, app);
}
inline Value::Value(app_t, EvalMemory & mem, Value & lhs, std::span<Value> args)
{
auto app = static_cast<Value::App *>(mem.allocBytes(sizeof(Value::App) + args.size_bytes()));
app->_left = lhs;
app->_n = args.size();
std::copy(args.begin(), args.end(), app->_args);
raw = tag(tApp, app);
}
inline Value::Value(thunk_t, EvalMemory & mem, Env & env, Expr & expr)
{
auto thunk = mem.allocType<Thunk>();
*thunk = {._env = &env, .expr = &expr};
raw = tag(tThunk, thunk);
}
inline Value::Value(lambda_t, EvalMemory & mem, Env & env, ExprLambda & lambda)
{
auto lp = mem.allocType<Lambda>();
new (lp) Lambda{env, lambda};
raw = tag(tAuxiliary, lp);
}
[[gnu::always_inline]]
Value * EvalMemory::allocValue()
void * EvalMemory::allocBytes(size_t size)
{
#if HAVE_BOEHMGC
/* We use the boehm batch allocator to speed up allocations of Values (of which there are many).
GC_malloc_many returns a linked list of objects of the given size, where the first word
of each object is also the pointer to the next object in the list. This also means that we
have to explicitly clear the first word of every object we take. */
if (!*valueAllocCache) {
*valueAllocCache = GC_malloc_many(sizeof(Value));
if (!*valueAllocCache) throw std::bad_alloc();
}
// NOTE: we purposely do not allocate 0 byte blocks on caches; we never allocate
// zero bytes anyway, and it makes cache index calculation a little bit simpler.
const auto cacheIdx = (size - 1) / CACHE_INCREMENT;
if (cacheIdx < CACHES) {
const auto roundedSize = (cacheIdx + 1) * CACHE_INCREMENT;
auto & cache = gcCache[cacheIdx];
if (!cache) {
cache = GC_malloc_many(roundedSize);
if (!cache) {
throw std::bad_alloc();
}
}
/* GC_NEXT is a convenience macro for accessing the first word of an object.
Take the first list item, advance the list to the next item, and clear the next pointer. */
void * p = *valueAllocCache;
*valueAllocCache = GC_NEXT(p);
GC_NEXT(p) = nullptr;
#else
void * p = gcAllocBytes(sizeof(Value));
/* GC_NEXT is a convenience macro for accessing the first word of an object.
Take the first list item, advance the list to the next item, and clear the next pointer.
*/
void * p = cache;
cache = GC_NEXT(p);
GC_NEXT(p) = nullptr;
return p;
}
#endif
stats.nrValues++;
return static_cast<Value *>(p);
return gcAllocBytes(size);
}
/// `gcAllocType`, but using allocation caches to amortize allocation overhead.
template<typename T>
[[gnu::always_inline]]
T * EvalMemory::allocType(size_t n)
{
// NOLINTNEXTLINE(bugprone-sizeof-expression)
return static_cast<T *>(allocBytes(checkedArrayAllocSize(sizeof(T), n)));
}
[[gnu::always_inline]]
Env & EvalMemory::allocEnv(size_t size)
{
static_assert(CACHES * CACHE_INCREMENT >= sizeof(Env) + sizeof(Value *));
stats.nrEnvs++;
stats.nrValuesInEnvs += size;
Env * env;
#if HAVE_BOEHMGC
if (size == 1) {
/* see allocValue for explanations. */
if (!*env1AllocCache) {
*env1AllocCache = GC_malloc_many(sizeof(Env) + sizeof(Value *));
if (!*env1AllocCache) throw std::bad_alloc();
}
void * p = *env1AllocCache;
*env1AllocCache = GC_NEXT(p);
GC_NEXT(p) = nullptr;
env = static_cast<Env *>(p);
} else
#endif
env = static_cast<Env *>(gcAllocBytes(sizeof(Env) + size * sizeof(Value *)));
Env * env = static_cast<Env *>(allocBytes(sizeof(Env) + size * sizeof(Value *)));
/* We assume that env->values has been cleared by the allocator; maybeThunk() and lookupVar fromWith expect this. */
@@ -69,22 +105,38 @@ Env & EvalMemory::allocEnv(size_t size)
void EvalState::forceValue(Value & v, const PosIdx pos)
{
if (v.isThunk()) {
Env * env = v.thunk.env;
Expr & expr = *v.thunk.expr;
try {
v.mkBlackhole();
expr.eval(*this, *env, v);
} catch (...) {
v.mkThunk(env, expr);
tryFixupBlackHolePos(v, pos);
throw;
auto & thunk = v.thunk();
if (thunk.resolved()) {
v = thunk.result();
} else {
const auto backup = thunk;
Env * env = thunk.env();
Expr & expr = *thunk.expr;
thunk = Value::blackHole;
try {
expr.eval(*this, *env, v);
thunk.resolve(v);
} catch (...) {
thunk = backup;
tryFixupBlackHolePos(v, pos);
throw;
}
}
} else if (v.isApp()) {
auto & app = v.app();
if (app.resolved()) {
v = app.result();
} else {
auto target = app.target();
if (!target.isPrimOp() || target.primOp()->arity <= app.totalArgs()) {
auto tmp = v.app().left();
callFunction(tmp, v.app().args(), v, pos);
app.resolve(v);
}
}
}
else if (v.isApp())
callFunction(*v.app.left, *v.app.right, v, pos);
}
[[gnu::always_inline]]
inline void EvalState::forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx)
{
+1 -1
View File
@@ -82,7 +82,7 @@ bool EvalSettings::isPseudoUrl(std::string_view s)
std::string EvalSettings::resolvePseudoUrl(std::string_view url)
{
if (url.starts_with("channel:"))
return "https://nixos.org/channels/" + std::string(url.substr(8)) + "/nixexprs.tar.xz";
return "https://channels.nixos.org/" + std::string(url.substr(8)) + "/nixexprs.tar.xz";
else
return std::string(url);
}
+533 -398
View File
File diff suppressed because it is too large Load Diff
+21 -68
View File
@@ -36,52 +36,7 @@ namespace eval_cache {
class EvalCache;
}
/**
* Function that implements a primop.
*/
using PrimOpImpl = void(EvalState & state, Value ** args, Value & v);
/**
* Info about a primitive operation, and its implementation
*/
struct PrimOp
{
/**
* Name of the primop. `__` prefix is treated specially.
*/
std::string name;
/**
* Names of the parameters of a primop, for primops that take a
* fixed number of arguments to be substituted for these parameters.
*/
std::vector<std::string> args;
/**
* Aritiy of the primop.
*
* If `args` is not empty, this field will be computed from that
* field instead, so it doesn't need to be manually set.
*/
size_t arity = 0;
/**
* Optional free-form documentation about the primop.
*/
const char * doc = nullptr;
/**
* Implementation of the primop.
*/
std::function<PrimOpImpl> fun;
/**
* Optional experimental for this to be gated on.
*/
std::optional<ExperimentalFeature> experimentalFeature;
};
std::ostream & operator<<(std::ostream & output, PrimOp & primOp);
std::ostream & operator<<(std::ostream & output, const PrimOp & primOp);
/**
* Info about a constant
@@ -106,12 +61,12 @@ struct Constant
bool impureOnly = false;
};
using ValMap = GcMap<std::string, Value *>;
using ValMap = GcMap<std::string, Value>;
struct Env
struct alignas(Value::Acb::TAG_ALIGN) Env
{
Env * up;
Value * values[0];
Value values[0];
};
void printEnvBindings(const EvalState &es, const Expr & expr, const Env & env);
@@ -224,43 +179,44 @@ struct StaticSymbols
class EvalMemory
{
/**
* Allocation cache for GC'd Value objects.
*/
std::shared_ptr<void *> valueAllocCache;
static constexpr size_t CACHES = 8;
static constexpr size_t CACHE_INCREMENT = sizeof(void *);
/**
* Allocation cache for size-1 Env objects.
* Allocation caches for small values.
*/
std::shared_ptr<void *> env1AllocCache;
void * gcCache[CACHES] = {};
public:
struct Statistics
{
unsigned long nrEnvs = 0;
unsigned long nrValuesInEnvs = 0;
unsigned long nrValues = 0;
unsigned long nrAttrsets = 0;
unsigned long nrAttrsInAttrsets = 0;
unsigned long nrListElems = 0;
};
EvalMemory();
~EvalMemory();
EvalMemory(const EvalMemory &) = delete;
EvalMemory(EvalMemory &&) = delete;
EvalMemory & operator=(const EvalMemory &) = delete;
EvalMemory & operator=(EvalMemory &&) = delete;
inline Value * allocValue();
inline void * allocBytes(size_t size);
template<typename T>
inline T * allocType(size_t n = 1);
inline Env & allocEnv(size_t size);
Bindings * allocBindings(size_t capacity);
Value newList(size_t length);
Value::List * newList(size_t length);
BindingsBuilder buildBindings(SymbolTable & symbols, size_t capacity)
{
return BindingsBuilder(*this, symbols, allocBindings(capacity));
return BindingsBuilder(*this, symbols, allocBindings(capacity), capacity);
}
const Statistics getStats() const { return stats; }
@@ -307,11 +263,9 @@ private:
void createBaseEnv(const SearchPath & searchPath, const Path & storeDir);
Value * addConstant(const std::string & name, const Value & v, Constant info);
void addConstant(const std::string & name, const Value & v, Constant info);
void addConstant(const std::string & name, Value * v, Constant info);
Value * addPrimOp(PrimOp && primOp);
void addPrimOp(PrimOpDetails primOp);
Value prepareNixPath(const SearchPath & searchPath);
@@ -839,13 +793,11 @@ public:
bool isFunctor(Value & fun);
// FIXME: use std::span
void callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos);
void callFunction(Value & fun, std::span<Value> args, Value & vRes, const PosIdx pos);
void callFunction(Value & fun, Value & arg, Value & vRes, const PosIdx pos)
{
Value * args[] = {&arg};
callFunction(fun, 1, args, vRes, pos);
callFunction(fun, {&arg, 1}, vRes, pos);
}
/**
@@ -885,7 +837,8 @@ public:
const SingleDerivedPath & p,
Value & v);
void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx);
void
concatLists(Value & v, std::span<Value> lists, const PosIdx pos, std::string_view errorCtx);
private:
+19 -4
View File
@@ -41,14 +41,24 @@ static bool askForSetting(
auto reply = logger->ask(fmt("Do you want to allow configuration setting '%s' to be set to '" ANSI_RED "%s" ANSI_NORMAL "'?\nThis may allow the flake to gain root, see the nix.conf manual page (" ANSI_BOLD "y" ANSI_NORMAL "es/" ANSI_BOLD "n" ANSI_NORMAL "o/" ANSI_BOLD "N" ANSI_NORMAL "o to all) ", name, valueS)).value_or('n');
if (reply == 'N') {
warn("Rejecting all untrusted nix.conf entries");
warn("you can set '%s' to '%b' to automatically reject configuration options supplied by flakes", "accept-flake-config", false);
printTaggedWarning("Rejecting all untrusted nix.conf entries");
printTaggedWarning(
"you can set '%s' to '%b' to automatically reject configuration options supplied by "
"flakes",
"accept-flake-config",
false
);
negativeTrustOverride = true;
} else {
if (std::tolower(reply) == 'y') {
trusted = true;
} else {
warn("you can set '%s' to '%b' to automatically reject configuration options supplied by flakes", "accept-flake-config", false);
printTaggedWarning(
"you can set '%s' to '%b' to automatically reject configuration options supplied "
"by flakes",
"accept-flake-config",
false
);
}
if (std::tolower(logger->ask(fmt("do you want to permanently (in %s) mark this value as %s? (y/N) ", trustedListPath(), trusted ? "trusted": "untrusted" )).value_or('n')) == 'y') {
@@ -117,7 +127,12 @@ void ConfigFile::apply()
debug("accepting trusted flake configuration setting '%s'", name);
globalConfig.set(name, valueS);
} else {
warn("ignoring untrusted flake configuration setting '%s', pass '%s' to trust it (may allow the flake to gain root, see the nix.conf manual page)", name, "--accept-flake-config");
printTaggedWarning(
"ignoring untrusted flake configuration setting '%s', pass '%s' to trust it (may "
"allow the flake to gain root, see the nix.conf manual page)",
name,
"--accept-flake-config"
);
}
}
}
+298 -131
View File
@@ -94,15 +94,67 @@ static void expectType(EvalState & state, ValueType type,
showType(type), showType(value.type()), state.ctx.positions[pos]);
}
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
EvalState & state, Value * value, const PosIdx pos,
const std::optional<Path> & baseDir, InputPath lockRootPath, unsigned depth);
static std::pair<std::map<FlakeId, FlakeInput>, std::optional<fetchers::Attrs>> parseFlakeInputs(
EvalState & state,
Value & value,
const PosIdx pos,
const std::optional<Path> & baseDir,
InputPath lockRootPath,
unsigned depth,
bool allowSelf
);
static FlakeInput parseFlakeInput(EvalState & state,
const std::string & inputName, Value * value, const PosIdx pos,
const std::optional<Path> & baseDir, InputPath lockRootPath, unsigned depth)
static void parseFlakeInputAttr(EvalState & state, const Attr & attr, fetchers::Attrs & attrs)
{
expectType(state, nAttrs, *value, pos);
// Allow selecting a subset of enum values
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
switch (attr.value.type()) {
case nString:
attrs.emplace(state.ctx.symbols[attr.name], std::string(attr.value.str()));
break;
case nBool:
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.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.ctx.symbols[attr.name], asUnsigned);
break;
}
default:
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
}
static FlakeInput parseFlakeInput(
EvalState & state,
const std::string & inputName,
Value & value,
const PosIdx pos,
const std::optional<Path> & baseDir,
InputPath lockRootPath,
unsigned depth
)
{
expectType(state, nAttrs, value, pos);
FlakeInput input;
@@ -114,48 +166,28 @@ static FlakeInput parseFlakeInput(EvalState & state,
fetchers::Attrs attrs;
std::optional<std::string> url;
for (nix::Attr attr : *(value->attrs)) {
for (nix::Attr attr : *(value.attrs())) {
try {
if (attr.name == sUrl) {
expectType(state, nString, *attr.value, attr.pos);
url = attr.value->string.s;
expectType(state, nString, attr.value, attr.pos);
url = attr.value.str();
attrs.emplace("url", *url);
} else if (attr.name == sFlake) {
expectType(state, nBool, *attr.value, attr.pos);
input.isFlake = attr.value->boolean;
expectType(state, nBool, attr.value, attr.pos);
input.isFlake = attr.value.boolean();
} else if (attr.name == sInputs) {
input.overrides = parseFlakeInputs(state, attr.value, attr.pos, baseDir, lockRootPath, depth + 1);
input.overrides =
parseFlakeInputs(
state, attr.value, attr.pos, baseDir, lockRootPath, depth + 1, false
)
.first;
} else if (attr.name == sFollows) {
expectType(state, nString, *attr.value, attr.pos);
auto follows(parseInputPath(attr.value->string.s));
expectType(state, nString, attr.value, attr.pos);
auto follows(parseInputPath(attr.value.str()));
follows.insert(follows.begin(), lockRootPath.begin(), lockRootPath.end());
input.follows = follows;
} else {
// Allow selecting a subset of enum values
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
switch (attr.value->type()) {
case nString:
attrs.emplace(state.ctx.symbols[attr.name], attr.value->string.s);
break;
case nBool:
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.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.ctx.symbols[attr.name], asUnsigned);
break;
}
default:
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
parseFlakeInputAttr(state, attr, attrs);
}
} catch (Error & e) {
e.addTrace(
@@ -190,26 +222,72 @@ static FlakeInput parseFlakeInput(EvalState & state,
return input;
}
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
EvalState & state, Value * value, const PosIdx pos,
const std::optional<Path> & baseDir, InputPath lockRootPath, unsigned depth)
static std::pair<std::map<FlakeId, FlakeInput>, std::optional<fetchers::Attrs>> parseFlakeInputs(
EvalState & state,
Value & value,
const PosIdx pos,
const std::optional<Path> & baseDir,
InputPath lockRootPath,
unsigned depth,
bool allowSelf = true
)
{
std::map<FlakeId, FlakeInput> inputs;
expectType(state, nAttrs, *value, pos);
expectType(state, nAttrs, value, pos);
for (nix::Attr & inputAttr : *(*value).attrs) {
inputs.emplace(state.ctx.symbols[inputAttr.name],
parseFlakeInput(state,
state.ctx.symbols[inputAttr.name],
inputAttr.value,
inputAttr.pos,
baseDir,
lockRootPath,
depth));
std::optional<fetchers::Attrs> selfAttrs = std::nullopt;
for (const nix::Attr & inputAttr : *value.attrs()) {
std::string inputName{state.ctx.symbols[inputAttr.name]};
if (inputName == "self") {
experimentalFeatureSettings.require(Xp::FlakeSelfAttrs);
if (!allowSelf) {
throw Error(
"'self' input attributes not allowed at %s", state.ctx.positions[inputAttr.pos]
);
}
expectType(state, nAttrs, inputAttr.value, inputAttr.pos);
selfAttrs = selfAttrs.value_or(fetchers::Attrs{});
for (auto & attr : *inputAttr.value.attrs()) {
parseFlakeInputAttr(state, attr, *selfAttrs);
}
} else {
inputs.emplace(
inputName,
parseFlakeInput(
state, inputName, inputAttr.value, inputAttr.pos, baseDir, lockRootPath, depth
)
);
}
}
return inputs;
return {inputs, selfAttrs};
}
static std::optional<FlakeRef> applySelfAttrs(const FlakeRef & ref, const Flake & flake)
{
// silently failing here is ok; since the parser requires the feature, we'll
// crash much earlier if it wasn't enabled
if (!flake.selfAttrs.has_value() || !experimentalFeatureSettings.isEnabled(Xp::FlakeSelfAttrs))
{
return std::nullopt;
}
static std::set<std::string> allowedAttrs{"submodules"};
auto newRef(ref);
for (auto & attr : *flake.selfAttrs) {
if (!allowedAttrs.contains(attr.first)) {
throw Error("flake 'self' attribute '%s' is not supported", attr.first);
}
newRef.input.attrs.insert_or_assign(attr.first, attr.second);
}
if (newRef != ref) {
return newRef;
}
return std::nullopt;
}
static Flake getFlake(
@@ -256,26 +334,55 @@ static Flake getFlake(
Value vInfo;
state.eval(flakeExpr, vInfo);
if (auto description = vInfo.attrs->get(state.ctx.s.description)) {
expectType(state, nString, *description->value, description->pos);
flake.description = description->value->string.s;
if (auto description = vInfo.attrs()->get(state.ctx.s.description)) {
expectType(state, nString, description->value, description->pos);
flake.description = description->value.str();
}
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 inputs = vInfo.attrs()->get(sInputs)) {
auto [flakeInputs, selfAttrs] =
parseFlakeInputs(state, inputs->value, inputs->pos, flakeDir, lockRootPath, 0, true);
flake.inputs = std::move(flakeInputs);
flake.selfAttrs = std::move(selfAttrs);
}
if (auto outputs = vInfo.attrs->get(state.ctx.s.outputs)) {
expectType(state, nFunction, *outputs->value, outputs->pos);
auto newLockedRef = applySelfAttrs(lockedRef, flake);
if (newLockedRef.has_value()) {
debug("refetching input '%s' due to self attribute", *newLockedRef);
// FIXME: need to remove attrs that are invalidated by the changed input
// attrs, such as 'narHash'.
newLockedRef->input.attrs.erase("narHash");
auto [sourceInfo2, resolvedRef2, lockedRef2] =
state.aio.blockOn(fetchOrSubstituteTree(state.ctx, *newLockedRef, false, flakeCache));
if (outputs->value->isLambda()) {
if (auto pattern = dynamic_cast<AttrsPattern *>(outputs->value->lambda.fun->pattern.get()); pattern) {
lockedRef = lockedRef2;
flake.lockedRef = lockedRef;
sourceInfo = sourceInfo2;
flake.sourceInfo = std::make_shared<fetchers::Tree>(std::move(sourceInfo));
resolvedRef = resolvedRef2;
flake.resolvedRef = resolvedRef;
}
if (auto outputs = vInfo.attrs()->get(state.ctx.s.outputs)) {
expectType(state, nFunction, outputs->value, outputs->pos);
if (outputs->value.isLambda()) {
if (auto pattern =
dynamic_cast<AttrsPattern *>(outputs->value.lambda().fun->pattern.get());
pattern)
{
for (auto & formal : pattern->formals) {
if (formal.name != state.ctx.s.self)
flake.inputs.emplace(state.ctx.symbols[formal.name], FlakeInput {
.ref = parseFlakeRef(state.ctx.symbols[formal.name])
});
flake.inputs.emplace(
state.ctx.symbols[formal.name],
FlakeInput{
.ref = parseFlakeRef(std::string(state.ctx.symbols[formal.name]))
}
);
}
}
}
@@ -285,46 +392,71 @@ static Flake getFlake(
auto sNixConfig = state.ctx.symbols.create("nixConfig");
if (auto nixConfig = vInfo.attrs->get(sNixConfig)) {
expectType(state, nAttrs, *nixConfig->value, nixConfig->pos);
if (auto nixConfig = vInfo.attrs()->get(sNixConfig)) {
expectType(state, nAttrs, nixConfig->value, nixConfig->pos);
for (auto & setting : *nixConfig->value->attrs) {
forceTrivialValue(state, *setting.value, setting.pos);
if (setting.value->type() == nString)
for (auto & setting : *nixConfig->value.attrs()) {
forceTrivialValue(state, setting.value, setting.pos);
if (setting.value.type() == nString) {
flake.config.settings.emplace(
state.ctx.symbols[setting.name],
std::string(state.forceStringNoCtx(*setting.value, setting.pos, "")));
else if (setting.value->type() == nPath) {
std::string(state.forceStringNoCtx(setting.value, setting.pos, ""))
);
} else if (setting.value.type() == nPath) {
NixStringContext emptyContext = {};
flake.config.settings.emplace(
state.ctx.symbols[setting.name],
state.coerceToString(setting.pos, *setting.value, emptyContext, "", StringCoercionMode::Strict, true, true) .toOwned());
}
else if (setting.value->type() == nInt)
state
.coerceToString(
setting.pos,
setting.value,
emptyContext,
"",
StringCoercionMode::Strict,
true,
true
)
.toOwned()
);
} else if (setting.value.type() == nInt) {
flake.config.settings.emplace(
state.ctx.symbols[setting.name],
state.forceInt(*setting.value, setting.pos, "").value);
else if (setting.value->type() == nBool)
state.forceInt(setting.value, setting.pos, "").value
);
} else if (setting.value.type() == nBool) {
flake.config.settings.emplace(
state.ctx.symbols[setting.name],
Explicit<bool> { state.forceBool(*setting.value, setting.pos, "") });
else if (setting.value->type() == nList) {
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.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, ""));
for (auto & elem : setting.value.listItems()) {
if (elem.type() != nString) {
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.ctx.symbols[setting.name], ss);
} else {
state.ctx.errors
.make<TypeError>(
"flake configuration setting '%s' is %s",
state.ctx.symbols[setting.name],
showType(setting.value)
)
.debugThrow();
}
else
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) {
for (auto & attr : *vInfo.attrs()) {
if (attr.name != state.ctx.s.description &&
attr.name != sInputs &&
attr.name != state.ctx.s.outputs &&
@@ -457,9 +589,11 @@ LockedFlake lockFlake(
auto follow = inputPath2.back();
inputPath2.pop_back();
if (inputPath2 == inputPathPrefix && !flakeInputs.count(follow))
warn(
printTaggedWarning(
"input '%s' has an override for a non-existent input '%s'",
printInputPath(inputPathPrefix), follow);
printInputPath(inputPathPrefix),
follow
);
}
/* Go over the flake inputs, resolve/fetch them if
@@ -670,12 +804,17 @@ LockedFlake lockFlake(
for (auto & i : lockFlags.inputOverrides)
if (!overridesUsed.count(i.first))
warn("the flag '--override-input %s %s' does not match any input",
printInputPath(i.first), i.second);
printTaggedWarning(
"the flag '--override-input %s %s' does not match any input",
printInputPath(i.first),
i.second
);
for (auto & i : lockFlags.inputUpdates)
if (!updatesUsed.count(i))
warn("'%s' does not match any input of this flake", printInputPath(i));
printTaggedWarning(
"'%s' does not match any input of this flake", printInputPath(i)
);
/* Check 'follows' inputs. */
newLockFile.check();
@@ -693,7 +832,12 @@ LockedFlake lockFlake(
if (sourcePath || lockFlags.outputLockFilePath) {
if (auto unlockedInput = newLockFile.isUnlocked()) {
if (fetchSettings.warnDirty)
warn("will not write lock file of flake '%s' because it has an unlocked input ('%s')", topRef, *unlockedInput);
printTaggedWarning(
"will not write lock file of flake '%s' because it has an unlocked "
"input ('%s')",
topRef,
*unlockedInput
);
} else {
if (!lockFlags.updateLockFile)
throw Error("flake '%s' requires lock file changes but they're not allowed due to '--no-update-lock-file'", topRef);
@@ -713,11 +857,19 @@ LockedFlake lockFlake(
auto s = chomp(diff);
if (lockFileExists) {
if (s.empty())
warn("updating lock file '%s'", outputLockFilePath);
printTaggedWarning(
"updating lock file '%s'", outputLockFilePath
);
else
warn("updating lock file '%s':\n%s", outputLockFilePath, Uncolored(s));
printTaggedWarning(
"updating lock file '%s':\n%s",
outputLockFilePath,
Uncolored(s)
);
} else
warn("creating lock file '%s':\n%s", outputLockFilePath, Uncolored(s));
printTaggedWarning(
"creating lock file '%s':\n%s", outputLockFilePath, Uncolored(s)
);
std::optional<std::string> commitMessage = std::nullopt;
@@ -735,9 +887,13 @@ LockedFlake lockFlake(
commitMessage = cm;
}
topRef.input.putFile(
CanonPath((topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock"),
newLockFileS, commitMessage);
state.aio.blockOn(topRef.input.putFile(
CanonPath(
(topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock"
),
newLockFileS,
commitMessage
));
}
/* Rewriting the lockfile changed the top-level
@@ -750,7 +906,10 @@ LockedFlake lockFlake(
if (lockFlags.commitLockFile &&
flake.lockedRef.input.getRev() &&
prevLockedRef.input.getRev() != flake.lockedRef.input.getRev())
warn("committed new revision '%s'", flake.lockedRef.input.getRev()->gitRev());
printTaggedWarning(
"committed new revision '%s'",
flake.lockedRef.input.getRev()->gitRev()
);
/* Make sure that we picked up the change,
i.e. the tree should usually be dirty
@@ -763,7 +922,9 @@ LockedFlake lockFlake(
} else
throw Error("cannot write modified lock file of flake '%s' (use '--no-write-lock-file' to ignore)", topRef);
} else {
warn("not writing modified lock file of flake '%s':\n%s", topRef, chomp(diff));
printTaggedWarning(
"not writing modified lock file of flake '%s':\n%s", topRef, chomp(diff)
);
flake.forceDirty = true;
}
}
@@ -780,34 +941,39 @@ void callFlake(EvalState & state,
const LockedFlake & lockedFlake,
Value & vRes)
{
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();
Value vLocks;
Value vRootSrc;
Value vRootSubdir;
Value vTmp1;
Value vTmp2;
vLocks->mkString(lockedFlake.lockFile.to_string());
vLocks.mkString(lockedFlake.lockFile.to_string());
emitTreeAttrs(
state.ctx,
*lockedFlake.flake.sourceInfo,
lockedFlake.flake.lockedRef.input,
*vRootSrc,
vRootSrc,
false,
lockedFlake.flake.forceDirty);
lockedFlake.flake.forceDirty
);
vRootSubdir->mkString(lockedFlake.flake.lockedRef.subdir);
vRootSubdir.mkString(lockedFlake.flake.lockedRef.subdir);
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.ctx.caches.vCallFlake);
state.ctx.caches.vCallFlake = allocRootValue({});
state.eval(
state.ctx.parseExprFromString(
#include "call-flake.nix.gen.hh"
, CanonPath::root
),
*state.ctx.caches.vCallFlake
);
}
state.callFunction(**state.ctx.caches.vCallFlake, *vLocks, *vTmp1, noPos);
state.callFunction(*vTmp1, *vRootSrc, *vTmp2, noPos);
state.callFunction(*vTmp2, *vRootSubdir, vRes, noPos);
state.callFunction(*state.ctx.caches.vCallFlake, vLocks, vTmp1, noPos);
state.callFunction(vTmp1, vRootSrc, vTmp2, noPos);
state.callFunction(vTmp2, vRootSubdir, vRes, noPos);
}
void prim_getFlake(EvalState & state, Value * * args, Value & v)
@@ -857,10 +1023,10 @@ void prim_flakeRefToString(
state.forceAttrs(*args[0], noPos,
"while evaluating the argument passed to builtins.flakeRefToString");
fetchers::Attrs attrs;
for (const auto & attr : *args[0]->attrs) {
auto t = attr.value->type();
for (const auto & attr : *args[0]->attrs()) {
auto t = attr.value.type();
if (t == nInt) {
auto intValue = attr.value->integer.value;
auto intValue = attr.value.integer().value;
if (intValue < 0) {
state.ctx.errors.make<EvalError>("negative value given for flake ref attr %1%: %2%", state.ctx.symbols[attr.name], intValue).debugThrow();
@@ -869,17 +1035,18 @@ void prim_flakeRefToString(
attrs.emplace(state.ctx.symbols[attr.name], asUnsigned);
} else if (t == nBool) {
attrs.emplace(state.ctx.symbols[attr.name],
Explicit<bool> { attr.value->boolean });
attrs.emplace(state.ctx.symbols[attr.name], Explicit<bool>{attr.value.boolean()});
} else if (t == nString) {
attrs.emplace(state.ctx.symbols[attr.name],
std::string(attr.value->str()));
attrs.emplace(state.ctx.symbols[attr.name], std::string(attr.value.str()));
} else {
state.ctx.errors.make<EvalError>(
"flake reference attribute sets may only contain integers, Booleans, "
"and strings, but attribute '%s' is %s",
state.ctx.symbols[attr.name],
showType(*attr.value)).debugThrow();
state.ctx.errors
.make<EvalError>(
"flake reference attribute sets may only contain integers, Booleans, "
"and strings, but attribute '%s' is %s",
state.ctx.symbols[attr.name],
showType(attr.value)
)
.debugThrow();
}
}
auto flakeRef = FlakeRef::fromAttrs(attrs);
+14
View File
@@ -72,25 +72,39 @@ struct Flake
* The original flake specification (by the user)
*/
FlakeRef originalRef;
/**
* registry references and caching resolved to the specific underlying flake
*/
FlakeRef resolvedRef;
/**
* the specific local store result of invoking the fetcher
*/
FlakeRef lockedRef;
/**
* pretend that 'lockedRef' is dirty
*/
bool forceDirty = false;
std::optional<std::string> description;
std::shared_ptr<const fetchers::Tree> sourceInfo;
FlakeInputs inputs;
/**
* Attributes to be retroactively applied to the `self` input
* (such as `submodules = true`).
*/
std::optional<fetchers::Attrs> selfAttrs;
/**
* 'nixConfig' attribute
*/
ConfigFile config;
~Flake();
};
+2 -2
View File
@@ -6,13 +6,13 @@ namespace nix {
FunctionCallTrace::FunctionCallTrace(const Pos & pos) : pos(pos) {
auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration);
printMsg(lvlInfo, "function-trace entered %1% at %2%", pos, ns.count());
printMsg(lvlInfo, "function-trace entered %1% at %2%", Uncolored(pos), Uncolored(ns.count()));
}
FunctionCallTrace::~FunctionCallTrace() {
auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration);
printMsg(lvlInfo, "function-trace exited %1% at %2%", pos, ns.count());
printMsg(lvlInfo, "function-trace exited %1% at %2%", Uncolored(pos), Uncolored(ns.count()));
}
}
+19 -15
View File
@@ -95,6 +95,24 @@ inline void * gcAllocBytes(size_t n)
return ptr;
}
[[gnu::always_inline]]
inline size_t checkedArrayAllocSize(size_t size, size_t howMany)
{
// NOTE: size_t * size_t, which can definitely overflow.
// Unsigned integer overflow is definitely a bug, but isn't undefined
// behavior, so we can just check if we overflowed after the fact.
// However, people can and do request zero sized allocations, so we need
// to check that neither of our multiplicands were zero before complaining
// about it.
auto checkedSz = checked::Checked<size_t>(howMany) * size;
if (checkedSz.overflowed()) {
// Congrats, you done did an overflow.
throw std::bad_alloc();
}
return checkedSz.valueWrapping();
}
/// Typed, safe wrapper around calloc() (transparently GC-enabled). Allocates
/// enough for the requested count of the specified type. Also checks for
/// nullptr (and throws @ref std::bad_alloc), and casts the void pointer to
@@ -103,21 +121,7 @@ template<typename T>
[[gnu::always_inline]]
inline T * gcAllocType(size_t howMany = 1)
{
// NOTE: size_t * size_t, which can definitely overflow.
// Unsigned integer overflow is definitely a bug, but isn't undefined
// behavior, so we can just check if we overflowed after the fact.
// However, people can and do request zero sized allocations, so we need
// to check that neither of our multiplicands were zero before complaining
// about it.
// NOLINTNEXTLINE(bugprone-sizeof-expression): yeah we only seem to alloc pointers with this. the calculation *is* correct though!
auto checkedSz = checked::Checked<size_t>(howMany) * sizeof(T);
size_t sz = checkedSz.valueWrapping();
if (checkedSz.overflowed()) {
// Congrats, you done did an overflow.
throw std::bad_alloc();
}
return static_cast<T *>(gcAllocBytes(sz));
return static_cast<T *>(gcAllocBytes(checkedArrayAllocSize(sizeof(T), howMany)));
}
/// GC-transparently allocates a buffer for a C-string of @ref size *bytes*,
+2 -2
View File
@@ -17,8 +17,8 @@ using SmallVector = boost::container::small_vector<T, nItems, TraceableAllocator
/**
* A vector of value pointers. See `SmallVector`.
*/
template <size_t nItems>
using SmallValueVector = SmallVector<Value *, nItems>;
template<size_t nItems>
using SmallValueVector = SmallVector<Value, nItems>;
/**
* A vector of values that must not be referenced after the vector is destroyed.
+112 -85
View File
@@ -64,9 +64,13 @@ try {
std::string DrvInfo::queryName(EvalState & state)
{
if (name == "" && attrs) {
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");
auto i = attrs->get(state.ctx.s.name);
if (!i) {
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;
}
@@ -75,8 +79,12 @@ std::string DrvInfo::queryName(EvalState & state)
std::string DrvInfo::querySystem(EvalState & state)
{
if (system == "" && attrs) {
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");
auto i = attrs->get(state.ctx.s.system);
system = !i
? "unknown"
: state.forceStringNoCtx(
i->value, i->pos, "while evaluating the 'system' attribute of a derivation"
);
}
return system;
}
@@ -85,12 +93,18 @@ std::string DrvInfo::querySystem(EvalState & state)
std::optional<StorePath> DrvInfo::queryDrvPath(EvalState & state)
{
if (!drvPath && attrs) {
Bindings::iterator i = attrs->find(state.ctx.s.drvPath);
auto i = attrs->get(state.ctx.s.drvPath);
NixStringContext context;
if (i == attrs->end())
if (!i) {
drvPath = {std::nullopt};
else
drvPath = {state.coerceToStorePath(i->pos, *i->value, context, "while evaluating the 'drvPath' attribute of a derivation")};
} else {
drvPath = {state.coerceToStorePath(
i->pos,
i->value,
context,
"while evaluating the 'drvPath' attribute of a derivation"
)};
}
}
return drvPath.value_or(std::nullopt);
}
@@ -107,10 +121,13 @@ StorePath DrvInfo::requireDrvPath(EvalState & state)
StorePath DrvInfo::queryOutPath(EvalState & state)
{
if (!outPath && attrs) {
Bindings::iterator i = attrs->find(state.ctx.s.outPath);
auto i = attrs->get(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");
if (i) {
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");
@@ -133,7 +150,7 @@ void DrvInfo::fillOutputs(EvalState & state, bool withPaths)
return;
}
Attr * outputs = this->attrs->get(state.ctx.s.outputs);
const Attr * outputs = this->attrs->get(state.ctx.s.outputs);
if (outputs == nullptr) {
fillDefault();
return;
@@ -141,27 +158,21 @@ void DrvInfo::fillOutputs(EvalState & state, bool withPaths)
// NOTE(Qyriad): I don't think there is any codepath that can cause this to error.
state.forceList(
*outputs->value,
outputs->pos,
"while evaluating the 'outputs' attribute of a derivation"
outputs->value, outputs->pos, "while evaluating the 'outputs' attribute of a derivation"
);
for (auto [idx, elem] : enumerate(outputs->value->listItems())) {
for (auto && [idx, elem] : enumerate(outputs->value.listItems())) {
// NOTE(Qyriad): This error should be *extremely* rare in practice.
// It is impossible to construct with `stdenv.mkDerivation`,
// `builtins.derivation`, or even `derivationStrict`. As far as we can tell,
// 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(
*elem,
outputs->pos,
errMsg
);
std::string_view outputName = state.forceStringNoCtx(elem, outputs->pos, errMsg);
if (withPaths) {
// Find the attr with this output's name...
Attr * out = this->attrs->get(state.ctx.symbols.create(outputName));
const Attr * out = this->attrs->get(state.ctx.symbols.create(outputName));
if (out == nullptr) {
// FIXME: throw error?
continue;
@@ -169,10 +180,10 @@ void DrvInfo::fillOutputs(EvalState & state, 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(state.ctx.s.outPath);
const Attr * outPath = out->value.attrs()->get(state.ctx.s.outPath);
if (outPath == nullptr) {
continue;
// FIXME: throw error?
@@ -181,12 +192,7 @@ void DrvInfo::fillOutputs(EvalState & state, bool withPaths)
NixStringContext context;
// And idk what could possibly cause this one to error
// that wouldn't error before here.
auto storePath = state.coerceToStorePath(
outPath->pos,
*outPath->value,
context,
errMsg
);
auto storePath = state.coerceToStorePath(outPath->pos, outPath->value, context, errMsg);
this->outputs.emplace(outputName, storePath);
} else {
this->outputs.emplace(outputName, std::nullopt);
@@ -216,9 +222,9 @@ DrvInfo::Outputs DrvInfo::queryOutputs(EvalState & state, bool withPaths, bool o
// 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.ctx.s.outputSpecified)) {
if (const Attr * outSpecAttr = attrs->get(state.ctx.s.outputSpecified)) {
bool outputSpecified = state.forceBool(
*outSpecAttr->value,
outSpecAttr->value,
outSpecAttr->pos,
"while evaluating the 'outputSpecified' attribute of a derivation"
);
@@ -238,14 +244,17 @@ DrvInfo::Outputs DrvInfo::queryOutputs(EvalState & state, bool withPaths, bool o
/* ^ this shows during `nix-env -i` right under the bad derivation */
if (!outTI->isList()) throw Error(errMsg + "expected a list but got %s", Uncolored(showType(outTI->type())));
Outputs result;
for (auto elem : outTI->listItems()) {
if (elem->type() != nString)
for (auto & elem : outTI->listItems()) {
if (elem.type() != nString) {
throw Error(
errMsg + "element is %s where a string was expected",
Uncolored(showType(elem->type()))
Uncolored(showType(elem.type()))
);
auto out = outputs.find(elem->string.s);
if (out == outputs.end()) throw Error(errMsg + "output '%s' does not exist", elem->string.s);
}
auto out = outputs.find(std::string(elem.str()));
if (out == outputs.end()) {
throw Error(errMsg + "output '%s' does not exist", elem.str());
}
result.insert(*out);
}
return result;
@@ -255,8 +264,11 @@ DrvInfo::Outputs DrvInfo::queryOutputs(EvalState & state, bool withPaths, bool o
std::string DrvInfo::queryOutputName(EvalState & state)
{
if (outputName == "" && attrs) {
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") : "";
auto i = attrs->get(state.ctx.s.outputName);
outputName = i ? state.forceStringNoCtx(
i->value, noPos, "while evaluating the output name of a derivation"
)
: "";
}
return outputName;
}
@@ -266,10 +278,12 @@ Bindings * DrvInfo::getMeta(EvalState & state)
{
if (meta) return meta;
if (!attrs) return 0;
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");
meta = a->value->attrs;
auto a = attrs->get(state.ctx.s.meta);
if (!a) {
return 0;
}
state.forceAttrs(a->value, a->pos, "while evaluating the 'meta' attribute of a derivation");
meta = a->value.attrs();
return meta;
}
@@ -288,15 +302,23 @@ bool DrvInfo::checkMeta(EvalState & state, Value & v)
{
state.forceValue(v, noPos);
if (v.type() == nList) {
for (auto elem : v.listItems())
if (!checkMeta(state, *elem)) return false;
for (auto & elem : v.listItems()) {
if (!checkMeta(state, elem)) {
return false;
}
}
return true;
}
else if (v.type() == nAttrs) {
Bindings::iterator i = v.attrs->find(state.ctx.s.outPath);
if (i != v.attrs->end()) return false;
for (auto & i : *v.attrs)
if (!checkMeta(state, *i.value)) return false;
auto i = v.attrs()->get(state.ctx.s.outPath);
if (i) {
return false;
}
for (auto & i : *v.attrs()) {
if (!checkMeta(state, i.value)) {
return false;
}
}
return true;
}
else return v.type() == nInt || v.type() == nBool || v.type() == nString ||
@@ -307,9 +329,11 @@ bool DrvInfo::checkMeta(EvalState & state, Value & v)
Value * DrvInfo::queryMeta(EvalState & state, const std::string & name)
{
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;
auto a = meta->get(state.ctx.symbols.create(name));
if (!a || !checkMeta(state, a->value)) {
return 0;
}
return &a->value;
}
@@ -317,7 +341,7 @@ std::string DrvInfo::queryMetaString(EvalState & state, const std::string & name
{
Value * v = queryMeta(state, name);
if (!v || v->type() != nString) return "";
return v->string.s;
return std::string(v->str());
}
@@ -325,12 +349,15 @@ NixInt DrvInfo::queryMetaInt(EvalState & state, const std::string & name, NixInt
{
Value * v = queryMeta(state, name);
if (!v) return def;
if (v->type() == nInt) return v->integer;
if (v->type() == nInt) {
return v->integer();
}
if (v->type() == nString) {
/* Backwards compatibility with before we had support for
integer meta fields. */
if (auto n = string2Int<NixInt::Inner>(v->string.s))
if (auto n = string2Int<NixInt::Inner>(v->str())) {
return NixInt{*n};
}
}
return def;
}
@@ -339,18 +366,23 @@ bool DrvInfo::queryMetaBool(EvalState & state, const std::string & name, bool de
{
Value * v = queryMeta(state, name);
if (!v) return def;
if (v->type() == nBool) return v->boolean;
if (v->type() == nBool) {
return v->boolean();
}
if (v->type() == nString) {
/* Backwards compatibility with before we had support for
Boolean meta fields. */
if (strcmp(v->string.s, "true") == 0) return true;
if (strcmp(v->string.s, "false") == 0) return false;
if (v->str() == "true") {
return true;
}
if (v->str() == "false") {
return false;
}
}
return def;
}
void DrvInfo::setMeta(EvalState & state, const std::string & name, Value * v)
void DrvInfo::setMeta(EvalState & state, const std::string & name, Value & v)
{
getMeta(state);
auto attrs = state.ctx.buildBindings(1 + (meta ? meta->size() : 0));
@@ -359,7 +391,7 @@ void DrvInfo::setMeta(EvalState & state, const std::string & name, Value * v)
for (auto i : *meta)
if (i.name != sym)
attrs.insert(i);
if (v) attrs.insert(sym, v);
attrs.insert(sym, v);
meta = attrs.finish();
}
@@ -378,7 +410,7 @@ static bool getDerivation(EvalState & state, Value & v,
state.forceValue(v, noPos);
if (!state.isDerivation(v)) return true;
DrvInfo drv(attrPath, v.attrs);
DrvInfo drv(attrPath, v.attrs());
drv.queryName(state);
@@ -402,10 +434,9 @@ std::optional<DrvInfo> getDerivation(EvalState & state, Value & v,
return std::move(drvs.front());
}
static std::string addToPath(const std::string & s1, const std::string & s2)
static std::string addToPath(std::string_view s1, std::string_view s2)
{
return s1.empty() ? s2 : s1 + "." + s2;
return s1.empty() ? std::string(s2) : fmt("%s.%s", s1, s2);
}
@@ -432,19 +463,13 @@ static void getDerivations(EvalState & state, Value & vIn, PosIdx pos,
if (v.type() == nList) {
// NOTE we can't really deduplicate here because small lists don't have stable addresses
// and can cause spurious duplicate detections due to v being on the stack.
for (auto [n, elem] : enumerate(v.listItems())) {
for (auto && [n, elem] : enumerate(v.listItems())) {
std::string joinedAttrPath = addToPath(pathPrefix, fmt("%d", n));
bool shouldRecurse = getDerivation(state, *elem, joinedAttrPath, drvs, ignoreAssertionFailures);
bool shouldRecurse =
getDerivation(state, elem, joinedAttrPath, drvs, ignoreAssertionFailures);
if (shouldRecurse) {
getDerivations(
state,
*elem,
pos,
joinedAttrPath,
autoArgs,
drvs,
done,
ignoreAssertionFailures
state, elem, pos, joinedAttrPath, autoArgs, drvs, done, ignoreAssertionFailures
);
}
}
@@ -459,7 +484,7 @@ static void getDerivations(EvalState & state, Value & vIn, PosIdx pos,
/* Dont consider sets we've already seen, e.g. y in
`rec { x.d = derivation {...}; y = x; }`. */
auto const &[_, didInsert] = done.insert(v.attrs);
auto const &[_, didInsert] = done.insert(v.attrs());
if (!didInsert) {
return;
}
@@ -467,14 +492,14 @@ static void getDerivations(EvalState & state, Value & vIn, PosIdx pos,
// FIXME: what the fuck???
/* !!! undocumented hackery to support combining channels in
nix-env.cc. */
bool combineChannels = v.attrs->find(state.ctx.symbols.create("_combineChannels")) != v.attrs->end();
bool combineChannels = v.attrs()->get(state.ctx.symbols.create("_combineChannels"));
/* 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.ctx.symbols)) {
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.ctx.symbols[attr->name]), attrRegex)) {
@@ -484,7 +509,7 @@ static void getDerivations(EvalState & state, Value & vIn, PosIdx pos,
if (combineChannels) {
getDerivations(
state,
*attr->value,
attr->value,
attr->pos,
joinedAttrPath,
autoArgs,
@@ -492,17 +517,19 @@ static void getDerivations(EvalState & state, Value & vIn, PosIdx pos,
done,
ignoreAssertionFailures
);
} else if (getDerivation(state, *attr->value, joinedAttrPath, drvs, ignoreAssertionFailures)) {
} else if (getDerivation(state, attr->value, joinedAttrPath, drvs, ignoreAssertionFailures))
{
/* If the value of this attribute is itself a set,
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.ctx.s.recurseForDerivations);
if (attr->value.type() == nAttrs) {
const Attr * recurseForDrvs =
attr->value.attrs()->get(state.ctx.s.recurseForDerivations);
if (recurseForDrvs == nullptr) {
continue;
}
bool shouldRecurse = state.forceBool(
*recurseForDrvs->value,
recurseForDrvs->value,
attr->pos,
fmt("while evaluating the '%s' attribute", Magenta("recurseForDerivations"))
);
@@ -512,7 +539,7 @@ static void getDerivations(EvalState & state, Value & vIn, PosIdx pos,
getDerivations(
state,
*attr->value,
attr->value,
attr->pos,
joinedAttrPath,
autoArgs,
+1 -1
View File
@@ -73,7 +73,7 @@ public:
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);
void setMeta(EvalState & state, const std::string & name, Value & v);
/*
MetaInfo queryMetaInfo(EvalState & state) const;
+39 -28
View File
@@ -1,4 +1,5 @@
#include "lix/libexpr/json-to-value.hh"
#include "gc-alloc.hh"
#include "lix/libexpr/value.hh"
#include "lix/libexpr/eval.hh"
#include "lix/libutil/json.hh"
@@ -7,11 +8,6 @@
namespace nix {
/*
* Used for `JSONObjectState`
*/
using ValueMap = GcMap<Symbol, Value *>;
// for more information, refer to
// https://github.com/nlohmann/json/blob/master/include/nlohmann/detail/input/json_sax.hpp
class JSONSax : nlohmann::json_sax<JSON> {
@@ -25,13 +21,14 @@ class JSONSax : nlohmann::json_sax<JSON> {
assert(false && "tried to close toplevel json parser state");
}
explicit JSONState(std::unique_ptr<JSONState> && p) : parent(std::move(p)) {}
explicit JSONState(Value * v) : v(allocRootValue(v)) {}
JSONState() = default;
JSONState(JSONState & p) = delete;
Value & value(EvalState & state)
Value & value()
{
if (!v)
v = allocRootValue(state.ctx.mem.allocValue());
return **v;
if (!v) {
v = allocRootValue({});
}
return *v;
}
virtual ~JSONState() {}
virtual void add() {}
@@ -39,35 +36,41 @@ class JSONSax : nlohmann::json_sax<JSON> {
class JSONObjectState : public JSONState {
using JSONState::JSONState;
ValueMap attrs;
GcMap<Symbol, Value> attrs;
Symbol _key;
std::unique_ptr<JSONState> resolve(EvalState & state) override
{
auto attrs2 = state.ctx.buildBindings(attrs.size());
for (auto & i : attrs)
attrs2.insert(i.first, i.second);
parent->value(state).mkAttrs(attrs2.alreadySorted());
parent->value().mkAttrs(attrs2.alreadySorted());
return std::move(parent);
}
void add() override { v = nullptr; }
void add() override
{
attrs.insert_or_assign(_key, value());
v = nullptr;
}
public:
void key(string_t & name, EvalState & state)
{
attrs.insert_or_assign(state.ctx.symbols.create(name), &value(state));
_key = state.ctx.symbols.create(name);
}
};
class JSONListState : public JSONState {
ValueVector values;
GcVector<Value> values;
std::unique_ptr<JSONState> resolve(EvalState & state) override
{
Value & v = parent->value(state);
v = state.ctx.mem.newList(values.size());
auto list = state.ctx.mem.newList(values.size());
parent->value() = {NewValueAs::list, list};
for (size_t n = 0; n < values.size(); ++n) {
v.listElems()[n] = values[n];
list->elems[n] = values[n];
}
return std::move(parent);
}
void add() override {
void add() override
{
values.push_back(*v);
v = nullptr;
}
@@ -82,25 +85,30 @@ class JSONSax : nlohmann::json_sax<JSON> {
std::unique_ptr<JSONState> rs;
public:
JSONSax(EvalState & state, Value & v) : state(state), rs(new JSONState(&v)) {};
JSONSax(EvalState & state) : state(state), rs(new JSONState()) {};
Value result()
{
return rs->value();
}
bool null() override
{
rs->value(state).mkNull();
rs->value().mkNull();
rs->add();
return true;
}
bool boolean(bool val) override
{
rs->value(state).mkBool(val);
rs->value().mkBool(val);
rs->add();
return true;
}
bool number_integer(number_integer_t val) override
{
rs->value(state).mkInt(val);
rs->value().mkInt(val);
rs->add();
return true;
}
@@ -108,24 +116,26 @@ public:
bool number_unsigned(number_unsigned_t val_) override
{
if (val_ > std::numeric_limits<NixInt::Inner>::max()) {
throw Error("unsigned json number %1% outside of Nix integer range", val_);
// Parse as a float for consistency with signed integers
// and interoperability with JSONs single numeric type.
return number_float(static_cast<number_float_t>(val_), "");
}
NixInt::Inner val = val_;
rs->value(state).mkInt(val);
rs->value().mkInt(val);
rs->add();
return true;
}
bool number_float(number_float_t val, const string_t & s) override
{
rs->value(state).mkFloat(val);
rs->value().mkFloat(val);
rs->add();
return true;
}
bool string(string_t & val) override
{
rs->value(state).mkString(val);
rs->value().mkString(val);
rs->add();
return true;
}
@@ -176,10 +186,11 @@ public:
void parseJSON(EvalState & state, const std::string_view & s_, Value & v)
{
JSONSax parser(state, v);
JSONSax parser(state);
bool res = JSON::sax_parse(s_, &parser);
if (!res)
throw JSONParseError("Invalid JSON Value");
v = parser.result();
}
}
+2 -1
View File
@@ -5,5 +5,6 @@ includedir=@includedir@
Name: Lix libexpr
Description: Lix Package Manager (libexpr)
Version: @PACKAGE_VERSION@
Requires: lix-base lix-util lix-fetchers lix-store bdw-gc
# dependencies on boost is omitted since it is optional (only required by some headers)
Requires: lix-base lix-util lix-store lix-fetchers @BOEHM_IF_FOUND@
Libs: -L${libdir} -llixexpr
+48 -27
View File
@@ -270,32 +270,59 @@ libexpr_headers = files(
# keep-sorted end
)
libexpr = library(
'lixexpr',
dependencies = [
liblixutil,
liblixstore,
liblixfetchers,
boehm,
boost,
kj,
nlohmann_json,
toml11,
]
libexpr_temp = library(
is_static ? 'lixexpr_temp' : 'lixexpr',
libexpr_sources,
libexpr_settings_header,
libexpr_generated_headers,
register_builtins_header,
register_builtin_constants_header,
dependencies : [
liblixutil,
liblixstore,
liblixfetchers,
boehm,
boost,
toml11,
nlohmann_json,
kj,
],
dependencies : dependencies,
# for shared.hh
include_directories : [
'../libmain',
],
cpp_pch : cpp_pch,
install : true,
install : not is_static,
# FIXME(Qyriad): is this right?
install_rpath : libdir,
)
# FIXME: remove when https://git.lix.systems/lix-project/lix/issues/359 is fixed.
# FIXME: replace by prelink when https://github.com/mesonbuild/meson/pull/14846 is widely available.
if is_static
libexpr_prelink = custom_target(
'lixexpr-prelink',
output : 'lixexpr-prelink.o',
input : libexpr_temp,
command : [
cxx.cmd_array(),
'-r',
'-o',
'@OUTPUT@',
is_darwin ? '-Wl,-force_load' : '-Wl,--whole-archive',
'@INPUT@',
],
)
libexpr = library(
'lixexpr',
[libexpr_prelink],
dependencies : dependencies,
install : true,
)
else
libexpr = libexpr_temp
endif
install_headers(
libexpr_headers,
@@ -306,23 +333,16 @@ install_headers(
liblixexpr = declare_dependency(
include_directories : include_directories('../..'),
sources : libexpr_settings_header,
# Parallels the requirement to link with boehm of the pkg-config but for internal targets.
dependencies : [boehm],
dependencies : [
liblixutil,
liblixfetchers,
boehm,
boost,
],
link_with : libexpr,
)
# FIXME: remove when https://git.lix.systems/lix-project/lix/issues/359 is fixed.
if is_static
liblixexpr_mstatic = declare_dependency(
include_directories : include_directories('../..'),
sources : libexpr_settings_header,
dependencies : [boehm],
link_whole : libexpr,
)
else
liblixexpr_mstatic = liblixexpr
endif
meson.override_dependency('lix-expr', liblixexpr_mstatic)
meson.override_dependency('lix-expr', liblixexpr)
# FIXME: not using the pkg-config module because it creates way too many deps
# while meson migration is in progress, and we want to not include boost here
@@ -335,5 +355,6 @@ configure_file(
'libdir' : libdir,
'includedir' : includedir,
'PACKAGE_VERSION' : meson.project_version(),
'BOEHM_IF_FOUND' : boehm.found() ? 'bdw-gc' : '',
},
)

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