Compare commits

..
Author SHA1 Message Date
Raito Bezarius 5e9551e662 gosh
Change-Id: I92dba73610fbdc9fc67ba6590e828a7bed869d28
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-07-06 16:24:40 +02:00
eldritch horrors 362bfd827f cli: drop extraneous daemon thread
it was only needed because we forked subdaemons and couldn't reuse the
main aio root. we now fork+exec, so the main aio root is always valid.

Change-Id: Ia19e20d52d65fe72721292be091f182a8a77a7cb
2025-07-04 14:16:33 +02:00
eldritch horrors a232d14e9c libutil: remove unused ProcessOptions members
neither are set to non-default values any more.

Change-Id: Iffe0f230c51324530dd1ad865e16e159f97ef827
2025-07-04 14:01:11 +02:00
eldritch horrors ebf665b1c8 libutil: remove unused DoSignalSave
all uses are DoSignalSave::Save now, and introducing new DontSave uses
should be avoided as much as possible. process management is already a
mess, simplifying it somewhat will make our life easier in the future.

Change-Id: I77eecabe45bee9de18fba0dfc948403d3ce46dfe
2025-07-04 14:01:11 +02:00
eldritch horrors 7b37d5ea6a cli: fork+exec subdaemons, don't just fork
this resolves problems with aio roots becoming invalid after fork (which
so far forced us to run the daemon loop in an aio-rootless thread), does
not require restarting the signal handler thread in the subdaemon (since
we no longer lose it), and is a step towards solving #18 (with transient
daemons doing the store manipulation started transparently when needed).

Change-Id: Iad0149cbc807e31964407c9a83d12314702c8122
2025-07-04 14:01:11 +02:00
eldritch horrors dffb8e9865 libutil/runProgram2: add explicit argv0 support
Change-Id: I292aed7f25de1c193f6e2374c1f6a7ba9d272dd4
2025-07-04 14:01:11 +02:00
eldritch horrors 164d23f38d libutil/runProgram2: support posix_spawn-like dup-to-self redirections
posix_spawn unsets CLOEXEC for fds that are dup'd onto their existing fd
number. this is very useful when inheriting fd numbers exceeding stderr.

Change-Id: I6f14585d424ded6741fdd087f0c4d33a05936bcc
2025-07-04 14:01:11 +02:00
eldritch horrors 0f0718422f libutil: rename runProgram redirections to make more sense
the `from`/`to` naming only made sense for unidirectional output fds,
for others (and for the dup2 api in general) it was backwards. rename
them to `dup`/`from` to make this look more like the assignment it is

Change-Id: Iee50d06f9cfcea765ace6cfbe85b192829207e5f
2025-07-04 14:01:08 +02:00
eldritch horrors 897f87e76a libutil: allow non-blocking fds for writeFull
writing to non-blocking fds happens during remote builds due to the way
file descriptions are shared between processes. we can either poll when
writing to non-blocking fds are reset fd flags. polling is just easier.
unfortunately there is no reasonable way to test this that isn't flaky.

fixes #896

Change-Id: I1d8666df57da97199247f0770c547d0180f6ce07
2025-07-03 22:37:40 +02:00
Jade Lovelace 61c276e858 repl: default to turning off ignore-try
This results in anything that uses nixpkgs getting stopped in the
debugger inside of nixpkgs internals, which are usually irrelevant.

Let's default to the more useful option.

Fixes: https://git.lix.systems/lix-project/lix/issues/666
Change-Id: If4b94a3d488bfb2f634ee5a2bc195e7a4b5434a5
2025-07-02 23:11:07 +00:00
eldritch horrors bfabaa688f libutil: fix segfault in makeInterruptible callback
cancelling the promise returned by makeInterruptible could free the
fulfiller before the interrupt callback handle, and no order of the
attachments made a difference. we must resort to putting fulfillers
into shared_ptrs so we can capture them in interrupt callbacks now.
(alternatively we could add another kind of interrupt callback, but
the complexity of doing that outweighs the cost of one shared_ptr.)

fixes #895

Change-Id: I008b160482fd4d81a29d7e9e452dcda858b090b9
2025-07-01 23:12:49 +02:00
eldritch horrors ed3c202c20 libstore: be more economical about fcntl on RemoteStore
download progress reports send a STDERR_RESULT frame. many concurrent
downloads send many STDERR_RESULT frames. each of these frames has us
run the report loop once. since many frames can happen in very little
time we may receive many frames in a single read from the socket, and
that in turn means we don't have to fcntl that socket on every round.
we must still ensure that the socket is in the correct state for each
part of the loop, and this does mean we may run two unnecessary fcntl
sequences per processStderr call. that's a small price to pay though.

Change-Id: I7af607d8c759b76aff0f6016435955e2f9456923
2025-07-01 17:06:26 +02:00
eldritch horrors ce6eba531e libutil: add makeNonBlocking, resetNonBlocking
these are used often enough that deduplicating them is worth it. we do
lose some error fidelity, but valid fds will never cause an error here

Change-Id: I2b91b4848f546a894a2a6c2d36c32a892fb73c9f
2025-07-01 17:06:26 +02:00
eldritch horrors 6e7c0812c7 libstore: drop checkInterrupt from LocalStore::verifyPath
it's only called by verifyStore, and verifyStore is only called by the
daemon and `nix-store --verify`. both pass the promise to `blockOn()`.

Change-Id: I829c0d189fa913cd8566ddd1a578c50e60fb2ddb
2025-06-30 21:46:29 +00:00
eldritch horrors 32cfbe3959 drop checkInterrupt from ThreadPool items
all of them block on a promise very soon after starting. only
queryValidPaths needs to make sure not to swallow Interrupted
exceptions to exit quickly instead of trying all paths first.

Change-Id: I4f99f5d75d7057bad109dc0131aa58e84275e362
2025-06-30 21:46:29 +00:00
eldritch horrors 96fbc29f09 libutil: checkInterrupt in AsyncIoRoot::blockOn
checkInterrupt is cheap, waiting for a promise isn't. checking for
interruptions before any top-level promise is awaited lets us drop
a bunch of checkInterrupt calls elsewhere, such as in thread pools

Change-Id: Id543edf9411e53b2a5bbec77d3084a8f65aaea46
2025-06-30 21:46:29 +00:00
eldritch horrors 2c00a68624 libutil: explicitly declare and document our reserved signals
Change-Id: Ia27cce0d3577219b7476f7ce6dade4387ba727b2
2025-06-30 21:46:29 +00:00
eldritch horrors 325f937cca remove old commented-out checkInterrupt calls
Change-Id: I843ee341ecfb4cb8be995b9e5ac628f75a4c7e4b
2025-06-30 21:46:29 +00:00
Raito Bezarius 286aa409b2 Revert "libstore/build: automatic clean up of unsuccessfully built scratch outputs"
This reverts commit 42e2bd045c
because this is the root cause of the critical correctness bug.

Change-Id: Ia2360e24650a923034de72ffc193ecb73470cc48
2025-06-29 21:26:36 +02:00
Raito Bezarius 13e46d3d00 Revert "libstore: fix scratch output cleanup"
This reverts commit a0a00948df
because this was insufficient to fix the critical correctness bugs.

Change-Id: I6c7b560ebeebacbbbcc1cbf26e6ef50c38b84f7f
2025-06-29 21:26:11 +02:00
Raito Bezarius a60c1de715 Revert "libstore: don't delete already valid outputs after build"
This reverts commit e356d54d7a
because this was insufficient to fix the critical correctness bugs.

Change-Id: I91c3e368ffd13ade6a3cebbbacdb42655796ea56
2025-06-29 21:26:01 +02:00
Emily d1db3e5fa3 libstore: fix Unix sockets in the build directory on sandboxed macOS
We’re already allowing `/tmp` anyway, so this should be harmless,
and it fixes a regression in the default configuration caused by
moving the build directories out of `temp-dir`. (For instance, that
broke the Lix `guessOrInventPath.sockets` test.)

Note that removing `/tmp` breaks quite a few builds, so although it may
be a good idea in general it would require work on the Nixpkgs side.

Fixes: 749afbbe99
Change-Id: I6a6a69645f429bc50d4cb24283feda3d3091f534
2025-06-29 13:18:00 +01:00
Emily e1ccbe9abd daemon: prefer daemon stores for nix-daemon --stdio
Using `AllowDaemon::Disallow` here broke `ssh-ng://` remote builds in
multi‐user setups where the remote builder user does not have write
access to the store, now that the automatic store selection logic
has changed. Switch to the default behaviour for this path to fix that.

This causes `ssh-ng://` builds to use the daemon by default on the
remote end, even as `root`. I think this is desirable, as the previous
change already made `ssh://` behave this way, and the pitfalls of
local stores apply to remote builds too. For instance, there were
persistent `ulimit` issues on the NixOS Hydra macOS builders that were
resolved by forcing use of the daemon, and I believe the Linux builders
also go through the daemon these days due to using non‐`root` SSH
users. I believe that the `root` vs. non‐`root` difference is just
as confusing for remote builds as it is for local ones.

`ssh-ng://root@builder?remote-store=local` can be used to revert back
to the previous default if necessary.

Closes: #884
Fixes: 9a59106c17
Change-Id: I6a6a696410f46cd3f2f5a94073ea924ad45dc99c
2025-06-29 01:14:07 +01:00
Emily b395831510 libstore: expose the allowDaemon parameter of openStore()
This allows other functions to parameterize over it themselves. An
enum class is used to avoid API misuse.

Change-Id: I6a6a6964d2b5ad47ae5ea9eb11af9b6373ce2141
2025-06-29 01:13:48 +01:00
Emily 3cfce7b37e tests: add test for bug with remote builds as non‐root user
Change-Id: I6a6a696420847c1f47f79269be6b63108ab63afa
2025-06-28 22:15:26 +01:00
Raito Bezarius 33122e79df libstore: weaken the top-level fallback temp dir to 0755 for macOS
Under macOS, the first level of directory has actually mode 0755 instead
of 0700 as macOS often do not possess the right primitives to chroot
inside of these directories, leading to
https://github.com/NixOS/nix/pull/11031.

Thanks to Emily for the heads-up on this type of matter.

Change-Id: I9d4e53717f61c9d573ff176f820610612804fbc3
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-28 17:37:43 +02:00
eldritch horrors ac80a11300 packaging: unbreak static builds
Change-Id: I84dbf66d2d4116c531384445a108d1eab7752ffb
2025-06-27 22:53:28 +02:00
Raito Bezarius fd35e86fc5 libstore: fallback on creating a safe space in the default tempdir
If `settings.buildDir` cannot be written to, because we are in a chroot
store, unprivileged or anything.

We can and should always gracefully fallback to a *secure* location
inside of /tmp, i.e. `/tmp/<a directory under 0700>/<our temporary
directory for build under 0700>/...`.

This does not reintroduce CVE-2025-52991 because we are creating a
directory in-between compared to creating only ONE level of directory.

Fixes #876.

Change-Id: Ie521202923f763225e1901ab1b9b6c6132aaf548
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-27 17:39:59 +00:00
eldritch horrors e356d54d7a libstore: don't delete already valid outputs after build
eagerly consider outputs as not needing deletion during output
registration rather than only doing so after registration. not
waiting for registration to succeed may keep store paths alive
in the file system if registration fails for some reason; that
seem preferrable to the possibility of having another instance
of this bug. since we only leave *good* outputs around there's
not much to worry about except maybe bit of wasted disk space.

fixes #883

Change-Id: I8c22c92e39b9e203f1061278f86cde19dc4474a4
2025-06-27 15:38:53 +02:00
eldritch horrors a0a00948df libstore: fix scratch output cleanup
the daemon must use real store paths, not virtual store paths. using
virtual paths may inadvertently delete paths in the system nix store
when a build was run on a redirected store as root, which isn't good

Change-Id: Id048b236bda0e0ab1f3be6ccba0ddc1de2a3e941
2025-06-27 15:38:53 +02:00
Wolfgang Walther 34696c65a2 libstore: fix race condition when creating state directories
Running (parallel?) nix in nix can lead to multiple instances trying
to create the state directories and failing on the createSymlink step,
because the link already exists.

`replaceSymlink` is already idempotent, so let's use that.

See also:
- https://github.com/NixOS/nix/pull/13368
- https://github.com/NixOS/nix/issues/2706

Change-Id: I7fadd0ce3c1ffcebc9d281c00e5b49c12af3d50b
2025-06-25 16:01:51 +00:00
Raito Bezariusandeldritch horrors 9a59106c17 libstore: NIX_REMOTE=auto tries the daemon socket *then* direct access
In the past, it tried direct access if it *could* [1] perform direct
access.

This solves a bunch of errors people had when they tried the cgroup
feature and their scripts did not pass NIX_REMOTE=daemon manually
(nixos-rebuild-ng, home-manager activation from a root systemd unit,
etc.)

To avoid looping infinitely while receiving daemon connections, we
forcibly change the store URI when forking for a subdaemon to do direct
access automatically, this doesn't break forward usecases where you
point a daemon to another socket because we only change NIX_REMOTE="",
NIX_REMOTE=daemon, NIX_REMOTE=auto to a local and direct access.

All these usecases would end up infinitely looping no matter what
settings are set, because we are also responsible for creating the
daemon socket.

[1]: this happened all the time if you were `root`.

Related: https://github.com/NixOS/nixpkgs/pull/415701
Change-Id: I783fc795a9c2ee25b3d9f44f453f8f94b063371f
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-25 14:59:58 +00:00
eldritch horrors e3caf98a8f libstore: wait for cgroups to die on cleanup
killing a cgroup via `cgroup.kill` is not synchronous, we need to give
the processes in the group some time to wake up and exit. due to a few
historical accidents in the codebase we cannot do this asycnhronously,
e.g. with a kj promise without creating yet more problems. we will, at
some point in the future, have to move cgroup management into the main
daemon rather than doing it with RAII wrappers within every subdaemon.

Change-Id: I03bf9060144b5737729f2b05c25771c674fd154c
2025-06-25 14:59:58 +00:00
Jade Lovelace 276add2cd7 repl: fix repl-overlays in pure eval mode
The reason this gets hit is because of the debugger in flakes. Otherwise
you never have a repl in pure mode anyway.

We evaluate the repl-overlay file in impure mode but this doesn't do
what one would initially expect.

Fixes: https://git.lix.systems/lix-project/lix/issues/777
Change-Id: I19b8ed2f5e9ce500b633b13301b42df69ab7deb3
2025-06-25 14:15:37 +00:00
Jade Lovelace 38850e59e1 repl-characterization: delete duplicate extra_data directory
idk how this mistake happened but it was really confusing to figure out
which one of these was right, so let's get rid of the impostor.

Change-Id: If3b6fb543e5976b1edad68fb143bfa994d1d6381
2025-06-24 22:20:28 +00:00
Raito Bezarius 42e2bd045c libstore/build: automatic clean up of unsuccessfully built scratch outputs
When a build fails, its scratch output paths are not cleaned up.

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

Fixes CVE-2025-52992.

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

Fixes CVE-2025-52991.

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

Fixes CVE-2025-46416.

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

Change-Id: I1dd2506a1fa3944a9d9062123ef8a74903c597ea
2025-06-24 10:49:49 +00:00
eldritch horrorsandRaito Bezarius 6f2b810b4a libutil: add generic redirections runProgram2
explicit stderr redirection makes mergeStderrToStdout unnecessary also.

Change-Id: I63de929e6dc53f6c5ceb2d43c2ce288bfc04d872
2025-06-24 10:49:49 +00:00
eldritch horrorsandRaito Bezarius 19930d423d libutil: make RunningProgram more useful
make it moveable, make it killable, and add a stdout fd accessor.

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

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

Fixes CVE-2025-46415.

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

Fixes CVE-2025-52993.

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

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

Change-Id: Ib871a5283a9c047db1e4fe48a241506e4aab9192
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-24 00:28:09 +02:00
Raito Bezarius 10509774ed libstore: chown to builder variant for file descriptors
We use it immediately for the build temporary directory.

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

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

Change-Id: I2d800740cb4f9912e64c923120d3f977c58ccb7e
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-23 16:57:23 +02:00
Raito BezariusandMaximilian Bosch cd129186ea libstore/s3: fulfill with PutObjectOutcome instead of HeadObjectOutcome
This was probably a typo introduced in
7453e2979f.

Unfortunately, AWS SDK is so well made that this typo became an assert
error in production.

AWS Outcome constructors contains
```
            // Move error from other type of outcome
            template<typename RT, typename ET,
enable_if_t<!std::is_convertible<RT, R>::value &&

std::is_convertible<ET, E>::value, int> = 0>
```

which means that when:

* RT → R is not possible (e.g. PutObjectOutcome → HeadObjectOutcome)
* ET → E is possible (e.g. S3Error → S3Error)

Then, we will instantiate the error-moving outcome constructor which
asserts `!o.success`… Though, the original outcome indeed succeeded.

Change-Id: I3809514ae0648e8c02b0f93fa64d91115a091cd9
Co-authored-by: Maximilian Bosch <maximilian@mbosch.me>
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-22 19:45:53 +00:00
Teo Camarasu 2172683388 libstore: better error message when remote version is too old
We clarify that the *remote* daemon is too old. Otherwise it can be a bit confusing since you might have a local daemon as well, and it's not clear if the error is coming from the local or remote end

Change-Id: I17344c6f59bd7e0e62960c0025184d72ec3f012b
2025-06-22 15:40:52 +00:00
Maximilian Bosch 242a228124 libutil: close file handle in async NAR parser
This bit us while upgrading Hydra[1]: when all the data was read into
the hashing sink while receinving NAR contents, the hash was never
created which lead to a test failing because file size was correct, but
the hash was std::nullopt.

[1] https://git.lix.systems/lix-project/hydra/src/commit/7a0dae579b53b4b96a829263b160c6dc9f42ce75/src/hydra-queue-runner/nar-extractor.cc#L70-L73

Change-Id: Ie71b5f1f17c926a2ab95fb2aabf23c7a575ff70b
2025-06-21 13:52:43 +02:00
Maximilian Bosch 3a6414760e libcmd: error if first argument for --arg/--argstr isn't an a valid identifier
Step two for #496.

The idea is to allow `nix-build --arg config.allowUnfree true` do the
right thing in the future. However, that's a breaking change since
people might be relying on the ability to set `"config.allowUnfree"` as
attribute-name when auto-calling a Nix-expression.

As a first step, a warning got introduced in 2.92, the next step is now
to reject this usage in 2.94 and await feedback if any so that we can do
the change in a future Lix release.

Change-Id: I6e38fafe26e234204f5bba2a3a4c1da10f80e5f2
2025-06-20 22:40:14 +02:00
Maximilian Bosch e23bed5e64 libutil: expose the functionality of Lix's exception handler
This introduces three new things:

* `handleException` which prints out exception details and its stack
  trace.
* `handleExceptionWithAsyncTrace` which does the same, but also prints
  the async trace if any.
* `LIX_BLOCK_ON` which is awaits a promise and adds an exception trace
  if an exception got thrown, similar to `LIX_TRY_AWAIT`. However, this
  is not supposed to be used in async functions, but on callsites of
  `aio.blockOn()` which is especially useful for Hydra[1].

For `LIX_BLOCK_ON` I had to introduce another function because there's
apparently no way to implement all of it in a macro: on macros with
compound statements the return value must be a trivial expression at the
bottom, i.e. no `try`/`catch`. Now, returning the value from the
`try`-block requires the variable to be defined up-front, but for that
we'd need to know the type-name. Hence the construction with a
template-function being invoked by a macro that injects the current
source-location.

[1] https://git.lix.systems/lix-project/hydra/pulls/52

Change-Id: I56cc92c94f7e8f0be5d4dc5a7d8cb21a92e776ef
2025-06-20 18:14:38 +02:00
Commentator2.0 35c3bfdacb tests/functional2/lang: require all files to be used
Added an additional check that all files present within a folder must be
used/referenced. Otherwise an InvalidLangTest will be created.
This ensures that there weren't any mishaps while migrating tests
resulting in files being ignored and hence some tests not being run.

Fixes: #852

Change-Id: Ie096c5670bc20325ba72c7d6ce33c06667c66ab1
2025-06-20 11:36:13 +02:00
Commentator2.0 d1afc83676 tests/functional2/lang: improve toml design
Redesigns the test.toml to use a list instead of a directory
additionally it is now possible to do toml and matrix tests on singular
files as well as on a subset of files.

Fixes: #851

Change-Id: If8635109c6274f406ad68fe35315b9125f45f67d
2025-06-20 11:36:13 +02:00
Commentator2.0 f2eb920e46 tests/functional2/lang: improve assertion failure message
Currently when a lang test fails, (or any snapshot assertion for that
matter) the error message is rather bulky.
This is due to both sides being printed fully, using escaped newlines
(i.e. everything is one line)

This is awful to read and check what the actual difference is. Also
there is no indication that one can update the golden files using the
cli flag.

This commit changes the error message when comparing snapshots against
something
a list of lines is shown, where the output differed. An additional note
about how to update the files automatically was added too

Change-Id: Ibedcf48018c27f924b807fbd42362fb608d27441
2025-06-20 11:26:03 +02:00
eldritch horrors 877b0d7121 libstore: asyncify Store::queryMissing
we no longer use thread pools for querying missing derivations. this
binds queryMissing to a single thread for now, but query performance
is still greatly improved. we may want to optimize the store code in
the near future too though since queryMissing is now fully cpu bound

Change-Id: I08a9c8cc199963ef5981572ca4a32d90dbdec028
2025-06-19 14:59:38 +00:00
eldritch horrors 2bfea5eefe libstore: use async streams in LegacySSHStore
this mirrors what have already done to the more modern wires.

Change-Id: I68b65bb400c889ba822386a9c280297c9ff4f740
2025-06-19 14:59:38 +00:00
eldritch horrors 02f61e7759 libstore: asyncify legacy ssh command/response
we intentionally omit writers for the new types we add for serialization
purposes since we do not plan to asyncify the legacy ssh server side. if
we ever change our mind we can extract these types into a header and add
writers as needed. due to the inevitable network overhead of the old ssh
wires we don't bother to optimize serialization too much and instead opt
to make the code more readable; the performance difference does not show
up in practice since network latency dominates the few nanoseconds spent
on extra promise allocations and awaits by a couple orders of magnitude.

Change-Id: Id3ee9a01f8bfa63fa23082fa07de5c673fd70883
2025-06-19 14:59:38 +00:00
eldritch horrors aae67feb19 libstore: make legacy ssh build settings a generator
that'll make sendCommand-ing the legacy protocol much easier.

Change-Id: I3193b306ab28c203fe50c404a15c45cf598ca7e7
2025-06-19 14:59:38 +00:00
eldritch horrors 508f476c18 libstore: don't crash when talking to old ssh:// remotes
protocol version 0x204 dates back to nix 2.0 in 2017. that's old enough
to not worry and drop the gratuitous assertion crash we see it instead.

Change-Id: I8cf23373d4daabccab61f1cbb670947479f0d2bc
2025-06-19 14:59:38 +00:00
Lily Ballard 20fed838a6 libcmd: replace @docroot@ when rendering markdown
Also replace links to `.md` files with the equivalent `.html` files.

Change-Id: Id0000000f267872d021985daf2d833a93ec06e66
2025-06-18 00:42:36 -07:00
Raito Bezarius 87d99da6ca libutil/cgroup: ensure that cleanup takes place even under interruptions
When Ctrl-C is sent to the workload, even across remote builds, the
whole process possess a global flag `_isInterrupted` which is checked in
certain filesystem operations, cancelling them, e.g. writeFile will
write nothing under interruption unwinding.

In addition, if any operation throws an exception before we `rmdir` the
cgroup, we may leave it hanging while we remove the state record.
Therefore, we put the final cleanup in a block.

In practice, reading statistics could lead to failures.

Control groups cleanups are critical though and should always be
performed.

Change-Id: I48fa87317b6a9f6663559bc8fa5f8a897f37011e
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-17 22:20:37 +02:00
eldritch horrors c3bc0d35dd libstore: use async streams in RemoteStore
this is a large step towards making RemoteStore a proper capnp rpc
interface, and it lets us get rid of the RemoteStore error handler
thread pool. this does mean we make six or more extra syscalls per
operation to set and clear socket non-blocking flags, but they are
pretty cheap compared to cross-thread wakeups and scheduling. once
we have real capnp rpc for store wires we can drop them again too.

Change-Id: I67dfebc8644a407cd4a8221ffcad02a938ac5abe
2025-06-17 15:25:32 +02:00
eldritch horrors 3f62905312 libstore: instantiate RemoteStore FdSources as needed
in the future we will want to instantiate either a sink, a source, both,
or streams, depending on how the fd is used. to do this we need to share
read buffers among sync and async readers. removing the FdSource we kept
in the connection also helps prove that we always use this buffer for io

Change-Id: Ib678e128ed6c4a07d6ce5ec1d3cde9eb3f5fc4ca
2025-06-17 14:34:05 +02:00
eldritch horrors 687ea19e6f libstore: drop pervasive RemoteStore send buffering
we don't need to double-buffer commands. only the subframe protocol
needs a buffered backing, and connection setup is special *anyway*.

Change-Id: I596f2bf8e297c3c5dc2befae674deafcf559d9a9
2025-06-17 14:34:05 +02:00
eldritch horrors b0edb262b2 libutil: add bidirectional async fd streams
this may as well be called AsyncSocketStream since that will be what we
use it for, but hopefully it will not exist for long enough to need any
other socket functions to actually justify such highly specific naming.

Change-Id: Icf2fe88cf345405218e4b1bd440267e7f132f5c7
2025-06-17 14:34:05 +02:00
eldritch horrors 7b65d7c508 libutil: add buffered async streams
these will let us share async stream io buffers with sync sinks and sources.

Change-Id: If3149803a9e1fda62391399177da62f7522a811b
2025-06-17 14:34:05 +02:00
eldritch horrors 81d2d26c3f libutil: add async output stream type
we also extend AsyncInputStream with a drainInto variant to give async
output streams rough feature parity with sync sinks. we still will not
add serialization support to streams though, that's far too expensive.

Change-Id: I60d5ab43610c45a40ea8740470a5eafe68064aea
2025-06-17 14:34:05 +02:00
eldritch horrors fa116c96f7 perl: ensure that stores are destroyed after aio roots
otherwise stores containing async objects will cause crashes during
shutdown. currently there are no such stores, but that will change.

Change-Id: I05d46ba6831c641774edfe6aa99aa7d0de457429
2025-06-17 14:34:05 +02:00
eldritch horrors bc33c21b8a cli: remove static destruction from nix-store
store objects may hold on to network connections. if those connections
are async they're bound to the lifetime of the aio runtime, which ends
long before the static object destructors we need for nix-store today.

Change-Id: I4aa5466681a82f7e5008cc0b952fcba01d5b39d7
2025-06-17 14:34:05 +02:00
eldritch horrors 49e6147f95 libutil: remove unused Source::good
Change-Id: I8dcb725578e27415b60a01a16c10720e96a5371b
2025-06-17 14:34:05 +02:00
eldritch horrors e5c4de34c5 libstore: eagerly mark daemon connections as bad on local errors
do not rely on Source/Sink `good()` or delayed guessing about whether
an exception was thrown by the daemon or not. mark connections as bad
for all local errors happening while communication is ongoing instead,
and leave it valid only when an exception was provided by the remote.

we may drop connections a bit too eagerly now, but all cases in which
that happens were vulnerable to protocol desynchronization. there are
still a few windows for this to happen left, but those are unfixable.

Change-Id: Iefaa66c552092c436b9de77aa3f8e09f847a966e
2025-06-17 14:34:05 +02:00
eldritch horrors 37c17804df libstore: serialize wire messages into temp buffer
once we make our socket fds non-blocking we won't be able to easily use
plain FdSink for serialization. performance impact of using a temporary
buffer should be low since we don't send very many messages and even in
the simple local daemon case networking overhead is already quite high.

Change-Id: I550d73142570b7d2e7b0feb1bcc57d61e9b45178
2025-06-17 14:34:05 +02:00
eldritch horrors 6f64e1b133 libutil: make Fd{Sink,Source} io buffer shareable
we will need this during RemoteStore wire asyncification to be able to
use the old synchronous serializers. alternatively we could define all
serializers on the async types as well, but that'd be slow and far too
much unnecessarily duplicated code (that will be deleted soon anyway).

Change-Id: I6e4f334025844b808a697ddcd8f80ddcd8c3fc9c
2025-06-17 14:34:05 +02:00
eldritch horrors fc18a6d170 libutil: disallow Fd{Sink,Source} copy and move
it was never safe. both discarded the buffer of the source object,
possibly leading to silent data corruption. FdSource discarded the
fancy EOF error string as well, possibly causing bad error reports

Change-Id: Ib5c07986471b5af03d707230cd487259201952e9
2025-06-17 14:34:05 +02:00
eldritch horrors 8835b2f057 libutil: remove unused AsyncFdInputStream
Change-Id: I549e0bc36637161847fde6c50887c917c1c1dadc
2025-06-17 14:34:05 +02:00
eldritch horrors d4d20dfe02 libutil: remove unused FdSink::written
don't know how we missed that when removing FdSource::read

Change-Id: I086587e190460a3cc81163008f961def3cce0576
2025-06-17 14:34:05 +02:00
eldritch horrors ba2432f8fe libutil: add asyncJoin, a Result-based joinPromises
we'll need this to asyncify withFramedSink and remove its thread pool.

Change-Id: I1a099392c094f8441482fde3b2d3843931420ffa
2025-06-17 14:34:05 +02:00
eldritch horrors 5f42f66afa libstore: rpc-ish-ify remaining RemoteStore methods
oops, forgot a few

Change-Id: Ic9ed34c29d26e94109d5f69eb90f334f26170ec3
2025-06-17 14:34:05 +02:00
Jade Lovelace 833aef5bcb fix(rl-next): systemd unit description is using wrong section
> The resource control configuration options are configured in the
> [Slice], [Scope], [Service], [Socket], [Mount], or [Swap] sections,
> depending on the unit type.

Reported by Worm on matrix.

Change-Id: I5f942b864e40bc461e8751cdf8337b1f8c2bbce4
2025-06-17 05:05:48 +00:00
Ruby Iris Juric e01ad92c9c libstore/local-derivation-goal: cleanup "hash mismatch" error formatting
The previous format was a little bit messy, with inconsistent alignment of items in each line after the main error
message. The format has been cleaned up, by aligning the start of all values on the same column, and right-aligning
their labels.

Change-Id: Ic9bb3300faef00cd2e51ebb2f5e0077ade2ff949
2025-06-17 12:50:23 +10:00
Lily Ballard 97f1c5cfa1 Fix markdown link edge cases
Lowdown doesn't quite conform to CommonMark in parsing shortcut links
that are followed by a parenthesized expression, which looks like
`[link text] (unrelated text)`. CommonMark says the space there is
significant and ensures the `[link text]` is parsed as a shortcut link,
but Lowdown parses this like `[link text](unrelated text)`.

This fixes the output of `nix help`. The other case of a near-link was
in the `nix-env --install` docs, which don't get parsed by Lowdown, but
it turns out the link reference definition was missing. The generated
manpage stripped the brackets but the HTML manual page rendered the
broken link with brackets.

Change-Id: I6a6a69641fd2dbf9930bcd875ed21ea80fba909a
2025-06-15 19:04:00 -07:00
eldritch horrors 7453e2979f libstore: asyncify S3BinaryCacheStore
this has side-effects for FileTransfer as well since that uses S3Helper
for s3:// urls. the side effects should be entirely positive though: we
can run multiple s3 requests in parallel without explicitly running any
of them from thread pools (the aws s3 client takes care of that for us)

Change-Id: I67232e604ebb12982b63770f1661ea1d56c5087b
2025-06-15 14:08:48 +00:00
eldritch horrors 1729c8ca3e libstore: asyncify curl return streams
making stores and their users fully async requires all data streams to
be async. the most notable data streams in common usage are curl first
and remote stores second. curl is much more contained today and easier
to asyncify (with the preparatory work we've done in the past commits)

Change-Id: I2d6ff4687ee2b47e4efaa6714827b7283bed941d
2025-06-15 13:36:31 +00:00
eldritch horrors 04a2aba00a libstore: explicitly init curl transfer sources
this too will make it easier to make the streams async.

Change-Id: I9a961fc667042e0aed23d2241326f1ea719bc7a4
2025-06-15 13:36:31 +00:00
eldritch horrors 490c4e3694 libstore: extract closures in curl wrapper to methods
turning them into promises will be much less problematic this way.

Change-Id: I055186a6318fb75c67ae5e7f57561b2cd62d874e
2025-06-15 13:36:31 +00:00
eldritch horrors de89c7f7c8 libstore: asyncify curl interface
Change-Id: I3fc93016b8ac5e59d9062d4f4aead19ae051a680
2025-06-15 13:36:31 +00:00
eldritch horrors a0d5900408 libstore: asyncify BinaryCacheStore::upsertFile
Change-Id: I8e72399c5bfdf70b551fff832b3002ef21f1ef58
2025-06-15 13:36:31 +00:00
eldritch horrors c76f0467b2 libstore: asyncify BinaryCacheStore::fileExists
Change-Id: I7574f61bf222389606be87bbaff486b386cdbecd
2025-06-15 13:36:31 +00:00
eldritch horrors c108f339f5 libstore: asyncify BinaryCacheStore::getFile
Change-Id: If3a1f127470fdaffb0bf79e0692c5d6baf21f18e
2025-06-15 13:36:31 +00:00
eldritch horrors 9f32ab85e8 libstore: asyncify BinaryCacheStore::getFileContents
Change-Id: I7972d6da6d0ac535d2d20c85390c6d67242cab35
2025-06-15 13:36:31 +00:00
eldritch horrors 743703ce35 libstore: asyncify Store::narFromPath return stream
Change-Id: I051c58e650109c70021c0e0a745c7342226e295b
2025-06-15 13:36:31 +00:00
eldritch horrors d824753377 libutil: add async decompression support
it's a real mess, but it's also the best we can reasonably do.

Change-Id: I3b84840cede0363396bdf290d6e6b0e03ace513c
2025-06-15 15:35:51 +02:00
1e34c37477 flake/inputs: 24.11-small -> 25.05-small
We upgrade to 25.05 release, which contains the curl commit
https://github.com/curl/curl/commit/5fbd78eb2dc4afbd8884e8eed27147fc3d4318f6
done in
https://github.com/NixOS/nixpkgs/pull/396200#issuecomment-2795944006.

This fixes HTTP transfers generating arbitrary errors and possibly
failing unusually.

Users who are already depending on 25.05-small or a recent unstable
already had the fix.

Special mention to the Linux kernel who gave me the opportunity to get
on a 24 hours bisection side quest to fix the local release engineering
test.

Special thanks to everyone who had to endure me ranting.

Change-Id: I866caf65d5ea103f1fa5eccd57df8031c9eacda0
Co-authored-by: eldritch horrors <pennae@lix.systems>
Co-authored-by: helle <helle@h3l.li>
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-13 18:15:47 +00:00
helle db56d236dd tests/functional2: fixes prerequisite to ruff upgrade to 0.11.10
Most of these are simple fixes and clarifications. One set of fixes will
come in the commit that actually upgrades nixpkgs and hence ruff as it
will otherwise cause errors here.

Change-Id: Ie857da0f6cf728478700ec2d24cf518f8c7b7815
2025-06-13 12:51:34 +02:00
eldritch horrors ee06552402 libstore: asyncify RemoteStore::Connection::processStderr
we need a wrapper type for the remote exception because our Result type
does not deal well with its good type being the same as its error type.
we could have also return a `Result<Result<void>>` to fix this, but the
wrapper type clarifies via its name where the exception_ptr originates.

Change-Id: Ia6ce67b962cb8d6528b017f4cb682a55d6918939
2025-06-11 22:59:23 +00:00
eldritch horrors 7a10df6e76 libstore: asyncify RemoteStore connection setup
without this processStderr cannot be turned into a promise.

Change-Id: Ia8ee44e9e2344f61c2c63b787b42f867864c7119
2025-06-11 22:32:49 +02:00
eldritch horrors cc04a433f0 libstore: remove flushing from processStderr
it's part of sending the command and should be treated as such.

Change-Id: I7406ead5cd08c79efe50f3b0fcb522a18d9d7bcf
2025-06-11 22:29:30 +02:00
eldritch horrors 8b3fdbc847 libstore: add framed data support to sendCommand
the subframing layer is ... a bit of challenge. since the old code is
synchronous but wants to handle errors asynchronously anyway it is on
the subframing layer to *spawn a thread* that polls for errors on the
wire, while non-framed commands handle errors synchronously once they
have sent all their data. this encapsulation of the wires is far from
perfect (let alone legible), but hopefully it will be only temporary.

Change-Id: I26d8020549b767794cae121313360c488504995f
2025-06-11 22:29:30 +02:00
eldritch horrors 1a2247560d libstore: encapsulate reading simple command results
much the same as the previous change, but on the receiving side.

Change-Id: I9f8a156a9d8fccaf91347e34a5b6baf301df5800
2025-06-11 22:29:30 +02:00
eldritch horrors 2128a2dbac libstore: encapsulate sending of simple commands
use a new helper method to send simple command data (that is, command
data that doesn't involve nested framing) to the daemon. this wraps a
large chunk of wire io, and once all wire io is wrapped thusly we can
replace the sink/source io model with new async input/output streams.

Change-Id: Ief9f520263c230a98403b8756bde917fd1cb236e
2025-06-11 22:29:30 +02:00
eldritch horrors ec374bc6e2 libstore: deserialize findRoots data as vector-of-tuples
a size_t followed by as many pairs of things is exactly the format of a
vector of two-element tuples. it would also be the format of a map, but
Roots is a map of sets. rather than adding a serialization format fixed
to this map type (or some wrapper) we can deserialize the response as a
vector and convert it to the map-of-sets later as this is not run much.

Change-Id: I3950c0f7cc59661576170ace10b25a6f8af1464b
2025-06-11 22:29:30 +02:00
eldritch horrors ab8f4ae7e3 libstore: add CommonProto code for bool/unsigned/uint64_t
we will need these very soon to make the daemon wires more rpc-like.

Change-Id: Ib54acdff0899d70a4c9b1d00c144932c37fdff91
2025-06-11 22:29:30 +02:00
eldritch horrors 87fbc15938 libutil: make the pool element factory a promise
processStderr of RemoteStore wants to be a promise and it must be used
from connection setup, so the pool factory callback must be a promise.

Change-Id: I9ac742b6048ae6dba0bfa5dcb58971386229690b
2025-06-11 22:28:44 +02:00
eldritch horrors 56847dc10d libutil: make Buffered{Sink,Source} io buffer shareable
async io for remote store connections needs some sync parts still for
serialization purposes, and those will have to reuse async io buffers

Change-Id: I05e066e3bf8c4318dc23306383f6a849d018ef91
2025-06-11 18:11:57 +00:00
eldritch horrors 7d681a5049 libutil: add io buffer abstraction
the rpc transition will require sync and async objects to share a single
io buffer (since defining serializers on async is an immense pain in the
tail, slow, and ultimately not necessary). a generic buffer class allows
us to reuse existing serializers more readily (reuse them at all, even).

Change-Id: I5ebba8449f26f2bb76016818928183c7e0123be0
2025-06-11 18:11:57 +00:00
eldritch horrors cc560704de libstore: have SSH use a socketpair, not two pipes
remote store async io will need to set O_NONBLOCK on the connection fds,
and right now the number of fds can vary between connection types: local
connections have one one fd for the sink/source pair since they use unix
sockets, but ssh connections have two because ssh uses pipes. this makes
it rather hard to manage flags correctly, and even harder to wait for io
readiness on both directions using kj. using sockets for ssh fixes this.

Change-Id: I0f563ece7627cd3fbd0f5ce21c25140469729e5a
2025-06-11 18:11:57 +00:00
eldritch horrors 9c4fd3d881 libstore: remove unused RemoteStore::Connection::closeWrite
Change-Id: I4a25807ad870c4704b8efa70e5652206ae654995
2025-06-11 18:11:57 +00:00
Raito Bezarius bea24c8d27 libutil/cgroup: destroy state record at destroy time
If state records are not destroyed at destroy time, this might confuse a
new build that thinks there's a remnant of a cgroup when actually it was
destroyed.

This fixes a bunch of inoffensive and noisy warnings about cgroups being
deleted by someone else.

Reported-by: Ramses <@rvdp:infosec.exchange>
Change-Id: Ib3d33f4ecd6143f33e032c5107b288b4ecabaee1
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-11 15:53:04 +02:00
Raito Bezarius 7bd82718e2 libstore/platform/linux: destroy cgroup before we release user locks
User locks are taken to avoid another build grabbing the same UID.

Under build user contention, it is possible to recycle the same UID from
another build which did not run the Goal destructor yet.

Prior to this change, cgroups were destroyed at Goal destruction time,
but user locks were released at `buildDone()` time.

Therefore, it was possible to have 2 builds fights for the same cgroup
and mess with it, resulting in confusion.

To avoid this, we override `cleanupHookFinally` in charge to release the
user locks and we destroy the cgroup before releasing the locks.

Statistics are kept in the `cgroup` object a bit longer and can be
obtained at `killSandbox(true)` time.

`AutoDestroyCgroup::kill` now ignore if the cgroup path has already been
destroyed, as kill is idempotent.

Reported-by: Ramses <@rvdp:infosec.exchange>
Reported-by: Frederico Schonborn <@fredericoschonborn:matrix.org>
Change-Id: Idfbf9aaf010c5f718f2c1c38548383d912d8ee95
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-11 13:47:38 +02:00
Raito Bezarius 797c6d4cd4 libutil/file-system: make AutoDelete not copyable and movable
Such a RAII structure should NEVER be copyable or movable, otherwise:

```
AutoDelete x;

x = AutoDelete(p, false);
```

will trigger the immediate deletion of `p`!

This fixes an annoying bug where the state record for cgroups was
deleted immediately as soon as it was created.

Change-Id: I2bfbc0815706700a0a75b79d1059cc552119b2c9
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-10 23:29:10 +02:00
Raito Bezarius 9f9fced2dd libstore/build/worker: clean up cgroup error messages typos
It's `delegated` and not `delgated`, also it's `DelegateSubgroup` and
not `DelegateSubtree` which I clearly hallucinated because of subtree
vs. sub(c)group.

Change-Id: Icfaa6116fa83416c431820978ef35aa8aa943feb
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-10 23:29:10 +02:00
a527bb251a libstore/build: cgroup delegation to sandbox
We offer full cgroup delegation to our sandbox now, required for running
containers inside the sandbox.

To run systemd-nspawn or containers managers inside the sandbox, there
is a need for one extra ingredient now: control over your own cgroup
subtree inside the sandbox.

If, in addition, you need multiple UIDs, for e.g. rootless usecases, you
need to run with the `uid-range` system feature.

Therefore, when the daemon or Nix runs under the right condition, e.g.
systemd-style delegation of the cgroup subtree while placing the
nix-daemon in a supervisor sub-cgroup, we create a new sub-cgroup for
each build based on the build UID and delegate that sub-cgroup to the
builder's process.

Additionally, `uid-range` always request the `cgroups` feature now, as
`uid-range` builds would probably always benefit from having cgroups
delegated, but the converse is not true.

Inspired from https://github.com/NixOS/nix/pull/11412 with a different
design that does not use function-local statics to derive the root
cgroup.

Co-authored-by: Linus Heckemann <git@sphalerite.org>
Co-authored-by: Parker Hoyes <contact@parkerhoyes.com>
Change-Id: Ic8947c5adaf4b5bbd153386e05fad65a935274fa
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-10 20:50:00 +02:00
Raito Bezarius 1783d5b348 libstore/build: drop cgroups experimental feature
We drop it to re-introduce it via the concept of build context which
will control in which cgroup a certain build should be spawned.

Change-Id: I4b4705d768129a6d7c0f061dc2163ba116088b18
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-10 16:00:51 +00:00
Raito Bezarius 21dbd7745d libstore/binary-cache-store: skip NAR listings if it's not possible to serialize it
Some source trees might not be representable inside of the NAR listing
format v1 as file paths (on Linux) are not guaranteed to be valid UTF-8.

When something like this happens on a large-scale build farm, a
mysterious "queued" but impossible to process job appears, this is
because we cannot write the NAR listing and serialization always fails.

Why did this work before? nlohmann was introduced _after_ such paths
were ingested, see: 09f00dd4d0.

What happened for such previously mis-serialized NAR listings?

```
curl -v 'https://cache.nixos.org/nz8p9hn00r6z7s57581c1hiv39pa1ia6.ls' |
brotli -d | jq .
```

This fixes the build of `sub-batch`
(https://github.com/kl/sub-batch/tree/master/tests/rename_invalid_utf8)
on ForkOS infrastructure.

Many thanks to Puck for the assistance on holding `rr` right on this one
and finding the history of these changes.

Change-Id: I2c2fbac70818e02810f9fd236c3a248187bf5fe7
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-10 15:22:22 +00:00
Raito Bezarius 1e71df37b7 doc/manual/rl-next: mention symbol value reuse
Forgotten in the symbol value reuse chain.

Change-Id: I7050f56cffcddce5fae4f74ebb35a9fe108a5dcf
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-10 16:25:29 +02:00
Raito Bezarius ebc8f56b52 libexpr/primops: pass the underlying Value of symbols if possible
Instead of allocating a new Value and copy the symbol string
representation inside of it, we can pass along the underlying Value,
which avoids (garbage collected) allocations.

This results in:

* a ~8 % reduction for `gc.totalBytes` over
  `nixos.ec2.closures.x86_64-linux` for NixOS 24.11. (920MiB → 842MiB)
* a slight reduction in CPU time due to less allocations being performed
  at all

Change-Id: I097f586dbc98f889fbc62d0a5f80c9d76ddedfd2
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-10 13:58:08 +02:00
Raito Bezarius 5e98a2159b libexpr/symbol-table: introduce InternedSymbol
The backing storage for symbols becomes a class storing a Value and a
string.

The Value is itself a string which contents points to the owned string.

Recovering a `SymbolStr` is still possible.

Change-Id: I171151abc3c0a513f2150c4b54edd61dea256cce
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-10 13:58:08 +02:00
Raito Bezarius 364e94fe23 libexpr/value: do not depend on Symbol
The symbol table will contain types that encloses a Value, thus, it
needs to depend upon the Value header, whereas the Value header depends
on `Symbol` for typedefs.

We move the typedefs in the place where they are used.

Change-Id: Ic533e5aad927b9bc4a9d1723430e90e86a4b5466
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-10 13:58:08 +02:00
Raito Bezarius 95ea358f98 libexpr/print: drop redundant constructor in emplace_back call
Change-Id: I79210edfede0a1d17f38b5834515f56d44c97466
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-10 13:58:08 +02:00
Tom HubrechtandRaito Bezarius ac3b742510 libexpr/symbol-table: remove unused field from SymbolTable::symbols
Change-Id: Id16ba5c9b7941757746d0cb79eb14463845aadb1
2025-06-10 13:58:08 +02:00
Raito BezariusandTom Hubrecht 5db71cfb3b libutil: add should emplace inside a ChunkedVector
This simplifies many call-sites where construction can take place
automatically.

Change-Id: I87f697d55375676345b388024eb8df900bf808de
Co-authored-by: Tom Hubrecht <github@mail.hubrecht.ovh>
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-10 13:58:06 +02:00
Qyriad d8b1bb5862 build: fix Meson warning about lix-clang-tidy not having meson_version
This fixes Meson's "Project does not target a minimum version but uses
feature introduced in '1.1': meson.options file" warning.

Silly Meson.

I also added a note in the top-level meson.build to indicate
`meson_version` is specified in more than one place.

Change-Id: I2c04278bb46a562a1c96cd2e5e4d9ce59ce8e125
2025-06-09 14:10:44 +02:00
Raito Bezarius b70bbbe680 misc/pre-commit: add automatic clang-format of changed lines
Lix has a style guide:
https://wiki.lix.systems/books/lix-contributors/page/code but
contributors like me have been unable to enforce it, which is sad.

To avoid further violations of that style guide, we enable a pre-commit
hook for clang formatting of the changed lines.

Change-Id: I217452efa3ac8bd66b4d3a08a6fe9a241207790b
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-07 15:27:11 +02:00
Raito Bezarius c19a0fe288 devshell: add git-clang-format
This is useful to reformat only changed hunks of a file via
`clang-format`.

Change-Id: I9aa8526d75fd2301113ee57f3a2e595f3b03504f
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-07 15:27:11 +02:00
Commentator2.0 004a505dc6 tests/functional2/nix: fix config serialization
Currently, the typecheck for the config values is only done
half-heartedly only checking if something is either a list or non-list
item, but not checking what type the list items are

this commit fixes the typecheck and adds test for proper serialization

Change-Id: Ifd93842b19b1dd870bdb3af0c000243b4380e7aa
2025-06-07 00:14:57 +02:00
Commentator2.0 0625e69912 tests/functional2: fix bad error message when merging files
The error message used to only contain the last key of the merge failure
this commit changes the message to contain the full path to the merge
conflict, resolving ambiguity

Change-Id: I9848a559b1b888e50a548eef8609bf34506040de
2025-06-07 00:14:57 +02:00
Commentator2.0 afa5b924cd tests/functional2: improve type checking util
currently, there is a small helper funciton in lang_util to check if
something is of a list type generic

to improve re-usability, this function is moved to utils and improved to
be also check for nested iterables and such

Change-Id: I92984daa4c4decf13d340a2ea5e52f724cee800e
2025-06-07 00:14:57 +02:00
eldritch horrors 60830ca5fa libstore: add derivation wire generator
this'll be useful later as it makes derivation writing composable.

Change-Id: Ib5bbbd04e7a136e448669e95a3b976f1fe196f52
2025-06-06 18:09:46 +02:00
eldritch horrors 4ebf79bc19 libstore: associate wire connection states with stores
why pass the stores as a distinct argument every time?

Change-Id: If529a49541483e8a3d33eb2b3532d66b3bb9738d
2025-06-06 18:09:46 +02:00
eldritch horrors ce9acd5f97 libstore: use proper connection handle for narFromPath
this could've just ignored exceptions thrown by the remote. in the
current implementation there's no way such an exception could have
propagated to the client though, so there's no change in behavior.

Change-Id: Ide03bda1cb0ad7fb5f27b4ee5d16efd6c2b635ba
2025-06-06 18:09:46 +02:00
eldritch horrors 2cd44d2e1d libstore: don't wrap&unwrap connection handles
this was only necessary for old protocols we no longer support.

Change-Id: Iebb06ce6266c2c7c3f97da469b1295f4cb54ee5a
2025-06-06 18:09:46 +02:00
eldritch horrors b33669b55a libutil: remove withFramedSink
always use withFramedSinkAsync instead to reduce logic duplication.

Change-Id: I82f4675c67c1fa593f00272e5ddb54bca9f64a79
2025-06-06 18:09:46 +02:00
eldritch horrors c13571015a libstore: send worker options packet as one blob
mostly to make moving this to async writes easier. this won't have a
performance impact because it's only a single packet, that's written
to a BufferedSink, but the connection sink only gets a single write.

Change-Id: I9a5f1afe7d3e25f5f4502ef9520ff2f2529431ba
2025-06-06 18:09:46 +02:00
eldritch horrors cf93814ca5 libutil: remove unused FdSource::read
Change-Id: Ie08c9a80dc029ae4e5eb91b09db81166c8a627a3
2025-06-06 18:09:46 +02:00
eldritch horrors 8c30a165e5 libutil: remove long-dead create_coro_gc_hook
Change-Id: Ia37c6a5401dbe6453bfdfa733da5237d2c2dc819
2025-06-06 18:09:46 +02:00
eldritch horrors dd31a23c31 libstore: add worker serializer for SubstitutablePathInfo
the test is for the map that usually wraps it though because it's the
bit we're interested in replacing, and it has custom serializer code.

Change-Id: If77a236dfca738b646ed2b7a5c65515dad6b7295
2025-06-06 18:09:46 +02:00
eldritch horrors fca0a30470 libstore: remove pre-2.18 protocols
the old protocols are largely untested, mostly unused, and have design
problems that make the RPC transition a lot harder, if not impossible.
in theory we could ship a transparent protocol-converting proxy that'd
isolate the daemon itself from old protocol versions, but that's a lot
of code to maintain for presumably little gain or even no gain at all.

Change-Id: I4c3f3bb34d39044f6aeb07c10caaf13b8340a220
2025-06-02 22:43:24 +00:00
piegames 019b17f4e9 tests/functional2/lang: Migrate trivial eval-okay tests
Change-Id: I07a2e70eacd3d7bca3fc4d7074b9892b9ea35346
2025-06-02 21:52:34 +02:00
piegames 9673c6480a tests/functional2/lang: Migrate trivial parse-okay tests
Change-Id: I11b6ea26b7dc6bcf8250c04d3c97ded452dd1eda
2025-06-02 21:52:34 +02:00
piegames 0219434ae9 tests/functional2/lang: Migrate trivial parse-fail tests
Change-Id: If6cd6a2432c081e4b918f480ce3be0da3e691d40
2025-06-02 21:52:34 +02:00
piegames 63edb9c678 tests/functional2/lang: Migrate trivial eval-fail tests
All changes are uniform and done with the same script, so checking only
some should suffice. For that reason, any tests involving multiple files
or custom CLI flags are not included in this commit.

Change-Id: Ib2d0e08937b56e241d99771a58aad34ed3ad308a
2025-06-02 21:52:34 +02:00
piegames a7e5ff0070 tests/functional2: Make symlink handling less confusing
The current `RelativeTo` design is both more complex and more confusing
than necessary. Its four variants are now reduced to only two. They are
now also represented as different classes, to better communicate the
difference in semantics and also intent.

Change-Id: Ia60fc7a2dfa0f62bdef90dde347fd8603fd3fbf9
2025-06-02 21:52:34 +02:00
Linus Heckemann e753fcb414 gc: delay throwing error until cleanup is complete
Previously, paths not being deleted by gcDeleteSpecific would result in
(a) hardlinks not being cleaned up, and
(b) statistics not being reported correctly.

By throwing the error later, we fix both of these problems.

Change-Id: I8019f3e10d9f22e81ea87bb26b77f04ebc888a19
2025-06-02 20:05:32 +02:00
Commentator2.0 530b40ac8e tests/functional2: fix overly broad xfail test passing
By default, xfail tests will always "pass" when the test fails,
disrecsarding any restrictions put on them via their parameters.

By enabling the `xfail_strict` option, xfails won't pass anymore when
the failstate is different from what is described in their parameters.

Change-Id: Ifea6e27d716d91f60210e6ba24175074fa39c304
2025-06-02 16:33:46 +02:00
Victor Fuentes ab1e58e948 feat(nix store ls): support reading nar listings from binary cache
Remote binary caches support `write-nar-listing` options where they create a `HASH.ls` file for quick indexing without having to download the nar.
This commit makes experimental `nix store ls` attempt to read these files instead of downloading the full nar.

The difference is very obvious with large packages like stellarium:

nix store ls --store "https://cache.nixos.org" /nix/store/ijpvwgs9zamqaax5dy2cd0kxgz7lr7an-stellarium-25.1 -R

Change-Id: I6a37e0788b3a91c319331a8de69c51daf3efa955
2025-06-01 22:47:11 -07:00
Commentator2.0 8c528529fc docs/functional2: overhaul documentation
Add Documentation for usage and development within functional2
including common fixtures and where to find them

This is done to make the migration from functional easier and give devs
a reference for how one writes tests

Change-Id: I6ee73e654d245fd4ad43e495d1172e406313cb23
2025-06-01 20:19:37 +02:00
Commentator2.0 c63ba3c485 tests/functional2/lang: migrated first tests
Change-Id: I4b5755a63d9454db20f63f751f5f33564a2ee5be
2025-06-01 20:19:37 +02:00
Commentator2.0 f7914e89e6 tests/functional2: add framework for lang tests
This creates a framework similar to the old lang.sh from functional.
Some notable changes:
- instead of having a .flags file, a test.toml can declare flags
- additionally the test.toml can also declare extra files and multiple
runners for the given input file.
- there won't be any old tests hanging around anymore which weren't
deleted properly in the installation
- all files for a single test are defined decleratively and there won't
be any residues

Tests can be placed within the functional2/lang folder
most migrations should be rather clean

Implements: #825

Change-Id: I5f9149903ec5b078008969a4ae77305417c11475
2025-06-01 20:19:37 +02:00
Commentator2.0 696efc58e7 tests/functional2: mark tests as skipped when snapshot updates golden files
Currently, tests are marked as "passed" when golden files are updated.
With this change, the tests are marked as skipped instead.

Additionally finally introduces tests to check if the snapshot behaves
as expected

Change-Id: I438eed70e0b94d561e99cc1e0363092809da827e
2025-06-01 20:19:37 +02:00
Commentator2.0 761a4f544c tests/functional2: add utils for files and paths; add pytest_command fixture
Add utils for general-use functions and paths

Additionally introduces a pytest_command fixture, which creates a
testing environment for pytest within the tmp_path. This allows for
encapsulated testing of our frameworks (i.e. snapshot, lang etc)

Change-Id: Ic0a5bc4bfc0b0bfbac15bc51dd4a94fae6ee6f26
2025-06-01 20:19:37 +02:00
Commentator2.0 80ea9c682a tests/functional2/files: allow absolute paths as origin
allow to pass absolute paths or similar Path entries to declaration of
files instead of just string paths relative to the requesting file

Change-Id: I616da6abbb73d1d63ead370e9ae37a401d85f42d
2025-06-01 20:19:37 +02:00
Commentator2.0 5a6bb0fb50 tests/functional2: add runner to justfile
Due to how meson works with the current justfile options, it is not
possible to pass additional arguments into the functional2 test
suit/pytest.
Due to that, it isn't possilbe to narrow down what tests to execute or
add output options or similar.

This commit adds an additional recipe, calling pytest directly ensuring
arguments are handed through

Change-Id: I3748d1cd5fddc16b11fff11c0f1a77195e37c837
2025-06-01 20:19:37 +02:00
Alois Wohlschlager 4505bfac8e libstore/gc: fix auto-GC blocking indefinitely during evaluation
The first auto-GC request would not be registered as a waiter due to a logic
error. As a result, if that request was synchronous (as happens during
evaluation) it would be stuck forever waiting on a promise that will never be
fulfilled.
Register also the first request properly so that it is notified and unblocked
again when the GC has finished. Also add a test verifying that auto-GC
triggering during evaluation will not get stuck.

Fixes: https://git.lix.systems/lix-project/lix/issues/844
Change-Id: I157afdc737415261e48d6d01d46c586a2927a1ad
2025-06-01 18:59:23 +02:00
Nikodem Rabuliński 3815dd5e64 tests/functional2/commands: fix failure with auto-allocate-uids
The custom subcommand test fixture used to replace the environment
with PATH prepended with the directory of the subcommand.
This caused the tests to fail on darwin with auto-allocate-uids
enabled, as the dynamic users aren't added to the user database
inside the sandbox, as opposed to linux.
Other environments were unaffected because the build user is a real user
with a database entry and HOME set.

Update the environment instead, also preserving hermetic env
created earlier by NixCommand constructor.

Change-Id: I7e59fd69ff13d1d395316d857b63a356e1648159
2025-05-30 09:07:41 +02:00
0c2ced0224 feat(nix-instantiate): add --raw flag
The experimental `nix eval` command already supports a `--raw` flag.
This commit implements the same flag for the stable nix-instantiate command.

Until now instructions and scripts that didn't want to rely on experimental
features had to use workarounds such as:

    nix-instantiate --eval <something> | tr -d \"

(which also undesirably also removes double quotation marks within the string), or

    nix-instantiate --eval <something> | jq -j

(which undesirably depends on another package).

Co-authored-by: Raito Bezarius <raito@lix.systems>
Co-authored-by: Silvan Mosberger <silvan.mosberger@tweag.io>
Change-Id: Iced9a80ee7edd60af2385c5193485f1774175339
2025-05-29 15:25:12 +02:00
Tom Hubrecht e468102508 fix: Consider fetchGit locked when narHash is present
`fetchGit` has been modified a long time ago to use fetchTree, however,
we don't care about `lastModified` because we are not in a flake
context, this hack introduces a `git-locked` type of input that only
cares about `narHash` being present. This is needed to avoid fetching
the remote repo each time `fetchGit` is evaluated whith the result
present in the store.

Change-Id: I521c6fcccf8cf12945594f205d7fd4c8c2cf89e9
2025-05-28 22:24:23 +00:00
Raito Bezarius b792279780 tests/functional/lang: update error path locations
The coerce integer feature was not rebased before merge and we do not
have a merge queue, hence, after merge, the HEAD was in a broken state.

We take a commitment to invest into a merge queue now and do a fixup
here.

Change-Id: Ied9410690b542359859ab5f597f22ebceb857305
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-05-28 22:29:04 +02:00
Tom HubrechtandLix Systems Gerrit 0d72109ada Merge "libfetchers: factorize inputFromAttrs" into main 2025-05-28 19:54:37 +00:00
Raito BezariusandLix Systems Gerrit 316aa591ac Merge "libexpr: coerce integers under the XP feature coerce-integers" into main 2025-05-28 19:39:59 +00:00
71rdandLix Systems Gerrit fda93021ca Merge "libutil/args: fix crash when NIX_GET_COMPLETIONS is not a number" into main 2025-05-27 23:31:40 +00:00
git@71rd.net 0e115a4828 libutil/args: fix crash when NIX_GET_COMPLETIONS is not a number
When assigning an a value to NIX_GET_COMPLETIONS that could not be
parsed as an integer lix would just crash, as the value was directly
passed to stoi, without handling the return value.
This change switches the parsing to use string2Int and throws an
exception if the return value is empty.

The behaviour of lix is slightly changed through, as the value of the
variable was previously parsed to an int and then assigned to a variable
of size_t.
This change in behaviour can only be observed in cases where the
value of NIX_GET_COMPLETIONS is chosen so when it overflows it would
be valid index of the provided arguments again.

Through this change the variable is parsed as a size_t and negative
values are rejected.

Change-Id: Idf7c5740274c6e07d5bb13d7e2ed32764bfc27f8
2025-05-27 22:20:22 +00:00
71rdandLix Systems Gerrit 8525345fd1 Merge "libutil/args: dont crash completion when receiving incorrect number of arguments" into main 2025-05-27 17:31:06 +00:00
git@71rd.net 20d50b049d libutil/args: dont crash completion when receiving incorrect number of arguments
When using completion, the number of the word for which the shell
requests completion is provided in the environment variable
`NIX_GET_COMPLETIONS`. When the number smaller than 1 is or larger
than the number of arguments nix coredumps as a assert is violated.

This change removes the assert and instead throws an exception informing
the user that their autocomplete is most likely misconfigured.

Change-Id: I821719e470e576b6f63c06beb097338b53d183e0
2025-05-27 14:36:51 +00:00
Raito Bezarius 1e40171ea4 libexpr: coerce integers under the XP feature coerce-integers
This introduces a new (demanded?) feature for coercing integers in
interpolation arguments under the experimental feature
`coerce-integers`.

This feature is being introduced behind an *experimental feature flag*
due to the cautious approach we're taking. The codebase has a track
record of revealing unexpected behaviors, often in subtle ways, so we
want to give this sufficient time and exposure before making it stable.

To remove the experimental flag, we want to see **at least two releases
or six months of real-world usage -- whichever is longer** -- that
demonstrate strong confidence the feature doesn't introduce regressions
or unintended side effects. If that level of confidence is reached,
we'll proceed to stabilize it.

Change-Id: I825904719eeba8f0e2a93cd6b93cfe6cebd7d827
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-05-27 11:42:53 +02:00
71rdandLix Systems Gerrit e27c86ad12 Merge "main: avoid crashing when aborting completions" into main 2025-05-26 21:34:24 +00:00
71rdandLix Systems Gerrit be138a555f Merge "main: fix complete outputting doc page" into main 2025-05-26 21:33:41 +00:00
71rdandLix Systems Gerrit fb084e30c5 Merge "docs: add missing setup step for building in shell" into main 2025-05-26 18:15:50 +00:00
git@71rd.net e360cf3a28 main: avoid crashing when aborting completions
To print completions lix created a Finally object containing the actual function,
so the function was executed by the destructor of the class.
Unfortunately  aborting autocomplete by sending a SIGINT signal
(i.E. by pressing C-c) leads to an exception, that finally cant return or eat,
when throwing its own exception.

To avoid crashing when using auto complete let the function "mainWrapped" execute
the autocomplete code directly before returning.
This avoids creating the "Finally" object and instead moves the codeblock next to
the check to return when "arg.completions" is called.

Change-Id: Id333a60ad43c6095e8866f6953af78d51fd43b64
2025-05-26 16:44:44 +00:00
git@71rd.net 826cfbc41b main: fix complete outputting doc page
When calling completion on a nix command containing the word
"--help" nix would first return the entire help page for the
command and then the result of the completion resulting in unusable
output.

By moving the check whether to return when completions were requested
before the check whether help was requested wrappedMain returns
without wrongly printing documentation.

Change-Id: Iedb37434a3ff101f15985319a9a3bcb3f8195796
2025-05-26 16:30:15 +00:00
Linus HeckemannandLix Systems Gerrit 6c773375c2 Merge "doc: explain runtime and indirect roots, merge roots section into GC section" into main 2025-05-26 15:34:25 +00:00
git@71rd.net aa8e07d8b6 docs: add missing setup step for building in shell
Change-Id: Ia02e4c994b13defbefb46b0cf21e1254d13166d9
2025-05-25 20:49:24 +00:00
Commentator2.0 dbff52bfbc tests/functional2: improve commands ux
with_env now overrides the environment, similar to with_stdin
an additional function update_env was created to mirror the prior
functionality of with_env, updating the env

This was changed as previously it was impossible to delete variables
from the env

replaced the code of .ok() with a call to .expect, to remove the code
duplication

Change-Id: I83933893c7f2ccfdc7bd4933b7592b475c435e76
2025-05-25 16:21:28 +02:00
Commentator2.0 438cb4cb31 tests/functional2: move commands to own lib file
Currently the Command and CommandResult classes are mixed into the nix
fixture file.
This commit moves them out into their own lib file, to make it more
obvious that they can be used standalone for other applications too

Additionally improved documentation of said classes
and bumped log level of stdout and err on unexpected exitcodes, as it is
within an error context

Change-Id: If2d554acde86fd54f2445fc46453f06923af5fe9
2025-05-24 14:38:56 +02:00
piegamesandLix Systems Gerrit 9cbffbbbb9 Merge "tests/functional/lang: Clean up lib.nix" into main 2025-05-24 10:57:02 +00:00
piegames 1071643259 tests/functional/lang: Clean up lib.nix
That file was written once in 2008 and never updated since, and let's
just say that a lot of things have changed since

Change-Id: I66b0c87ecbba6ca653470966c9514edb21882ca3
2025-05-24 08:49:09 +02:00
Linus HeckemannandLix Systems Gerrit bbc9aaf8a6 Merge "build: disable LTO on Darwin" into main 2025-05-23 12:27:04 +00:00
Tom Hubrecht 76524b92ee libfetchers: factorize inputFromAttrs
Each `inputFromAttrs` is roughly the same function in each class, we
check that the attributes given are correct (in term of keys and other
types) then we coppy the attributes. Instead of having the same code
copied in 10 places, set it in the parent class and specify what is
specific per child class.

Change-Id: If9aecb76cff1e28a1ef6668d83d825686cce8353
2025-05-22 14:00:19 +02:00
Commentator2.0 5daddad39a tests/functional2: Fix pytest garbage collection
Due to nix-store making its paths read-only, pytest was unable to remove
the test files and hence the entire temporary directory, screaming all
over the place in stderr about that, getting worse for each test run.

By making the nix fixture first yield nix and then, after the test
finished running, changing the file permissions to include read on all
files and directories within the temp folder, pytest is able to properly
remove old test runs again

Additionally added more clear instructions for file deletion to the
pytest configuration

Change-Id: Ia7e3d195665968ac80a57d0e525691b28be7f503
2025-05-22 13:51:23 +02:00
Linus Heckemann da94e860dd build: disable LTO on Darwin
Due to https://git.lix.systems/lix-project/lix/issues/832 , Lix 2.93.0
fails to build on Darwin without overrides. Until the root cause has
been determined and fixed, build without LTO.

Change-Id: I4db5eb294d8f19e5a366b1e19efa5a327b3e2e78
2025-05-22 13:34:30 +02:00
piegames 5d49e26f71 tests/functional/lang: Remove disabled tail-call test
It was introduced back in 2013, was disabled in 2014 again for dubious
reasons and according to horrors is unsound anyways and can never really
work.

It was the only disabled test, so I removed the "infrastructure" for
that in the test runner as well. functional2/lang will have much better
ways for skipping tests anyways

Change-Id: Icb8697fb85221e3206fb64cb917c03607ef278a7
2025-05-22 01:48:34 +02:00
piegames 76d6b51f5c tests/functional/lang: Don't pipe input into stdin
Back in the days, this used to be the modus operandi, but then, still
many but less years ago, Eelco came along and changed it to passing in
the actual file. Of course, no motivation was provided, and it was only
done on half of the test runners for some reason, leaving us to wonder
what the true intentions of this code are …

Anyways, with this commit now everything standardises on passing in the
file by path instead of via stdin. Motivation:

- We need to `sed` out the path anyways for various other reasons,
including import tests and path value tests
- Given that, the presumed primary motivation for using stdin in the
first place becomes moot
- Bonus points for giving better error messages, especially in tests
that involve multiple input files

Change-Id: Ic6de1ec24f4c4d3c05e33d1ee053614784677513
2025-05-22 01:48:34 +02:00
piegamesandLix Systems Gerrit f92199d651 Merge changes I186b0edb,Ie394691b into main
* changes:
  tests/functional/lang: Don't use tabs for indentation
  tests/functional/lang: Change base path to the lang directory
2025-05-21 22:17:42 +00:00
Commentator2.0andLix Systems Gerrit dbfb85ab18 Merge "tests/functional2: add snapshot fixture" into main 2025-05-21 19:29:31 +00:00
piegamesandCommentator2.0 e52cad0285 tests/functional/lang: Don't use tabs for indentation
Don't ask me the fuck why, but *somehow* Nix prints error locations
differently if the input file is passed as a path vs through stdin, and
I have a hunch that this might have to do with tabs

Change-Id: I186b0edb90edd48856da3621815463e372c37512
2025-05-21 20:36:20 +02:00
Commentator2.0 573788f75a tests/functional2: add snapshot fixture
Add a snapshot fixture, which allows comparing and updating strings
against external files

resolves #595

Change-Id: I518f594c601eb7805c6492c0352fca753fda04c9
2025-05-21 20:36:20 +02:00
piegamesandCommentator2.0 e2944876cc tests/functional/lang: Change base path to the lang directory
Currently, all tests are relative to `./tests/functional` instead of
`./tests/functional/lang`. Whether this is a historical artefact or as
intended, the current move is to align the tests with the new design of
functional2, preparing them for an easier migration.

Change-Id: Ie394691b071488a8000a005080b9167786d5bd9a
2025-05-21 20:36:20 +02:00
Raito BezariusandLix Systems Gerrit 4f433a6186 Merge changes I9f893374,Ief7a4756 into main
* changes:
  libexpr: rename `forceString` to `isInterpolation`
  libexpr: refactor string coercion modes
2025-05-20 20:45:21 +00:00
eldritch horrors 03da670021 libutil: remove ca-derivations experimental features
also remove all the documentation referencing it, or rewrite the docs
to make sense in the non-floating-content-addressed world we live in.

Change-Id: I724e67839f44cc9f1cfc7d6f1c05252b62752b42
2025-05-20 17:43:46 +00:00
eldritch horrors 8c3c24e5b0 libstore: remove ca database bits from LocalStore
we don't need to worry about leaving around old ca data in the database:
this was always a possiblity when enabling ca derivations, and disabling
them again some time later. behavior is unchanged, but we lose dead code

Change-Id: I8c10ff7fdcee08c3badf23d64403f5ee6452e41e
2025-05-20 17:43:46 +00:00
eldritch horrors 96e28966ab libstore: remove unused BuiltPath bits
Change-Id: I21d0f95a41c22cb64fb80bb696bcb403b5a38fc1
2025-05-20 17:43:46 +00:00
eldritch horrors 5097c5db63 libstore: remove unused DownstreamPlaceholder
Change-Id: I3b71778aa9e92bf8ac0edef0ca929e92010f5c6a
2025-05-20 17:43:46 +00:00
eldritch horrors 34317c0081 libexpr: simplify EvalState::mkOutputString{,Raw}
we no longer need placeholders to represent all derivation output paths
as string context, and thus will not need experimental features either.

Change-Id: I9e86ce86810e976cf8397b2c2f473af11390874c
2025-05-20 17:43:46 +00:00
eldritch horrors ab36085b6b libstore: remove DerivationGoal::queryPartialDerivationOutputMap
it's fully redundant with queryDerivationOutputMap.

Change-Id: I38475ab1249bdf9db66d8538fb230579f036a3f2
2025-05-20 17:43:46 +00:00
eldritch horrors 6785f5c720 libstore: rename query{,Static}PartialDerivationOutputMap
neither are actually partial now, and the the non-Static variant has a
non-Partial wrapper which merely returns the Partial result unchanged.

Change-Id: I5fa86682883c2305cc12c711ccff58537b7a278d
2025-05-20 17:43:46 +00:00
eldritch horrors 6b5f82e78b libstore: deoptionalize queryPartialDerivationOutputMap
derivation outpaths are now statically known at all times. the one snag
here is that the wires encode even statically known paths as optionals,
forcing us to check for this any time we receive an output map. remotes
answering with nullopt paths for derivations we still support now would
be a protocol error on its own though, so we do not diagnose it deeply.

Change-Id: Ib7080b2a0c45c3506233e87c8ef6842576f61050
2025-05-20 17:43:46 +00:00
eldritch horrors ca7f6ff96b libstore: remove unused realisation methods
Change-Id: I7e7371cdbe477f25e9410272ad635eddadf32101
2025-05-20 17:43:46 +00:00
eldritch horrors 68ab8797b5 libstore: remove unused realisation disk caching
we don't need to touch the schema of the cache here. keeping the table
around doesn't hurt (and avoids cppnix breakage) thanks to foreign key
constraints and the ca bits of the schema being independent enough for
us to just ignore them (and not having to do any maintenance on them).

Change-Id: Ib5d8eb1cd838826d88eb65bbf8f245703a2482da
2025-05-20 17:43:46 +00:00
eldritch horrors 976f6de81e libstore: remove realisation query support
only a daemon wire operation and the perl bindings could initiate these
queries at this point. the daemon ops can throw an error instead (as if
the daemon were older) and realistically should never be queries if the
client hasn't evaluated a ca derivation on a given store, and perl code
is best off dying early. nothing known except hydra uses these bdingins
anyway, and we control our hydra so we don't need backward compat code.

Change-Id: Ia7df27aba59a4a4a692ae014f407415f3bea63f2
2025-05-20 17:43:46 +00:00
eldritch horrors aa69d39c0f libstore: drop feature-gated realisation queries
these will never run without the ability to enable the feature.

Change-Id: I917024e8c3c5b1f422c9e5a509130998bee4e511
2025-05-20 17:43:46 +00:00
eldritch horrors dc47f9aa72 libstore: don't return optionals from Derivation::path
output paths are always known now that CA and deferred outputs are gone.

Change-Id: I359d13ffb5141f1e07a5fc55425831af3332c22e
2025-05-20 17:43:46 +00:00
eldritch horrors 1cbb6ba21c libstore: remove Store::registerDrvOutput
it's only used by the RegisterDrvOutput daemon wire operation now, and
that one we can safely stub out to throw an error when called instead.

Change-Id: If29716976392c9c7a2a05b151dfe80b2c8d9c07d
2025-05-20 17:43:46 +00:00
eldritch horrors f25dc923ca libstore: remove ca support from common store api
this removes the ca-derivations system feature and, perhaps most
importantly, realisation closure copy support. the latter is not
needed any more and its existence blocks some more code removal.

Change-Id: I2931b03637e25d35252ae6bd5f34f0c0168d80e9
2025-05-20 17:43:46 +00:00
eldritch horrors 484319fd2d libstore: remove unused Derivation::tryResolve
Change-Id: Ia36b066badf60b3727ec6c6a04057c3c97461e2c
2025-05-20 17:43:46 +00:00
eldritch horrors 01dcbf3359 libstore: remove Derivation::hasKnownOutputPaths
it's always true now that floating and deferred outputs are gone.

Change-Id: Ie694b9af4d2c247c0fb4fdebadd55a0a487b9828
2025-05-20 17:43:46 +00:00
eldritch horrors e543ac686f libstore: remove DerivationOutput::Deferred
we can't create these any more except by reading an old json-formatted
derivation that used them. since we cannot do anything with a deferred
derivation even when read we will remove json support for them as well

Change-Id: I4f9ea0b7c6469f57977784037f7710f939e40a2c
2025-05-20 17:43:46 +00:00
eldritch horrors d03be35c44 libstore: remove DrvHash::Kind
now that we have no deferred hashes (since floating ca derivations were
the only way to create them) we can safely remove this enumeration too.

Change-Id: Ic72ed90500fcee7aa5b3b5a302477fa515acf1be
2025-05-20 17:43:46 +00:00
eldritch horrors e3717b728c libstore: trivialize DerivationType::ContentAddressed
only FODs can be content-addressed now, and those are always fixed.
FODs are also never sandboxed, so we do not need that field either.

Change-Id: I1be62b3ec85e08ec003cc8769723328d19777728
2025-05-20 17:43:46 +00:00
eldritch horrors bfd10db217 libstore: remove DerivationOutput::CAFloating
nothing can create floating ca outputs any more.

Change-Id: Ic69f4a22066e1f5c0837f44e8d4fa2d93ca20ff6
2025-05-20 17:43:46 +00:00
eldritch horrors a7866d56b8 cli: remove ca support from commands
this mostly takes the form of removes feature checks and the associated
"ca derivations enabled" branches, but for the realisation info command
turns into a stub. we keep it around for compatibility, but from now on
it will always throws "ca derivations not implemented" errors when run.

Change-Id: I0abea5f76262013415330adcca2b498c6dca555b
2025-05-20 17:43:46 +00:00
Raito Bezarius 3e4bffcc24 libexpr: rename forceString to isInterpolation
`ExprConcatStrings` tracks whether the expression is an interpolation or
not via an obscure boolean called `forceString`.

Instead, we rename it to `isInterpolation`.

This is a breaking change for the JSON AST representation.

Change-Id: I9f89337449b56f6e99a961e21169761f554c9896
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-05-20 19:14:49 +02:00
Raito BezariusandPierre Bourdon dab871f129 libexpr: refactor string coercion modes
Inspired by cl/3191 and
https://git.lix.systems/delroth/lix/commit/ae0247cbb4fc739ab013dc87d02e5f3191cf25ab.

`coerceToString` takes now an enumeration that lives in `value.hh`, this
enumeration is meant to represent increasing subsets of behaviors, e.g.
any level above Strict should do what the previous levels do and extra
behavior until `ToString`, which transforms many Nix values into an
arbitrary string representation, e.g. `null` to `""`.

Change-Id: Ief7a4756e8c0660e197623efebeaf07710746ec7
Signed-off-by: Raito Bezarius <raito@lix.systems>
Co-authored-by: Pierre Bourdon <delroth@gmail.com>
2025-05-20 19:14:49 +02:00
eldritch horrors 6567707dc1 libstore: remove DrvOutputSubstitutionGoal
this goal is only involved for output paths that aren't known at initial
build time, which in turn can only happen if they are ca paths. since we
can no longer create ca derivations during eval *or* read them from disk
we can now assume that we will never run this goal. there are still some
vestiges like output known-ness we can't remove yet, so those must stay.

Change-Id: I989e5ad4600c628bcbe8e17e1b082ce8d73a3bd9
2025-05-20 11:28:12 +00:00
eldritch horrors b60735791e libstore: remove unused drvOutputReferences
ca derivation build was the last remaining user.

Change-Id: Ib0e6d954e7802a53d1328c4c18f5f12dddeb838a
2025-05-20 13:27:04 +02:00
eldritch horrors d4e88b98e9 libstore: remove ca derivation build support
we remove not only support for *building* a ca derivation, but also
support for *resolving* ca derivations as part of a build. we never
have to resolve derivations from here on, so this code is now dead.

Change-Id: I0346442d5fa00eb927177545ae61315f588477cc
2025-05-20 13:27:04 +02:00
eldritch horrors 2cc420c1e5 libstore: remove ca derivation read support
we can now no longer read ca store derivations from disk.

Change-Id: I233edea597b550dd7e8c78a555b7f12760a993dd
2025-05-20 13:27:04 +02:00
eldritch horrors 8d5bc9ed48 libexpr: remove ca derivation eval support
Change-Id: I8c06825d0fa7544b8bc9e3bda948e84a5f21ee16
2025-05-20 13:27:04 +02:00
eldritch horrors dad28eca75 cli: disallow ca derivations
we no longer have any experimental features depending on ca derivations,
so we can start removing them. since ca derivations are very invasive we
will need a while to remove all of the explicitly experimental code, and
even then we will not have removed *all* code related to ca derivations.
especially in the derivation goals there is a lot of code that is not as
easy to disentangle from experimental features as some would have hoped.

Change-Id: Ia456aadc6164613ded343f571318494d9310a549
2025-05-20 13:27:04 +02:00
Linus Heckemann 498e828efe doc: explain runtime and indirect roots, merge roots section into GC section
Change-Id: Ib2547c04c938af8fc7346f49616085f514c81749
2025-05-20 12:52:51 +02:00
piegames 0fbbb1e49b libexpr: Switch StaticEnv to LinearMap
Change-Id: If98bfafce9fa5235fe962274c03c619fe965dd60
2025-05-19 16:21:10 +02:00
piegamesandLix Systems Gerrit eb18a90afb Merge "libutil: Introduce LinearMap" into main 2025-05-19 14:20:34 +00:00
piegames bd8ec106fa libutil: Introduce LinearMap
Change-Id: I68ce4c1dc17b0742690e49f62206c65f5a1a4a30
2025-05-19 15:35:52 +02:00
Lily BallardandLix Systems Gerrit 8fa0363b91 Merge "libutil: read window size from stdout if stderr fails" into main 2025-05-18 21:09:16 +00:00
Raito BezariusandLix Systems Gerrit cfaeed469b Merge "libstore/ssh: remove echo started check" into main 2025-05-18 19:51:20 +00:00
Raito Bezarius 0dd8bf6c1c libstore/ssh: remove echo started check
The pre-flight `echo started` check over SSH was originally added in
577ebeaefb. As it is usual with these old
commits, understanding why is there a need for something is difficult.

The closest thing would be
> Fix a race starting the SSH master. We now wait synchronously for
> the SSH master to finish starting. This prevents the SSH clients
> from starting their own connections.

But, we removed SSH connection sharing, so this does not apply anymore.

Nonetheless, we believed this check was meant as a way to catch obvious
misconfigurations or SSH failures early, before handing off to
`nix-store`. However, this approach was not fruitful: it assumes the
remote has a `bash`-compatible shell, `echo` behaves in a standard way,
and no `ForceCommand` interferes—all of which are unreliable assumptions
in practice.

While the intent was to provide slightly better diagnostics (e.g. in
case of SSH hanging or returning an interactive shell), in practice it
does not meaningfully catch or improve real failure cases. The
underlying protocol or engine can and should handle those errors more
robustly anyway.

In contrast, this check *does* break several legitimate workflows,
including:

* remote builders using `ForceCommand` wrappers (e.g.
`nix-remote-build`-style setups), see
<https://discourse.nixos.org/t/wrapper-to-restrict-builder-access-through-ssh-worth-upstreaming/25834/15>,

* SSHing into minimal environments lacking `bash` (e.g. initrd,
busybox-based systems),

* configurations that don’t default to POSIX-like shells, e.g., nushell
enthusiasts.

As such, we’re removing this code. Protocol mismatch errors and SSH
failures can be rethought and handled more structurally elsewhere in the
engine.

Change-Id: I187f6881375d42ef83987a13a350c97964bbdb30
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-05-18 18:45:26 +02:00
Lily Ballard 8630cedbca libutil: read window size from stdout if stderr fails
This fixes the output of `nix help 2>/dev/null` so it stops wrapping at
60 columns.

Change-Id: Id0000000842b5befd73afbd6aff2825bdeae46af
2025-05-17 12:19:06 -07:00
Linus HeckemannandLix Systems Gerrit a7634f87aa Merge "libstore/local-derivation-goal: better debuggability for FOD mismatches" into main 2025-05-16 15:18:11 +00:00
eldritch horrors 8029cec3c0 libstore: remove some unused code
these must've been left around by accident if past cleanups.

Change-Id: I3458ae45e362e7912a6bbe1f31956c41094144d3
2025-05-16 13:16:27 +02:00
Rebecca Turner 3f355b8fd1 .editorconfig: json: init with 4 space indent
Relevant for `version.json`. Noticed while running the 2.93.0 release.

Change-Id: I9f740a3cd412c1d27a76c3feb4e05d89319de3b4
2025-05-15 15:11:57 -07:00
Rebecca Turner 61920dd663 releng: Update README.md documentation
These are (polished versions of) some notes I wrote down while preparing
the 2.93.0 release with Jade.

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

Change-Id: Ib7f0b83ce2984a86d3a0c354e707fc6ee569a7ea
2025-05-15 10:47:10 -07:00
Lily BallardandLix Systems Gerrit b7ce00fc55 Merge "libutil: move filterANSIEscapes tests" into main 2025-05-15 01:42:34 +00:00
rebecca “wiggles” turnerandLix Systems Gerrit fed92d56ee Merge changes Id732c31a,I022583e9,I2f08bd2f,I85090f4c into main
* changes:
  aws s3 cp: Note issue with `--checksum-algorithm=SHA256`
  releng/docker_assemble.py: fix empty `auths` error
  releng: remove unused variables
  releng: ignore `prev-git-branch.txt`
2025-05-14 19:15:53 +00:00
Lily BallardandLix Systems Gerrit 5ed5efe6fc Merge "libcmd: use correct stream for ANSI testing for markdown" into main 2025-05-14 02:15:18 +00:00
Lily Ballard bc5d7ad458 libutil: move filterANSIEscapes tests
This moves the original test suite for `filterANSIEscapes` into the same
file as the newer tests. There is some overlap between the old and new
tests but that doesn't hurt anything so I kept them as-is.

Change-Id: Id00000009919024a5f206ec9a7bc0022541ff612
2025-05-13 19:04:52 -07:00
Lily BallardandLix Systems Gerrit 6237c50161 Merge "libutil: handle OSC escapes in filterANSIEscapes()" into main 2025-05-14 02:02:08 +00:00
Lily Ballard 207b5d81bf libutil: handle OSC escapes in filterANSIEscapes()
This teaches `filterANSIEscapes()` how to find the end of an OSC
sequence. It also keeps OSC 8 (hyperlinks) when not instructed to filter
out all escapes, just as it keeps colors.

This also relaxes the parsing of CSI escapes to find the end of the
sequence for invalid sequences, and handles better escapes that don't
start CSI or OSC.

This fixes the repl output for `:doc builtins.fetchGit`.

Fixes: https://git.lix.systems/lix-project/lix/issues/160
Change-Id: Id0000000f2a6956c042c883a4545edf347fa1799
2025-05-13 18:59:08 -07:00
Rebecca Turner a3b3b06a21 aws s3 cp: Note issue with --checksum-algorithm=SHA256
There may or may not be a bug in `garage` here. Previously we added
`--checksum-algorithm=SHA256` here to fix it, but when @rbt was
running the release for 2.93.0, she found it actually made the S3
uploads fail.

If this command is failing, here are some links to investigate.

See: https://git.deuxfleurs.fr/Deuxfleurs/garage/issues/963
See: https://git.deuxfleurs.fr/Deuxfleurs/garage/issues/1017
See: https://github.com/boto/boto3/issues/4392
See: https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-envvars.html#envvars-list-AWS_REQUEST_CHECKSUM_CALCULATION
Change-Id: Id732c31aa715191e78c7f0246e1b74cc202e675c
2025-05-13 12:45:21 -07:00
Rebecca Turner 4611d1f331 releng/docker_assemble.py: fix empty auths error
I had a `~/.docker/config.json` which was missing an `auths` key, which
caused an error. The release automation succesfully ignored the error,
but it was noisy. Using `json_obj.get('auths', {})` instead of
`json_obj['auths']` fixes this `KeyError`.

Change-Id: I022583e9e668bf8ad7bdc1fa5a3305aee2f18d85
2025-05-13 12:18:32 -07:00
Rebecca Turner 2e25580d8d releng: remove unused variables
`lib` and `config` were unused here. In the future maybe we should
integrate `deadnix` or something similar for linting.

Change-Id: I2f08bd2f87f74b90a5f76ea7db7e6d4db1663450
2025-05-13 12:18:31 -07:00
Rebecca Turner 53673b7331 releng: ignore prev-git-branch.txt
This file is created by `python -m releng tag`.

Change-Id: I85090f4c6d9c2ba9a991b47df6a7382b80f7ff52
2025-05-13 12:18:31 -07:00
Raito BezariusandLix Systems Gerrit 901940df00 Merge "libstore/profiles: do not dereference current generation if it doesn't exist" into main 2025-05-12 22:47:48 +00:00
eldritch horrors 3684c2f8a0 libutil: remove dynamic derivations feature
Change-Id: Id48c775197e89bfda711c5cb03752980785c3d26
2025-05-12 13:37:54 +02:00
eldritch horrors ccdd916226 libstore: de-ref {Derived,Built}Path::Built drvPath
they're no longer recursive, so this is perfectly fine.

Change-Id: If565a557f2c2074e2a96a7f89c51ff1c51146b36
2025-05-12 13:37:54 +02:00
eldritch horrors 84c1df46ea libstore: remove DerivedPathMap
single-level maps suffice now that dynamic derivations are gone.

Change-Id: If29998b104b31255292ab0c789622d7d27040f69
2025-05-12 13:37:54 +02:00
eldritch horrors 68dfcfc6a4 treewide: don't resolveDerivedPath opaque paths
resolution of opaque paths is just an expensive `->path`.

Change-Id: I0c8d8b908f358d0bd26d1dcba84b2de6cbdc7c29
2025-05-12 13:37:54 +02:00
eldritch horrors f5e2e78266 libexpr: remove more obsolete DerivedPath methods
split from the prior commit for easier review.

Change-Id: Iabf1bc759cb56a04211d4cb07769f53b0f302b39
2025-05-12 13:37:54 +02:00
eldritch horrors 5f723e96e6 libstore: flatten {,Single}{Built,Derived}Path
only dynamic derivations could produce a non-opaque drvPath. since
dynamic derivations are no longer supported we can have drvPath be
opaque at all times, simplifying downstream code significantly and
making quite a few methods unnecessary. discardOutputPath was only
called on drvPath members anyway and thus reduces to a copy, other
operations at the very least are no longer recursive. some vestige
of dynamic derivations remains in DerivedPathMap though (for now).

Change-Id: Ifb4ad53a3c67800be5a62540068c8279d4ae0046
2025-05-12 13:37:54 +02:00
eldritch horrors 8a539424c8 libexpr: drop support for dyn-drv string context
string context doesn't need any tests because it's never persisted or
shown to the user. getting rid of recursive string context means that
the context string parsers can be a lot simpler from here on forward.

Change-Id: I58443679ad76c0f28ea5f4eb8bfb3874f270e764
2025-05-12 13:37:54 +02:00
eldritch horrors a490e2d946 libstore: remove ability to read or write dyn-drvs
as with impure derivations it is still possible to garbage-collect
existing xp-dyn-drv derivations. we once again don't introduce any
new kinds of errors, we only change the dynamic type of exceptions
from MissingExperimentalFeature to UnimplementedError (although we
do throw FormatError when reading xp-dyn-drv derivations now, that
seems to make a little more sense than "feature not implemented").

Change-Id: Ic26e5b6c9c9e2533093e27f6cf901dc9db57c83e
2025-05-12 13:37:54 +02:00
eldritch horrors 5ae8ac84ea libstore: remove dyn-drvs remote store error hack
with dynamic derivations gone this code will never run again.

Change-Id: I7673a81269c33e62c6c184d33cbc9f3ba0079bee
2025-05-12 13:37:54 +02:00
eldritch horrors 936ac14f1a libexpr: disallow creation of text-hashed derivation outputs
only dynamic derivation produce text-hashed derivation outputs. toFile
produces text-hashed store paths, so we cannot remove text hashing now
without breaking stores, but we can disallow it in derivation outputs.

Change-Id: I95ff9882a59153a7d5fd509f5c9fd85925f30d02
2025-05-12 13:37:54 +02:00
eldritch horrors b8b05d4da4 libexpr: remove dynamic derivation eval support
Change-Id: I8bbdaa280f634bafd5abd7034a605564f45978c0
2025-05-12 13:37:54 +02:00
eldritch horrors 540071dd77 cli: disallow dynamic derivations
with impure derivations gone we move on to dynamic derivations. this too
is not done in a single commit because dynamic derivations are invasive,
modifying semantics of all references to derivation output paths and all
derivation dependency calculations. removing dynamic derivations cleanly
is made significantly harder by the multiple did-you-mean-sum types, aka
"wrappers for std::variant", holding all derivation outpath information.

Change-Id: Ice7a7700c7b54c6a6061d4beb322b4175923d27a
2025-05-12 13:37:54 +02:00
Lily Ballard 530532ca8a libcmd: use correct stream for ANSI testing for markdown
Rendering markdown tests if ANSI is supported in order to tell lowdown
to disable ANSI escapes. Unfortunately it was testing stderr and yet
nearly all rendered markdown output was printed to stdout.

Change-Id: Id0000000f0e667d235239c095330d355a9b7714a
2025-05-11 17:55:44 -07:00
Lily Ballard e4b48ca3f0 version.json: 2.93.0 -> 2.94.0-dev
Change-Id: Id00000000f2c82a2c5cedd4e962521a34436d45d
2025-05-11 16:51:25 -07:00
eldritch horrors 18aebab9b6 libstore: remove unused resolveDerivedPath overload
Change-Id: Iaaeb688d601a21c817fb0449faf9541ea8e0301a
2025-05-11 21:16:36 +00:00
Linus Heckemann d19593d00b libstore/local-derivation-goal: better debuggability for FOD mismatches
The expected and the obtained path are now printed as part of the
error message, making comparing them easier when they're both at hand.

The extra rethrow for the hash-mismatch exception in the bmCheck case
has been removed, allowing the path to be registered as in the
non-check case. This makes having both paths at hand a lot more likely!

The determinism check logic was incorrect for content-addressed paths,
since it only ever tried to compare the path produced, even if this
was not the path expected (in the case of fixed-output derivations) or
the path previously produced (in the case of non-fixed CA
derivations). This made little sense, because that would always be the
same path if it exists! The determinism check is therefore now
bypassed for CA paths. Having a correct determinism check for
non-fixed CA derivations and running the diff hook for fixed-output
derivations would be nice, but feels out of scope and bypassing the
inapplicable logic isn't a regression from the previous behaviour.

Change-Id: I5fc14fb477c8c7d2f5bdedad5591af916f72b128
2025-05-11 21:22:52 +02:00
YurekaandLix Systems Gerrit b2b519a3af Merge "lix-doc: remove meson pre-1.5 hacks" into main 2025-05-11 19:14:44 +00:00
eldritch horrors 7bbe6fc47f libstore: remove impure-drvs feature
no documentation seems to have existed for this feature.

Change-Id: I3ec8afd9aeedbff1cc5edabc9074df33dae7d357
2025-05-11 17:27:05 +02:00
eldritch horrors be07629820 libstore: remove DerivationType::Impure
all derivations are now pure again, making isPure a constant function.

Change-Id: I2c65a7b6c8255beb97e2bdd280e4b11e39cb4387
2025-05-11 17:27:05 +02:00
eldritch horrors ae98420772 libstore: drop support for representing impure outputs
nothing can create, or even handle, them any more.

Change-Id: I5ef80129d6b734e65633df6eb9f161ffd31d1327
2025-05-11 17:27:05 +02:00
eldritch horrors 2f9a4a71aa libstore: drop support for reading impure derivations
writing them is technically still supported because what makes a
derivation impure is entirely specified by magically named data,
but without derivationStrict being able to pass these through to
libstore there is no way (besides reading existing files) to get
any new impure derivations into an existing store. it will still
be possible to garbage-collect existing impure derivations since
the gc process does not need to read them as derivations, and we
are not introducing any new kinds of unsupported-feature errors.

Change-Id: I648f53129ce67ee2b48d0591219759812dd557da
2025-05-11 17:27:05 +02:00
eldritch horrors 75f234d84b libexpr: remove impure derivation creation support
Change-Id: I58f481c188c6a6e99d226b4862508cef853b7571
2025-05-11 17:27:05 +02:00
eldritch horrors 6599be1a9f cli: disallow impure derivations
we don't remove the entire feature in one go to make review easier.
impure derivations are rather unintrusive on their own, at least if
we compare them to dynamic or ca derivations in general, so we will
be done with this soon. as it stands impure derivations cannot work
without ca derivations, and those we *really* want to leave behind.

Change-Id: I4f01d8d758b2c85dcd6c3078304b5ee1b52f65b0
2025-05-11 17:26:55 +02:00
Raito BezariusandQyriad d19a9e3039 libstore/profiles: do not dereference current generation if it doesn't exist
If the profile inode is invalid, e.g. invalid symlink, the current
generation cannot be discovered.

Nonetheless, this should not be a reason for an assert failure, instead
of crashing, just raise an error.

Fixes fj#801.

Change-Id: I63937672173bc3bf37196de98307800adc5757e1
Signed-off-by: Raito Bezarius <raito@lix.systems>
Co-authored-by: Qyriad <qyriad@qyriad.me>
2025-05-11 01:07:18 +02:00
Commentator2.0andLix Systems Gerrit 1da9c0261e Merge changes I6830c2fc,Ib88565a1,I0b280587 into main
* changes:
  functional2: Added ruff formatter
  functional2: use loggers
  fix codestyle of functional2
2025-05-10 20:49:43 +00:00
Commentator2.0 b17502088d functional2: Added ruff formatter
Ruff is used to enforce our code-style for the python parts of the
reposity, similar to clang-tidy for the cpp parts.

This includes a pre-commit hook to format code before it is committed
When "unfixable" - i.e. no autoformatting is available - the commit is
rejected

resolves #812

Change-Id: I6830c2fc29ae86337ec18f2b0e3565fac66c5523
2025-05-10 22:14:10 +02:00
Commentator2.0 01985e5add functional2: use loggers
Use logger in favor over print statment.
This is explicitly supported and encuraged by pytest, which also allows
for capturing logs separate from stdout calls, which is handy for when
e.g. lix code calls out to stdout to keep those differentiated from test
output

Change-Id: Ib88565a1663da3b77ca6b95f8edf644eafb4a99d
2025-05-10 21:13:45 +02:00
Commentator2.0 427696a58d fix codestyle of functional2
Fixing up codestyle issues found within functional2 for later adding
ruff formatter

Change-Id: I0b280587c8243137184091a6d36df3dfe7568eb7
2025-05-10 21:13:45 +02:00
eldritch horrors d8e2f53d07 deprecate CA, dynamic, and impure derivations
ca derivations are what we're really after, but dynamic derivations
must also go because they depend on ca derivations. we can't easily
implement dynamic derivations any other way, so we remove them too.
impure derivations build on the content-addressed infrastructure in
ways we cannot easily detangle, so they too must go for time being.

see #815

Change-Id: If61371736dfd89cc71a1b2ae5a005757c3cb9484
2025-05-10 17:15:26 +00:00
benaryorg fcd967da16 libexpr: fetchGit output documentation
Documentation for the output of `builtins.fetchGit`.
In particular this includes details on the both `lastModifiedDate` and `shortRev` which were not readily apparent.
Recommendations are made on the use of `shortRev`; it is considered stable, yet use is discouraged to avoid compatibility and interoperablity issues.

Fixes fj#814

Change-Id: If65c4f84d8a1569dcab2db07f63e69e4053ab74b
Signed-off-by: benaryorg <binary@benary.org>
2025-05-10 13:43:38 +00:00
eldritch horrors 5917db84aa nix: don't send tarballTtl to the daemon
it's an eval-time only setting, the daemon doesn't use it anywhere. this
is a hack, but until we have a much better settings system we are stuck.

fixes #680

Change-Id: I532088b0279f13da0a0a65c2bd2e5f9d1dfb39da
2025-05-10 12:20:09 +00:00
Linus HeckemannandLix Systems Gerrit a10de1ee56 Merge "build-release-notes: ignore dotfiles" into main 2025-05-10 09:53:56 +00:00
Linus Heckemannandjade b0947777ec build-release-notes: ignore dotfiles
Editors may leave these hanging about, but they're not likely to be
valid release notes

Change-Id: Ie3c9a6e849c6d54514ac25370a8d847a0caf499d
2025-05-08 23:47:59 +00:00
Yureka 5d0213ac55 lix-doc: remove meson pre-1.5 hacks
Change-Id: I9786d59fc849ae93643d6128ebd598d9de8637f6
Signed-off-by: Yureka <yureka@cyberchaos.dev>
2025-05-08 17:40:38 +02:00
674 changed files with 10971 additions and 10788 deletions
+4
View File
@@ -33,3 +33,7 @@ max_line_length = 0
[meson.build]
indent_style = space
indent_size = 2
[*.json]
indent_style = space
indent_size = 4
+2
View File
@@ -39,3 +39,5 @@ buildtime.bin
# Python compiled files from the code generators and test suite
*.pyc
**/.idea
+19 -6
View File
@@ -66,13 +66,13 @@ delan:
forgejo: delan
github: delan
delroth:
github: delroth
detroyejr:
display_name: Jonathan De Troye
github: detroyejr
edef:
github: edef1c
edolstra:
display_name: Eelco Dolstra
github: edolstra
@@ -101,6 +101,9 @@ ian-h-chamberlain:
forgejo: ian-h-chamberlain
github: ian-h-chamberlain
infinisil:
github: infinisil
isabelroses:
forgejo: isabelroses
github: isabelroses
@@ -155,9 +158,17 @@ midnightveil:
forgejo: midnightveil
github: midnightveil
nan-git:
display_name: NaN-git
github: NaN-git
ncfavier:
github: ncfavier
not-my-profile:
display_name: Martin Fischer
github: not-my-profile
p-e-meunier:
display_name: Pierre-Etienne Meunier
github: P-E-Meunier
@@ -200,9 +211,6 @@ roberth:
display_name: Robert Hensing
github: roberth
sandydoo:
github: sandydoo
seppel3210:
github: Seppel3210
@@ -232,6 +240,11 @@ vigress8:
forgejo: vigress8
github: vigress8
vlinkz:
display_name: Victor Fuentes
forgejo: vlinkz
github: vlinkz
winter:
forgejo: winter
github: winterqt
+16
View File
@@ -0,0 +1,16 @@
---
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
@@ -0,0 +1,20 @@
---
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
@@ -0,0 +1,12 @@
---
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
@@ -0,0 +1,69 @@
---
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
@@ -0,0 +1,16 @@
---
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
@@ -0,0 +1,13 @@
---
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
@@ -0,0 +1,24 @@
---
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
@@ -0,0 +1,24 @@
---
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>
```
@@ -0,0 +1,25 @@
---
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
@@ -0,0 +1,47 @@
---
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.
@@ -0,0 +1,11 @@
---
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
@@ -0,0 +1,14 @@
---
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
@@ -0,0 +1,12 @@
---
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.
@@ -0,0 +1,12 @@
---
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
@@ -0,0 +1,20 @@
---
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/
@@ -0,0 +1,21 @@
---
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
@@ -0,0 +1,20 @@
---
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.
@@ -0,0 +1,17 @@
---
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
@@ -0,0 +1,20 @@
---
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
@@ -0,0 +1,32 @@
---
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.
@@ -0,0 +1,15 @@
---
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.
-1
View File
@@ -20,7 +20,6 @@
- [Basic Package Management](package-management/basic-package-mgmt.md)
- [Profiles](package-management/profiles.md)
- [Garbage Collection](package-management/garbage-collection.md)
- [Garbage Collector Roots](package-management/garbage-collector-roots.md)
- [Sharing Packages Between Machines](package-management/sharing-packages.md)
- [Serving a Nix store via HTTP](package-management/binary-cache-substituter.md)
- [Copying Closures via SSH](package-management/copy-closure.md)
@@ -148,8 +148,8 @@ To copy the store path with symbolic name `gcc` from another profile:
$ nix-env --install --from-profile /nix/var/nix/profiles/foo gcc
```
To install a specific [store derivation] (typically created by
`nix-instantiate`):
To install a specific [store derivation](@docroot@/glossary.md#gloss-store-derivation)
(typically created by `nix-instantiate`):
```console
$ nix-env --install /nix/store/fibjb1bfbpm5mrsxc4mh2d8n37sxh91i-gcc-3.4.3.drv
@@ -5,7 +5,7 @@
# Synopsis
`nix-instantiate`
[`--parse` | `--eval` [`--strict`] [`--json`] [`--xml`] ]
[`--parse` | `--eval` [`--strict`] [`--raw`] [`--json`] [`--xml`] ]
[`--read-write-mode`]
[`--arg` *name* *value*]
[{`--attr`| `-A`} *attrPath*]
@@ -107,6 +107,11 @@ 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`
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.
- `--json`\
When used with `--eval`, print the resulting value as an JSON
representation of the abstract syntax tree rather than as a Nix expression.
@@ -15,7 +15,6 @@ Each of *paths* is processed as follows:
1. If it is not [valid], substitute the store derivation file itself.
2. Realise its [output paths]:
- Try to fetch from [substituters] the [store objects] associated with the output paths in the store derivation's [closure].
- With [content-addressed derivations] (experimental): Determine the output paths to realise by querying content-addressed realisation entries in the [Nix database].
- For any store paths that cannot be substituted, produce the required store objects. This involves first realising all outputs of the derivation's dependencies and then running the derivation's [`builder`](@docroot@/language/derivations.md#attr-builder) executable. <!-- TODO: Link to build process page #8888 -->
- Otherwise, and if the path is not already valid: Try to fetch the associated [store objects] in the path's [closure] from [substituters].
@@ -28,7 +27,6 @@ If no substitutes are available and no store derivation is given, realisation fa
[store objects]: @docroot@/glossary.md#gloss-store-object
[closure]: @docroot@/glossary.md#gloss-closure
[substituters]: @docroot@/command-ref/conf-file.md#conf-substituters
[content-addressed derivations]: @docroot@/contributing/experimental-features.md#xp-feature-ca-derivations
[Nix database]: @docroot@/glossary.md#gloss-nix-database
The resulting paths are printed on standard output.
+1 -1
View File
@@ -39,7 +39,7 @@ $ nix-shell -A native-clangStdenvPackages
### Building from the development shell
Run a clean build and test with `just clean build install test`.
Run a clean build and test with `just clean setup build install test`.
You can also run the unit tests and integration tests separately:
-3
View File
@@ -449,9 +449,6 @@ I grepped `lix/` for `get[eE]nv\("` to find the mentions in Lix code.
- `NIX_CLIENT_PACKAGE` - Runs the test suite against an alternate Nix client with the current daemon.
**Expected value**: something like `/nix/store/...-nix-2.18.2`
- `NIX_TESTS_CA_BY_DEFAULT` - Pass `__contentAddressed`, `outputHashMode` and `outputHashAlgo` to builds of some input-addressed derivations in the test suite.
**Expected value**: 1
- `TEST_DATA` - Not an environment variable! This is used in repl characterization tests to refer to `tests/functional/repl_characterization/data`.
More specifically, that path is replaced with the string `$TEST_DATA` in output for reproducibility.
- `TEST_HOME` (output) - Set to the temporary directory that is set as `$HOME` inside the tests, underneath `$TEST_ROOT`.
+1 -8
View File
@@ -41,12 +41,6 @@
[realise]: #gloss-realise
- [content-addressed derivation]{#gloss-content-addressed-derivation}
A derivation which has the
[`__contentAddressed`](./language/advanced-attributes.md#adv-attr-__contentAddressed)
attribute set to `true`.
- [fixed-output derivation]{#gloss-fixed-output-derivation}
A derivation which includes the
@@ -114,14 +108,13 @@
- [input-addressed store object]{#gloss-input-addressed-store-object}
A store object produced by building a
non-[content-addressed](#gloss-content-addressed-derivation),
non-[fixed-output](#gloss-fixed-output-derivation)
derivation.
- [output-addressed store object]{#gloss-output-addressed-store-object}
A [store object] whose [store path] is determined by its contents.
This includes derivations, the outputs of [content-addressed derivations](#gloss-content-addressed-derivation), and the outputs of [fixed-output derivations](#gloss-fixed-output-derivation).
This includes derivations and the outputs of [fixed-output derivations](#gloss-fixed-output-derivation).
- [substitute]{#gloss-substitute}
@@ -209,15 +209,8 @@ Derivations can declare some infrequently used optional attributes.
- [`__contentAddressed`]{#adv-attr-__contentAddressed}
> **Warning**
> This attribute is part of an [experimental feature](@docroot@/contributing/experimental-features.md).
>
> To use this attribute, you must enable the
> [`ca-derivations`](@docroot@/contributing/experimental-features.md#xp-feature-ca-derivations) experimental feature.
> For example, in [nix.conf](../command-ref/conf-file.md) you could add:
>
> ```
> extra-experimental-features = ca-derivations
> ```
> This attribute is part of a removed [experimental feature](@docroot@/contributing/experimental-features.md).
> Setting this flag *will* cause eval errors.
If this attribute is set to `true`, then the derivation
outputs will be stored in a content-addressed location rather than the
@@ -71,3 +71,62 @@ $ nix-collect-garbage -d
```
is a quick and easy way to clean up your system.
## Garbage Collector Roots
### Explicit roots
All store paths to which there are symlinks in the directory
`prefix/nix/var/nix/gcroots` will be used as roots by the garbage
collector. For instance, the following command makes the path
`/nix/store/d718ef...-foo` a root of the collector:
```console
$ ln -s /nix/store/d718ef...-foo /nix/var/nix/gcroots/bar
```
That is, after this command, the garbage collector will not remove
`/nix/store/d718ef...-foo` or any of its dependencies.
Subdirectories of `prefix/nix/var/nix/gcroots` are also searched for
symlinks.
Symlinks may also point to paths outside the nix store. If the
destination of the symlink is itself a symlink to a store path, it
is also considered a root. This style of GC root is called an
"indirect root", and is created by tools like `nix-build` to avoid
garbage-collecting paths that are being used on-the-fly rather than
installed in profiles.
### In-use roots
Lix will also perform a best-effort detection of paths that are in use
by running processes when scanning for garbage collection roots, to
avoid removing paths that are still needed by running processes.
Exact details vary between platforms, but the following will generally
be taken into account:
- Executables in the store that are currently running;
- Other files in the store that are mapped into a process's address space (e.g. shared libraries);
- Files in the store to which processes have open handles;
- Store paths found in processes' environment variables.
Note that this detection is susceptible to missing paths that may still be in use for multiple reasons:
- Time-of-check-to-time-of-use (TOCTTOU): new processes may appear
after Lix has enumerated the currently running processes, and will
not be taken into account;
- Access privileges: if the garbage collection is not running as the
root user (this is typically the case for single-user
installations), it will not be able to scan processes belonging to
other users;
- Other types of references: store paths may be stored in parts of the
filesystem (e.g. databases) or process memory (e.g. environment
variables changed since the start of the process) that Lix does not
scan.
For this reason, it is recommended to create explicit roots whenever
using store paths that aren't obtained from some existing explicit GC
root.
@@ -1,18 +0,0 @@
# Garbage Collector Roots
The roots of the garbage collector are all store paths to which there
are symlinks in the directory `prefix/nix/var/nix/gcroots`. For
instance, the following command makes the path
`/nix/store/d718ef...-foo` a root of the collector:
```console
$ ln -s /nix/store/d718ef...-foo /nix/var/nix/gcroots/bar
```
That is, after this command, the garbage collector will not remove
`/nix/store/d718ef...-foo` or any of its dependencies.
Subdirectories of `prefix/nix/var/nix/gcroots` are also searched for
symlinks. Symlinks to non-store paths are followed and searched for
roots, but symlinks to non-store paths *inside* the paths reached in
that way are not followed to prevent infinite recursion.
+4 -14
View File
@@ -2,18 +2,8 @@
For historical reasons, [derivations](@docroot@/glossary.md#gloss-store-derivation) are stored on-disk in [ATerm](https://homepages.cwi.nl/~daybuild/daily-books/technology/aterm-guide/aterm-guide.html) format.
Derivations are serialised in one of the following formats:
Derivations are serialised in the following format:
- ```
Derive(...)
```
For all stable derivations.
- ```
DrvWithVersion(<version-string>, ...)
```
The only `version-string`s that are in use today are for [experimental features](@docroot@/contributing/experimental-features.md):
- `"xp-dyn-drv"` for the [`dynamic-derivations`](@docroot@/contributing/experimental-features.md#xp-feature-dynamic-derivations) experimental feature.
```
Derive(...)
```
-238
View File
@@ -1,242 +1,4 @@
# Lix 2.93 "Bici Bici" (2025-05-09)
# Lix 2.93.4 (2026-05-04)
## 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.
- 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.
- 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) [cl/4214](https://gerrit.lix.systems/c/lix/+/4214)
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-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.
- Fix unsigned overflow leading to out-of-band write in the NAR parser [cl/5537](https://gerrit.lix.systems/c/lix/+/5537)
The NAR parser contained an unsigned integer overflow that could be used by an
attacker to write arbitrary data to an unknown memory location and possibly
achieve code execution. A successful attack on the system-wide Lix daemon
could lead to privilege escalation to root. Any process that involves NAR
serialization could trigger this issue, including (but not limited to)
- local user interaction, whether the users are trusted or untrusted
- malicious substituters sending malformed NARs
- remote builders sending malformed build results
- remote daemons sending malformed inputs when requesting remote builds
Successful attacks using this bug require ASLR weakening of some sort, whether
by architecture constraints (e.g. on 32 bit systems, where little randomization
is possible) or system configuration (e.g. low ASLR entropy when loading
libraries), and millions of attempts. Local attacks can be mounted in less than
an hour. Remote builds typically require a fresh SSH connection for each build
and are thus less susceptible. Only one attempt can be made by substituters for
every build using substituters, they are thus not a likely vector for attacks.
At the time of writing, MITRE has not assigned this a CVE yet.
Many thanks to [eldritch horrors](https://git.lix.systems/pennae), [Raito Bezarius](https://git.lix.systems/raito), [edef](https://github.com/edef1c), and [sandydoo](https://github.com/sandydoo) for this.
# Lix 2.93.3 (2025-07-22)
## Improvements
- `--keep-failed` chowns the build directory to the user that request the build [cl/3678](https://gerrit.lix.systems/c/lix/+/3678)
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.
# Lix 2.93.2 (2025-06-30)
## Fixes
- Revert CVE-2025-52992 failed mitigation [fj#883](https://git.lix.systems/lix-project/lix/issues/883) [fj#887](https://git.lix.systems/lix-project/lix/issues/887) [cl/3444](https://gerrit.lix.systems/c/lix/+/3444) [cl/3528](https://gerrit.lix.systems/c/lix/+/3528)
Following the initial mitigation of **CVE-2025-52992** in `cl/3444`, we
received reports of **unexpected deletion of in-use store paths**.
Upon investigation, we found that the patch did **not correctly cancel all
automatic deleters**, resulting in potentially critical path loss during normal
operation.
Given the severity and time-sensitive nature of the situation ([see incident
report](https://lix.systems/blog/2025-06-27-lix-critical-bug/)), we evaluated
possible options to repair the behavior safely. However, we concluded that a
rushed fix would either
* **Overdelete**, i.e. breaking running systems, or,
* **Underdelete**, effectively **reopening CVE-2025-52992** while leaving
orphaned paths behind.
As **CVE-2025-52992 has no known exploit vector**, and correctness is critical
in the Lix project, we have **fully reverted the previous mitigations**.
The affected patches (`cl/3444`) have been rolled back for the time being.
Moving forward, the Lix team will rework this code path in a **long-term,
correctness-first fix** on the main branch. We will explore backporting it to
stable channels once its safety is assured.
We are deeply sorry for the stability incident and the Lix team remain
available for assisting you in recovering your systems.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) and [eldritch horrors](https://git.lix.systems/pennae) 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.
# Lix 2.93.1 (2025-06-23)
## Breaking Changes
- Fixed output derivations can be run using `pasta` network isolation [fj#285](https://git.lix.systems/lix-project/lix/issues/285) [cl/3442](https://gerrit.lix.systems/c/lix/+/3442)
Fixed output derivations traditionally run in the host network namespace.
On Linux this allows such derivations to communicate with other sandboxes
or the host using the abstract Unix domains socket namespace; this hasn't
been unproblematic in the past and has been used in two distinct exploits
to break out of the sandbox. For this reason fixed output derivations can
now run in a network namespace (provided by [`pasta`]), restricted to TCP
and UDP communication with the rest of the world. When enabled this could
be a breaking change and we classify it as such, even though we don't yet
enable or require such isolation by default. We may enforce this in later
releases of Lix once we have sufficient confidence that breakage is rare.
[`pasta`]: https://passt.top/
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) and [puck](https://git.lix.systems/puck) for this.
## Fixes
- Always clean up scratch paths after derivations failed to build [cl/3444](https://gerrit.lix.systems/c/lix/+/3444)
Previously, scratch paths created during builds were not always cleaned up if
the derivation failed, potentially leaving behind unnecessary temporary files
or directories in the Nix store.
This fix ensures that such paths are consistently removed after a failed build,
improving Nix store hygiene, hardening Lix against mis-reuse of failed builds
scratch paths.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) and [eldritch horrors](https://git.lix.systems/pennae) for this.
- `build-dir` no longer defaults to `temp-dir` [cl/3443](https://gerrit.lix.systems/c/lix/+/3443)
The directory in which temporary build directories are created no longer defaults
to the value of the `temp-dir` setting to avoid builders making their directories
world-accessible. This behavior has been used to escape the build sandbox and can
cause build impurities even when not used maliciously. We now default to `builds`
in `NIX_STATE_DIR` (which is `/nix/var/nix/builds` in the default configuration).
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
- 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.
## 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.
# Lix 2.93.0 (2025-05-09)
+31 -31
View File
@@ -62,37 +62,38 @@ let
++ autoLayered
++ extraPkgs;
users = {
users =
{
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!)";
};
}
// lib.listToAttrs (
map (n: {
name = "nixbld${toString n}";
value = {
uid = 30000 + n;
gid = 30000;
groups = [ "nixbld" ];
description = "Nix build user ${toString n}";
root = {
uid = 0;
shell = "${pkgs.bashInteractive}/bin/bash";
home = "/root";
gid = 0;
groups = [ "root" ];
description = "System administrator";
};
}) (lib.lists.range 1 32)
);
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.lists.range 1 32)
);
groups = {
root.gid = 0;
@@ -360,8 +361,7 @@ 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": 1757198069,
"narHash": "sha256-m3VUcOD4rTs8J7S+3dOjWMrAjw6RcITC3XYQ98zhEFs=",
"lastModified": 1749522908,
"narHash": "sha256-eWANkhWXFL1MmaxzsZ9bhLCNT8OVs7CC+OXaSDGlA8A=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "0747026fc57ecb9c28901c7f7a2b5dc40e8af43c",
"rev": "e5cb99555c45a13dcc5f1317462238530b0066b7",
"type": "github"
},
"original": {
+14 -53
View File
@@ -175,13 +175,8 @@
{
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;
};
@@ -219,9 +214,6 @@
inherit versionSuffix officialRelease;
stdenv = currentStdenv;
busybox-sandbox-shell = final.busybox-sandbox-shell or final.default-busybox-sandbox-shell;
# See below
lowdown = final.lowdown_3_0;
lowdown-unsandboxed = final.lowdown_3_0.override { enableDarwinSandbox = false; };
};
lix-clang-tidy = final.callPackage ./subprojects/lix-clang-tidy { };
@@ -246,30 +238,6 @@
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;
# As soon as Nixpkgs updates to >= 3.0.0, change to lowdown_2_0!
# We don't change the default version in order to not change the hash
# of Nix/Lix from upstream Nixpkgs.
lowdown_3_0 =
assert lib.versionOlder prev.lowdown.version "3.0.0";
prev.lowdown.overrideAttrs (
finalAttrs: prevAttrs: {
version = "3.0.1";
src = final.fetchurl {
url = "https://kristaps.bsd.lv/lowdown/snapshots/lowdown-${finalAttrs.version}.tar.gz";
sha512 = "fe68e1b7ff23f3992398356d7aa9a330dfd7b72e22bea9a91eeef74182b209ecea0c9f3e2b2216e1a07b2358da2b746238ec9cbbdeebdd3551cef14dd2d79f46";
};
# no longer compiles with GNU make
nativeBuildInputs = prevAttrs.nativeBuildInputs ++ [ final.bmake ];
# dylib fixups on darwin are no longer necessary
postInstall = "";
# doesn't work on darwin due to disallowed nested sandboxes
doInstallCheck = prevAttrs.doInstallCheck && !(final.stdenv.hostPlatform.isDarwin);
doCheck = prevAttrs.doCheck && !(final.stdenv.hostPlatform.isDarwin);
}
);
};
in
{
@@ -284,15 +252,6 @@
# Binary package for various platforms.
build = forAllSystems (system: self.packages.${system}.nix);
# Ensure support for lowdown < 3.0 doesn't regress for NixOS 25.11
build-lowdown_2_0.aarch64-linux = lib.genAttrs [ "aarch64-linux" ] (
system:
self.packages.${system}.nix.override {
lowdown = nixpkgsFor.${system}.native.lowdown;
lowdown-unsandboxed = nixpkgsFor.${system}.native.lowdown-unsandboxed;
}
);
devShell = forAllSystems (system: {
default = self.devShells.${system}.default;
clang = self.devShells.${system}.native-clangStdenvPackages;
@@ -439,16 +398,15 @@
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" { inherit 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" { nixVersions.latest = nix; }).attrpathsSuperset {
evalSystem = system;
})
];
}
);
};
@@ -521,7 +479,10 @@
}
// (
lib.optionalAttrs (builtins.elem system linux64BitSystems) {
nix-static = nixpkgsFor.${system}.static.nix;
# python doesn't work in static builds as of 2025-06-27
nix-static = nixpkgsFor.${system}.static.nix.overrideAttrs (_: {
doCheck = false;
});
dockerImage =
let
pkgs = nixpkgsFor.${system}.native;
+4
View File
@@ -41,6 +41,10 @@ test-unit *OPTIONS: (test "--suite" "check")
# Run integration tests only
test-integration *OPTIONS: install (test "--suite" "installcheck")
# Run functional2 tests using pytest directly, allowing for additional arguments to be passed to pytest e.g. for more granular test selection
test-functional2 *OPTIONS:
cd tests && python -m pytest -v {{ OPTIONS }} functional2
alias clang-tidy := lint
# Lint with `clang-tidy`
+13 -34
View File
@@ -84,7 +84,13 @@ static int main_build_remote(AsyncIoRoot & aio, std::string programName, Strings
initPlugins();
auto store = aio.blockOn(openStore());
// 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));
/* It would be more appropriate to use $XDG_RUNTIME_DIR, since
that gets cleared on reboot, but it wouldn't work on macOS. */
@@ -324,7 +330,7 @@ connected:
//
// 2. Changing the `inputSrcs` set changes the associated
// output ids, which break CA derivations
if (!drv.inputDrvs.map.empty())
if (!drv.inputDrvs.empty())
drv.inputSrcs = store->parseStorePathSet(inputs);
optResult = aio.blockOn(sshStore->buildDerivation(*drvPath, (const BasicDerivation &) drv));
auto & result = *optResult;
@@ -336,7 +342,7 @@ connected:
));
auto res = aio.blockOn(sshStore->buildPathsWithResults({
DerivedPath::Built {
.drvPath = makeConstantStorePathRef(*drvPath),
.drvPath = makeConstantStorePath(*drvPath),
.outputs = OutputsSpec::All {},
}
}));
@@ -346,31 +352,11 @@ connected:
}
auto outputHashes = aio.blockOn(staticOutputHashes(*store, drv));
std::set<Realisation> missingRealisations;
StorePathSet missingPaths;
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations) && !drv.type().hasKnownOutputPaths()) {
for (auto & outputName : wantedOutputs) {
auto thisOutputHash = outputHashes.at(outputName);
auto thisOutputId = DrvOutput{ thisOutputHash, outputName };
if (!aio.blockOn(store->queryRealisation(thisOutputId))) {
debug("missing output %s", outputName);
assert(optResult);
auto & result = *optResult;
auto i = result.builtOutputs.find(outputName);
assert(i != result.builtOutputs.end());
auto & newRealisation = i->second;
missingRealisations.insert(newRealisation);
missingPaths.insert(newRealisation.outPath);
}
}
} else {
auto outputPaths = drv.outputsAndOptPaths(*store);
for (auto & [outputName, hopefullyOutputPath] : outputPaths) {
assert(hopefullyOutputPath.second);
if (!aio.blockOn(store->isValidPath(*hopefullyOutputPath.second)))
missingPaths.insert(*hopefullyOutputPath.second);
}
auto outputPaths = drv.outputsAndPaths(*store);
for (auto & [outputName, outputPath] : outputPaths) {
if (!aio.blockOn(store->isValidPath(outputPath.second)))
missingPaths.insert(outputPath.second);
}
if (!missingPaths.empty()) {
@@ -382,13 +368,6 @@ connected:
copyPaths(*sshStore, *store, missingPaths, NoRepair, NoCheckSigs, NoSubstitute)
);
}
// XXX: Should be done as part of `copyPaths`
for (auto & realisation : missingRealisations) {
// Should hold, because if the feature isn't enabled the set
// of missing realisations should be empty
experimentalFeatureSettings.require(Xp::CaDerivations);
aio.blockOn(store->registerDrvOutput(realisation));
}
return 0;
}
+18 -38
View File
@@ -187,8 +187,7 @@ 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 buildTopTmpDir(createTempSubdir(tmpDir, "build-top"));
AutoDelete tmpDir(createTempDir("", myName));
if (outLink.empty())
outLink = (Path) tmpDir + "/result";
@@ -356,7 +355,7 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
auto bashDrv = drv->requireDrvPath(*state);
pathsToBuild.push_back(DerivedPath::Built {
.drvPath = makeConstantStorePathRef(bashDrv),
.drvPath = makeConstantStorePath(bashDrv),
.outputs = OutputsSpec::Names {"out"},
});
pathsToCopy.insert(bashDrv);
@@ -369,22 +368,16 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
}
}
std::function<void(ref<SingleDerivedPath>, const DerivedPathMap<StringSet>::ChildNode &)> accumDerivedPath;
accumDerivedPath = [&](ref<SingleDerivedPath> inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
if (!inputNode.value.empty())
auto accumDerivedPath = [&](SingleDerivedPath::Opaque inputDrv, const StringSet & inputNode) {
if (!inputNode.empty())
pathsToBuild.push_back(DerivedPath::Built {
.drvPath = inputDrv,
.outputs = OutputsSpec::Names { inputNode.value },
.outputs = OutputsSpec::Names { inputNode },
});
for (const auto & [outputName, childNode] : inputNode.childMap)
accumDerivedPath(
make_ref<SingleDerivedPath>(SingleDerivedPath::Built { inputDrv, outputName }),
childNode);
};
// Build or fetch all dependencies of the derivation.
for (const auto & [inputDrv0, inputNode] : drv.inputDrvs.map) {
for (const auto & [inputDrv0, inputNode] : drv.inputDrvs) {
// To get around lambda capturing restrictions in the
// standard.
const auto & inputDrv = inputDrv0;
@@ -393,7 +386,7 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
return !std::regex_search(store->printStorePath(inputDrv), regex::parse(exclude));
}))
{
accumDerivedPath(makeConstantStorePathRef(inputDrv), inputNode);
accumDerivedPath(makeConstantStorePath(inputDrv), inputNode);
pathsToCopy.insert(inputDrv);
}
}
@@ -408,14 +401,8 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
if (shellDrv) {
auto shellDrvOutputs =
aio.blockOn(store->queryPartialDerivationOutputMap(shellDrv.value(), &*evalStore));
shell = store->printStorePath(shellDrvOutputs.at("out").value()) + "/bin/bash";
}
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
auto resolvedDrv = aio.blockOn(drv.tryResolve(*store));
assert(resolvedDrv && "Successfully resolved the derivation");
drv = *resolvedDrv;
aio.blockOn(store->queryDerivationOutputMap(shellDrv.value(), &*evalStore));
shell = store->printStorePath(shellDrvOutputs.at("out")) + "/bin/bash";
}
// Set the environment.
@@ -432,8 +419,7 @@ 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(buildTopTmpDir);
env["NIX_BUILD_TOP"] = env["TMPDIR"] = env["TEMPDIR"] = env["TMP"] = env["TEMP"] = getEnvNonEmpty("TMPDIR").value_or("/tmp");
env["NIX_STORE"] = store->config().storeDir;
env["NIX_BUILD_CORES"] = std::to_string(settings.buildCores);
@@ -457,20 +443,16 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
if (env.count("__json")) {
StorePathSet inputs;
std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accumInputClosure;
accumInputClosure = [&](const StorePath & inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
auto accumInputClosure = [&](const StorePath & inputDrv, const StringSet & inputNode) {
auto outputs =
aio.blockOn(store->queryPartialDerivationOutputMap(inputDrv, &*evalStore));
for (auto & i : inputNode.value) {
aio.blockOn(store->queryDerivationOutputMap(inputDrv, &*evalStore));
for (auto & i : inputNode) {
auto o = outputs.at(i);
aio.blockOn(store->computeFSClosure(*o, inputs));
aio.blockOn(store->computeFSClosure(o, inputs));
}
for (const auto & [outputName, childNode] : inputNode.childMap)
accumInputClosure(*outputs.at(outputName), childNode);
};
for (const auto & [inputDrv, inputNode] : drv.inputDrvs.map)
for (const auto & [inputDrv, inputNode] : drv.inputDrvs)
accumInputClosure(inputDrv, inputNode);
ParsedDerivation parsedDrv(drvInfo.requireDrvPath(*state), drv);
@@ -583,7 +565,7 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
throw Error("derivation '%s' lacks an 'outputName' attribute", store->printStorePath(drvPath));
pathsToBuild.push_back(DerivedPath::Built{
.drvPath = makeConstantStorePathRef(drvPath),
.drvPath = makeConstantStorePath(drvPath),
.outputs = OutputsSpec::Names{outputName},
});
pathsToBuildOrdered.push_back({drvPath, {outputName}});
@@ -609,11 +591,9 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
drvPrefix += fmt("-%d", counter + 1);
auto builtOutputs =
aio.blockOn(store->queryPartialDerivationOutputMap(drvPath, &*evalStore));
aio.blockOn(store->queryDerivationOutputMap(drvPath, &*evalStore));
auto maybeOutputPath = builtOutputs.at(outputName);
assert(maybeOutputPath);
auto outputPath = *maybeOutputPath;
auto outputPath = builtOutputs.at(outputName);
if (auto store2 = store.try_cast_shared<LocalFSStore>()) {
std::string symlink = drvPrefix;
+2 -2
View File
@@ -495,7 +495,7 @@ static void printMissing(EvalState & state, DrvInfos & elems)
for (auto & i : elems)
if (auto drvPath = i.queryDrvPath(state))
targets.emplace_back(DerivedPath::Built{
.drvPath = makeConstantStorePathRef(*drvPath),
.drvPath = makeConstantStorePath(*drvPath),
.outputs = OutputsSpec::All { },
});
else
@@ -792,7 +792,7 @@ static void opSet(Globals & globals, Strings opFlags, Strings opArgs)
std::vector<DerivedPath> paths {
drvPath
? (DerivedPath) (DerivedPath::Built {
.drvPath = makeConstantStorePathRef(*drvPath),
.drvPath = makeConstantStorePath(*drvPath),
.outputs = OutputsSpec::All { },
})
: (DerivedPath) (DerivedPath::Opaque {
+8 -2
View File
@@ -22,7 +22,7 @@ static Path gcRoot;
static int rootNr = 0;
enum OutputKind { okPlain, okXML, okJSON };
enum OutputKind { okPlain, okRaw, okXML, okJSON };
void processExpr(EvalState & state, const Strings & attrPaths,
bool parseOnly, bool strict, Bindings & autoArgs,
@@ -48,7 +48,11 @@ void processExpr(EvalState & state, const Strings & attrPaths,
vRes = v;
else
state.autoCallFunction(autoArgs, v, vRes, noPos);
if (output == okXML)
if (output == okRaw)
std::cout << *state.coerceToString(noPos, vRes, context, "while generating the nix-instantiate output", StringCoercionMode::Strict);
// We intentionally don't output a newline here. The default PS1 for Bash in NixOS starts with a newline
// and other interactive shells like Zsh are smart enough to print a missing newline before the prompt.
else if (output == okXML)
printValueAsXML(state, strict, location, vRes, std::cout, context, noPos);
else if (output == okJSON) {
printValueAsJSON(state, strict, vRes, noPos, std::cout, context);
@@ -130,6 +134,8 @@ static int main_nix_instantiate(AsyncIoRoot & aio, std::string programName, Stri
gcRoot = getArg(*arg, arg, end);
else if (*arg == "--indirect")
;
else if (*arg == "--raw")
outputKind = okRaw;
else if (*arg == "--xml")
outputKind = okXML;
else if (*arg == "--json")
+131 -83
View File
@@ -33,25 +33,23 @@ namespace nix {
using std::cin;
using std::cout;
typedef void (* Operation) (AsyncIoRoot & aio, Strings opFlags, Strings opArgs);
typedef void (*Operation)(
std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs
);
static Path gcRoot;
static int rootNr = 0;
static bool noOutput = false;
static std::shared_ptr<Store> store;
ref<LocalStore> ensureLocalStore()
ref<LocalStore> ensureLocalStore(std::shared_ptr<Store> store)
{
auto store2 = std::dynamic_pointer_cast<LocalStore>(store);
if (!store2) throw Error("you don't have sufficient rights to use this command");
return ref<LocalStore>::unsafeFromPtr(store2);
}
static kj::Promise<Result<StorePath>> useDeriver(const StorePath & path)
static kj::Promise<Result<StorePath>>
useDeriver(std::shared_ptr<Store> store, const StorePath & path)
try {
if (path.isDerivation()) co_return path;
auto info = TRY_AWAIT(store->queryPathInfo(path));
@@ -65,7 +63,8 @@ try {
/* Realise the given path. For a derivation that means build it; for
other paths it means ensure their validity. */
static kj::Promise<Result<PathSet>> realisePath(StorePathWithOutputs path, bool build = true)
static kj::Promise<Result<PathSet>>
realisePath(std::shared_ptr<Store> store, StorePathWithOutputs path, bool build = true)
try {
auto store2 = std::dynamic_pointer_cast<LocalFSStore>(store);
@@ -125,7 +124,8 @@ try {
/* Realise the given paths. */
static void opRealise(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void
opRealise(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
bool dryRun = false;
BuildMode buildMode = bmNormal;
@@ -170,7 +170,7 @@ static void opRealise(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
if (!ignoreUnknown)
for (auto & i : paths) {
auto paths2 = aio.blockOn(realisePath(i, false));
auto paths2 = aio.blockOn(realisePath(store, i, false));
if (!noOutput)
for (auto & j : paths2)
cout << fmt("%1%\n", j);
@@ -179,7 +179,7 @@ static void opRealise(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
/* Add files to the Nix store and print the resulting paths. */
static void opAdd(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void opAdd(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
if (!opFlags.empty()) throw UsageError("unknown flag");
@@ -196,7 +196,8 @@ static void opAdd(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
/* Preload the output of a fixed-output derivation into the Nix
store. */
static void opAddFixed(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void
opAddFixed(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
auto method = FileIngestionMethod::Flat;
@@ -222,7 +223,8 @@ static void opAddFixed(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
/* Hack to support caching in `nix-prefetch-url'. */
static void opPrintFixedPath(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void
opPrintFixedPath(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
auto method = FileIngestionMethod::Flat;
@@ -245,19 +247,20 @@ static void opPrintFixedPath(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
})));
}
static kj::Promise<Result<StorePathSet>> maybeUseOutputs(const StorePath & storePath, bool useOutput, bool forceRealise)
static kj::Promise<Result<StorePathSet>> maybeUseOutputs(
std::shared_ptr<Store> store, const StorePath & storePath, bool useOutput, bool forceRealise
)
try {
if (forceRealise) TRY_AWAIT(realisePath({storePath}));
if (forceRealise) {
TRY_AWAIT(realisePath(store, {storePath}));
}
if (useOutput && storePath.isDerivation()) {
auto drv = TRY_AWAIT(store->derivationFromPath(storePath));
StorePathSet outputs;
if (forceRealise)
co_return TRY_AWAIT(store->queryDerivationOutputs(storePath));
for (auto & i : drv.outputsAndOptPaths(*store)) {
if (!i.second.second)
throw UsageError("Cannot use output path of floating content-addressed derivation until we know what it is (e.g. by building it)");
outputs.insert(*i.second.second);
for (auto & i : drv.outputsAndPaths(*store)) {
outputs.insert(i.second.second);
}
co_return outputs;
}
@@ -270,8 +273,14 @@ try {
/* Some code to print a tree representation of a derivation dependency
graph. Topological sorting is used to keep the tree relatively
flat. */
static void printTree(AsyncIoRoot & aio, const StorePath & path,
const std::string & firstPad, const std::string & tailPad, StorePathSet & done)
static void printTree(
std::shared_ptr<Store> store,
AsyncIoRoot & aio,
const StorePath & path,
const std::string & firstPad,
const std::string & tailPad,
StorePathSet & done
)
{
if (!done.insert(path).second) {
cout << fmt("%s%s [...]\n", firstPad, store->printStorePath(path));
@@ -291,16 +300,21 @@ static void printTree(AsyncIoRoot & aio, const StorePath & path,
for (const auto &[n, i] : enumerate(sorted)) {
bool last = n + 1 == sorted.size();
printTree(aio, i,
printTree(
store,
aio,
i,
tailPad + (last ? treeLast : treeConn),
tailPad + (last ? treeNull : treeLine),
done);
done
);
}
}
/* Perform various sorts of queries. */
static void opQuery(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void
opQuery(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
enum QueryType
{ qOutputs, qRequisites, qReferences, qReferrers
@@ -351,7 +365,9 @@ static void opQuery(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
case qOutputs: {
for (auto & i : opArgs) {
auto outputs = aio.blockOn(maybeUseOutputs(store->followLinksToStorePath(i), true, forceRealise));
auto outputs = aio.blockOn(
maybeUseOutputs(store, store->followLinksToStorePath(i), true, forceRealise)
);
for (auto & outputPath : outputs)
cout << fmt("%1%\n", store->printStorePath(outputPath));
}
@@ -364,7 +380,9 @@ static void opQuery(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
case qReferrersClosure: {
StorePathSet paths;
for (auto & i : opArgs) {
auto ps = aio.blockOn(maybeUseOutputs(store->followLinksToStorePath(i), useOutput, forceRealise));
auto ps = aio.blockOn(maybeUseOutputs(
store, store->followLinksToStorePath(i), useOutput, forceRealise
));
for (auto & j : ps) {
if (query == qRequisites) {
aio.blockOn(store->computeFSClosure(j, paths, false, includeOutputs));
@@ -415,7 +433,7 @@ static void opQuery(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
case qBinding:
for (auto & i : opArgs) {
auto path = aio.blockOn(useDeriver(store->followLinksToStorePath(i)));
auto path = aio.blockOn(useDeriver(store, store->followLinksToStorePath(i)));
Derivation drv = aio.blockOn(store->derivationFromPath(path));
StringPairs::iterator j = drv.env.find(bindingName);
if (j == drv.env.end())
@@ -428,7 +446,10 @@ static void opQuery(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
case qHash:
case qSize:
for (auto & i : opArgs) {
for (auto & j : aio.blockOn(maybeUseOutputs(store->followLinksToStorePath(i), useOutput, forceRealise))) {
for (auto & j : aio.blockOn(maybeUseOutputs(
store, store->followLinksToStorePath(i), useOutput, forceRealise
)))
{
auto info = aio.blockOn(store->queryPathInfo(j));
if (query == qHash) {
assert(info->narHash.type == HashType::SHA256);
@@ -442,15 +463,19 @@ static void opQuery(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
case qTree: {
StorePathSet done;
for (auto & i : opArgs)
printTree(aio, store->followLinksToStorePath(i), "", "", done);
printTree(store, aio, store->followLinksToStorePath(i), "", "", done);
break;
}
case qGraph: {
StorePathSet roots;
for (auto & i : opArgs)
for (auto & j : aio.blockOn(maybeUseOutputs(store->followLinksToStorePath(i), useOutput, forceRealise)))
for (auto & j : aio.blockOn(maybeUseOutputs(
store, store->followLinksToStorePath(i), useOutput, forceRealise
)))
{
roots.insert(j);
}
aio.blockOn(printDotGraph(ref<Store>::unsafeFromPtr(store), std::move(roots)));
break;
}
@@ -458,8 +483,12 @@ static void opQuery(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
case qGraphML: {
StorePathSet roots;
for (auto & i : opArgs)
for (auto & j : aio.blockOn(maybeUseOutputs(store->followLinksToStorePath(i), useOutput, forceRealise)))
for (auto & j : aio.blockOn(maybeUseOutputs(
store, store->followLinksToStorePath(i), useOutput, forceRealise
)))
{
roots.insert(j);
}
aio.blockOn(printGraphML(ref<Store>::unsafeFromPtr(store), std::move(roots)));
break;
}
@@ -473,8 +502,12 @@ static void opQuery(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
case qRoots: {
StorePathSet args;
for (auto & i : opArgs)
for (auto & p : aio.blockOn(maybeUseOutputs(store->followLinksToStorePath(i), useOutput, forceRealise)))
for (auto & p : aio.blockOn(maybeUseOutputs(
store, store->followLinksToStorePath(i), useOutput, forceRealise
)))
{
args.insert(p);
}
StorePathSet referrers;
aio.blockOn(store->computeFSClosure(
@@ -494,8 +527,8 @@ static void opQuery(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
}
}
static void opPrintEnv(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void
opPrintEnv(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
if (!opFlags.empty()) throw UsageError("unknown flag");
if (opArgs.size() != 1) throw UsageError("'--print-env' requires one derivation store path");
@@ -520,8 +553,8 @@ static void opPrintEnv(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
cout << "'\n";
}
static void opReadLog(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void
opReadLog(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
if (!opFlags.empty()) throw UsageError("unknown flag");
@@ -538,8 +571,8 @@ static void opReadLog(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
}
}
static void opDumpDB(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void
opDumpDB(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
if (!opFlags.empty()) throw UsageError("unknown flag");
if (!opArgs.empty()) {
@@ -554,8 +587,13 @@ static void opDumpDB(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
}
}
static void registerValidity(AsyncIoRoot & aio, bool reregister, bool hashGiven, bool canonicalise)
static void registerValidity(
std::shared_ptr<Store> store,
AsyncIoRoot & aio,
bool reregister,
bool hashGiven,
bool canonicalise
)
{
ValidPathInfos infos;
@@ -578,20 +616,20 @@ static void registerValidity(AsyncIoRoot & aio, bool reregister, bool hashGiven,
}
}
aio.blockOn(ensureLocalStore()->registerValidPaths(infos));
aio.blockOn(ensureLocalStore(store)->registerValidPaths(infos));
}
static void opLoadDB(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void
opLoadDB(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
if (!opFlags.empty()) throw UsageError("unknown flag");
if (!opArgs.empty())
throw UsageError("no arguments expected");
registerValidity(aio, true, true, false);
registerValidity(store, aio, true, true, false);
}
static void opRegisterValidity(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void
opRegisterValidity(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
bool reregister = false; // !!! maybe this should be the default
bool hashGiven = false;
@@ -603,11 +641,11 @@ static void opRegisterValidity(AsyncIoRoot & aio, Strings opFlags, Strings opArg
if (!opArgs.empty()) throw UsageError("no arguments expected");
registerValidity(aio, reregister, hashGiven, true);
registerValidity(store, aio, reregister, hashGiven, true);
}
static void opCheckValidity(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void
opCheckValidity(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
bool printInvalid = false;
@@ -626,8 +664,7 @@ static void opCheckValidity(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
}
}
static void opGC(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void opGC(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
bool printRoots = false;
GCOptions options;
@@ -673,7 +710,8 @@ static void opGC(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
/* Remove paths from the Nix store if possible (i.e., if they do not
have any remaining referrers and are not reachable from any GC
roots). */
static void opDelete(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void
opDelete(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
GCOptions options;
options.action = GCOptions::gcDeleteSpecific;
@@ -703,7 +741,7 @@ static void opDelete(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
/* Dump a path as a Nix archive. The archive is written to stdout */
static void opDump(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void opDump(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
if (!opFlags.empty()) throw UsageError("unknown flag");
if (opArgs.size() != 1) throw UsageError("only one argument allowed");
@@ -716,7 +754,8 @@ static void opDump(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
/* Restore a value from a Nix archive. The archive is read from stdin. */
static void opRestore(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void
opRestore(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
if (!opFlags.empty()) throw UsageError("unknown flag");
if (opArgs.size() != 1) throw UsageError("only one argument allowed");
@@ -725,8 +764,8 @@ static void opRestore(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
restorePath(*opArgs.begin(), source);
}
static void opExport(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void
opExport(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
for (auto & i : opFlags)
throw UsageError("unknown flag '%1%'", i);
@@ -741,8 +780,8 @@ static void opExport(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
sink.flush();
}
static void opImport(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void
opImport(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
for (auto & i : opFlags)
throw UsageError("unknown flag '%1%'", i);
@@ -758,7 +797,7 @@ static void opImport(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
/* Initialise the Nix databases. */
static void opInit(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void opInit(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
if (!opFlags.empty()) throw UsageError("unknown flag");
if (!opArgs.empty())
@@ -769,7 +808,8 @@ static void opInit(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
/* Verify the consistency of the Nix environment. */
static void opVerify(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void
opVerify(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
if (!opArgs.empty())
throw UsageError("no arguments expected");
@@ -790,7 +830,8 @@ static void opVerify(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
/* Verify whether the contents of the given store path have not changed. */
static void opVerifyPath(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void
opVerifyPath(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
if (!opFlags.empty())
throw UsageError("no flags expected");
@@ -802,7 +843,7 @@ static void opVerifyPath(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
printMsg(lvlTalkative, "checking path '%s'...", store->printStorePath(path));
auto info = aio.blockOn(store->queryPathInfo(path));
HashSink sink(info->narHash.type);
aio.blockOn(store->narFromPath(path))->drainInto(sink);
aio.blockOn(aio.blockOn(store->narFromPath(path))->drainInto(sink));
auto current = sink.finish();
if (current.first != info->narHash) {
printError("path '%s' was modified! expected hash '%s', got '%s'",
@@ -819,7 +860,8 @@ static void opVerifyPath(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
/* Repair the contents of the given path by redownloading it using a
substituter (if available). */
static void opRepairPath(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void
opRepairPath(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
if (!opFlags.empty())
throw UsageError("no flags expected");
@@ -830,7 +872,8 @@ static void opRepairPath(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
/* Optimise the disk space usage of the Nix store by hard-linking
files with the same contents. */
static void opOptimise(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void
opOptimise(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
if (!opArgs.empty() || !opFlags.empty())
throw UsageError("no arguments expected");
@@ -839,7 +882,8 @@ static void opOptimise(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
}
/* Serve the nix store in a way usable by a restricted ssh user. */
static void opServe(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void
opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
bool writeAllowed = false;
for (auto & i : opFlags)
@@ -860,9 +904,11 @@ static void opServe(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
ServeProto::ReadConn rconn {
.from = in,
.store = *store,
.version = clientVersion,
};
ServeProto::WriteConn wconn {
.store = *store,
.version = clientVersion,
};
@@ -909,7 +955,7 @@ static void opServe(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
case ServeProto::Command::QueryValidPaths: {
bool lock = readInt(in);
bool substitute = readInt(in);
auto paths = ServeProto::Serialise<StorePathSet>::read(*store, rconn);
auto paths = ServeProto::Serialise<StorePathSet>::read(rconn);
if (lock && writeAllowed)
for (auto & path : paths)
aio.blockOn(store->addTempRoot(path));
@@ -919,18 +965,18 @@ static void opServe(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
}
auto valid = aio.blockOn(store->queryValidPaths(paths));
out << ServeProto::write(*store, wconn, valid);
out << ServeProto::write(wconn, valid);
break;
}
case ServeProto::Command::QueryPathInfos: {
auto paths = ServeProto::Serialise<StorePathSet>::read(*store, rconn);
auto paths = ServeProto::Serialise<StorePathSet>::read(rconn);
// !!! Maybe we want a queryPathInfos?
for (auto & i : paths) {
try {
auto info = aio.blockOn(store->queryPathInfo(i));
out << store->printStorePath(info->path);
out << ServeProto::write(*store, wconn, static_cast<const UnkeyedValidPathInfo &>(*info));
out << ServeProto::write(wconn, static_cast<const UnkeyedValidPathInfo &>(*info));
} catch (InvalidPath &) {
}
}
@@ -939,8 +985,8 @@ static void opServe(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
}
case ServeProto::Command::DumpStorePath:
aio.blockOn(store->narFromPath(store->parseStorePath(readString(in))))
->drainInto(out);
aio.blockOn(aio.blockOn(store->narFromPath(store->parseStorePath(readString(in))))
->drainInto(out));
break;
case ServeProto::Command::ImportPaths: {
@@ -953,7 +999,7 @@ static void opServe(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
case ServeProto::Command::ExportPaths: {
readInt(in); // obsolete
aio.blockOn(store->exportPaths(
ServeProto::Serialise<StorePathSet>::read(*store, rconn), out
ServeProto::Serialise<StorePathSet>::read(rconn), out
));
break;
}
@@ -992,7 +1038,7 @@ static void opServe(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
MonitorFdHup monitor(in.fd);
auto status = aio.blockOn(store->buildDerivation(drvPath, drv));
out << ServeProto::write(*store, wconn, status);
out << ServeProto::write(wconn, status);
break;
}
@@ -1000,12 +1046,12 @@ static void opServe(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
bool includeOutputs = readInt(in);
StorePathSet closure;
aio.blockOn(store->computeFSClosure(
ServeProto::Serialise<StorePathSet>::read(*store, rconn),
ServeProto::Serialise<StorePathSet>::read(rconn),
closure,
false,
includeOutputs
));
out << ServeProto::write(*store, wconn, closure);
out << ServeProto::write(wconn, closure);
break;
}
@@ -1020,7 +1066,7 @@ static void opServe(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
};
if (deriver != "")
info.deriver = store->parseStorePath(deriver);
info.references = ServeProto::Serialise<StorePathSet>::read(*store, rconn);
info.references = ServeProto::Serialise<StorePathSet>::read(rconn);
in >> info.registrationTime >> info.narSize >> info.ultimate;
info.sigs = readStrings<StringSet>(in);
info.ca = ContentAddress::parseOpt(readString(in));
@@ -1049,8 +1095,9 @@ static void opServe(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
}
}
static void opGenerateBinaryCacheKey(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void opGenerateBinaryCacheKey(
std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs
)
{
for (auto & i : opFlags)
throw UsageError("unknown flag '%1%'", i);
@@ -1068,8 +1115,8 @@ static void opGenerateBinaryCacheKey(AsyncIoRoot & aio, Strings opFlags, Strings
writeFile(secretKeyFile, secretKey.to_string());
}
static void opVersion(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
static void
opVersion(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
{
printVersion("nix-store");
}
@@ -1214,10 +1261,11 @@ static int main_nix_store(AsyncIoRoot & aio, std::string programName, Strings ar
if (showHelp) showManPage("nix-store" + opName);
if (!op) throw UsageError("no operation specified");
std::shared_ptr<Store> store;
if (op != opDump && op != opRestore) /* !!! hack */
store = aio.blockOn(openStore());
op(aio, std::move(opFlags), std::move(opArgs));
op(store, aio, std::move(opFlags), std::move(opArgs));
return 0;
}
+5 -85
View File
@@ -13,9 +13,9 @@ namespace nix {
bool MY_TYPE ::operator COMPARATOR (const MY_TYPE & other) const \
{ \
const MY_TYPE* me = this; \
auto fields1 = std::tie(*me->drvPath, me->FIELD); \
auto fields1 = std::tie(me->drvPath, me->FIELD); \
me = &other; \
auto fields2 = std::tie(*me->drvPath, me->FIELD); \
auto fields2 = std::tie(me->drvPath, me->FIELD); \
return fields1 COMPARATOR fields2; \
}
#define CMP(CHILD_TYPE, MY_TYPE, FIELD) \
@@ -23,10 +23,6 @@ namespace nix {
CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, !=) \
CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, <)
#define FIELD_TYPE std::pair<std::string, StorePath>
CMP(SingleBuiltPath, SingleBuiltPathBuilt, output)
#undef FIELD_TYPE
#define FIELD_TYPE std::map<std::string, StorePath>
CMP(SingleBuiltPath, BuiltPathBuilt, outputs)
#undef FIELD_TYPE
@@ -34,16 +30,6 @@ CMP(SingleBuiltPath, BuiltPathBuilt, outputs)
#undef CMP
#undef CMP_ONE
StorePath SingleBuiltPath::outPath() const
{
return std::visit(
overloaded{
[](const SingleBuiltPath::Opaque & p) { return p.path; },
[](const SingleBuiltPath::Built & b) { return b.output.second; },
}, raw()
);
}
StorePathSet BuiltPath::outPaths() const
{
return std::visit(
@@ -59,32 +45,10 @@ StorePathSet BuiltPath::outPaths() const
);
}
SingleDerivedPath::Built SingleBuiltPath::Built::discardOutputPath() const
{
return SingleDerivedPath::Built {
.drvPath = make_ref<SingleDerivedPath>(drvPath->discardOutputPath()),
.output = output.first,
};
}
SingleDerivedPath SingleBuiltPath::discardOutputPath() const
{
return std::visit(
overloaded{
[](const SingleBuiltPath::Opaque & p) -> SingleDerivedPath {
return p;
},
[](const SingleBuiltPath::Built & b) -> SingleDerivedPath {
return b.discardOutputPath();
},
}, raw()
);
}
kj::Promise<Result<JSON>> BuiltPath::Built::toJSON(const Store & store) const
try {
JSON res;
res["drvPath"] = TRY_AWAIT(drvPath->toJSON(store));
res["drvPath"] = TRY_AWAIT(drvPath.toJSON(store));
for (const auto & [outputName, outputPath] : outputs) {
res["outputs"][outputName] = store.printStorePath(outputPath);
}
@@ -93,36 +57,6 @@ try {
co_return result::current_exception();
}
kj::Promise<Result<JSON>> SingleBuiltPath::Built::toJSON(const Store & store) const
try {
JSON res;
res["drvPath"] = TRY_AWAIT(drvPath->toJSON(store));
auto & [outputName, outputPath] = output;
res["output"] = outputName;
res["outputPath"] = store.printStorePath(outputPath);
co_return res;
} catch (...) {
co_return result::current_exception();
}
kj::Promise<Result<JSON>> SingleBuiltPath::toJSON(const Store & store) const
try {
co_return TRY_AWAIT(std::visit([&](const auto & buildable) {
return buildable.toJSON(store);
}, raw()));
} catch (...) {
co_return result::current_exception();
}
kj::Promise<Result<JSON>> BuiltPath::toJSON(const Store & store) const
try {
co_return TRY_AWAIT(std::visit([&](const auto & buildable) {
return buildable.toJSON(store);
}, raw()));
} catch (...) {
co_return result::current_exception();
}
kj::Promise<Result<RealisedPath::Set>> BuiltPath::toRealisedPaths(Store & store) const
try {
@@ -141,24 +75,10 @@ try {
[&](const BuiltPath::Built & p) -> kj::Promise<Result<void>> {
try {
auto drvHashes = TRY_AWAIT(
staticOutputHashes(store, TRY_AWAIT(store.readDerivation(p.drvPath->outPath())))
staticOutputHashes(store, TRY_AWAIT(store.readDerivation(p.drvPath.path)))
);
for (auto& [outputName, outputPath] : p.outputs) {
if (experimentalFeatureSettings.isEnabled(
Xp::CaDerivations)) {
auto drvOutput = get(drvHashes, outputName);
if (!drvOutput)
throw Error(
"the derivation '%s' has unrealised output '%s' (derived-path.cc/toRealisedPaths)",
store.printStorePath(p.drvPath->outPath()), outputName);
auto thisRealisation = TRY_AWAIT(store.queryRealisation(
DrvOutput{*drvOutput, outputName}));
assert(thisRealisation); // Weve built it, so we must
// have the realisation
res.insert(*thisRealisation);
} else {
res.insert(outputPath);
}
res.insert(outputPath);
}
co_return result::success();
} catch (...) {
+1 -51
View File
@@ -7,63 +7,15 @@
namespace nix {
struct SingleBuiltPath;
struct SingleBuiltPathBuilt {
ref<SingleBuiltPath> drvPath;
std::pair<std::string, StorePath> output;
SingleDerivedPathBuilt discardOutputPath() const;
std::string to_string(const Store & store) const;
static SingleBuiltPathBuilt parse(const Store & store, std::string_view, std::string_view);
kj::Promise<Result<JSON>> toJSON(const Store & store) const;
DECLARE_CMP(SingleBuiltPathBuilt);
};
namespace built_path::detail {
using SingleBuiltPathRaw = std::variant<
DerivedPathOpaque,
SingleBuiltPathBuilt
>;
}
struct SingleBuiltPath : built_path::detail::SingleBuiltPathRaw {
using Raw = built_path::detail::SingleBuiltPathRaw;
using Raw::Raw;
using Opaque = DerivedPathOpaque;
using Built = SingleBuiltPathBuilt;
inline const Raw & raw() const {
return static_cast<const Raw &>(*this);
}
StorePath outPath() const;
SingleDerivedPath discardOutputPath() const;
static SingleBuiltPath parse(const Store & store, std::string_view);
kj::Promise<Result<JSON>> toJSON(const Store & store) const;
};
static inline ref<SingleBuiltPath> staticDrv(StorePath drvPath)
{
return make_ref<SingleBuiltPath>(SingleBuiltPath::Opaque { drvPath });
}
/**
* A built derived path with hints in the form of optional concrete output paths.
*
* See 'BuiltPath' for more an explanation.
*/
struct BuiltPathBuilt {
ref<SingleBuiltPath> drvPath;
DerivedPathOpaque drvPath;
std::map<std::string, StorePath> outputs;
std::string to_string(const Store & store) const;
static BuiltPathBuilt parse(const Store & store, std::string_view, std::string_view);
kj::Promise<Result<JSON>> toJSON(const Store & store) const;
DECLARE_CMP(BuiltPathBuilt);
@@ -93,8 +45,6 @@ struct BuiltPath : built_path::detail::BuiltPathRaw {
StorePathSet outPaths() const;
kj::Promise<Result<RealisedPath::Set>> toRealisedPaths(Store & store) const;
kj::Promise<Result<JSON>> toJSON(const Store & store) const;
};
typedef std::vector<BuiltPath> BuiltPaths;
+24 -18
View File
@@ -9,6 +9,7 @@
#include "lix/libstore/store-api.hh"
#include "lix/libcmd/command.hh"
#include "lix/libutil/async.hh"
#include "lix/libutil/error.hh"
#include "lix/libutil/regex.hh"
#include <regex>
@@ -16,31 +17,36 @@
namespace nix {
static std::regex const identifierRegex = regex::parse("^[A-Za-z_][A-Za-z0-9_'-]*$");
static void warnInvalidNixIdentifier(const std::string & name)
static void checkValidNixIdentifier(const std::string & name)
{
std::smatch match;
if (!std::regex_match(name, match, identifierRegex)) {
warn("This Nix invocation specifies a value for argument '%s' which isn't a valid \
Nix identifier. The project is considering to drop support for this \
or to require quotes around args that aren't valid Nix identifiers. \
If you depend on this behvior, please reach out in \
https://git.lix.systems/lix-project/lix/issues/496 so we can discuss \
your use-case.", name);
throw UsageError(
"This invocation specifies a value for argument '%s' "
"which isn't a valid Nix identifier. "
"The project is dropping support for this so that it's possible to make e.g. "
"'%s' evaluating to '%s' in the future. "
"If you depend on this behavior, please reach out in "
"<https://git.lix.systems/lix-project/lix/issues/496> so we can discuss your use-case.",
name,
"--arg config.allowUnfree true",
"{ config.allowUnfree = true; }"
);
}
}
MixEvalArgs::MixEvalArgs()
{
addFlag({
.longName = "arg",
.description = "Pass the value *expr* as the argument *name* to Nix functions.",
.category = category,
.labels = {"name", "expr"},
.handler = {[&](std::string name, std::string expr) {
warnInvalidNixIdentifier(name);
autoArgs[name] = 'E' + expr;
}}
});
addFlag(
{.longName = "arg",
.description = "Pass the value *expr* as the argument *name* to Nix functions.",
.category = category,
.labels = {"name", "expr"},
.handler = {[&](std::string name, std::string expr) {
checkValidNixIdentifier(name);
autoArgs[name] = 'E' + expr;
}}}
);
addFlag({
.longName = "argstr",
@@ -48,7 +54,7 @@ MixEvalArgs::MixEvalArgs()
.category = category,
.labels = {"name", "string"},
.handler = {[&](std::string name, std::string s) {
warnInvalidNixIdentifier(name);
checkValidNixIdentifier(name);
autoArgs[name] = 'S' + s;
}},
});
+1 -1
View File
@@ -76,7 +76,7 @@ DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths(EvalState & state)
for (auto & [drvPath, outputs] : byDrvPath)
res.push_back({
.path = DerivedPath::Built {
.drvPath = makeConstantStorePathRef(drvPath),
.drvPath = makeConstantStorePath(drvPath),
.outputs = outputs,
},
.info = make_ref<ExtraPathInfoValue>(ExtraPathInfoValue::Value {
+2 -3
View File
@@ -35,7 +35,7 @@ InstallableDerivedPath InstallableDerivedPath::parse(
// Remove this prior to stabilizing the new CLI.
if (storePath.isDerivation()) {
auto oldDerivedPath = DerivedPath::Built {
.drvPath = makeConstantStorePathRef(storePath),
.drvPath = makeConstantStorePath(storePath),
.outputs = OutputsSpec::All { },
};
warn(
@@ -48,8 +48,7 @@ InstallableDerivedPath InstallableDerivedPath::parse(
},
// If the user did use ^, we just do exactly what is written.
[&](const ExtendedOutputsSpec::Explicit & outputSpec) -> DerivedPath {
auto drv = make_ref<SingleDerivedPath>(SingleDerivedPath::parse(*store, prefix));
drvRequireExperiment(*drv);
auto drv = DerivedPathOpaque::parse(*store, prefix);
return DerivedPath::Built {
.drvPath = std::move(drv),
.outputs = outputSpec,
+1 -1
View File
@@ -98,7 +98,7 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths(EvalState & state)
return {{
.path = DerivedPath::Built {
.drvPath = makeConstantStorePathRef(std::move(drvPath)),
.drvPath = makeConstantStorePath(std::move(drvPath)),
.outputs = std::visit(overloaded {
[&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec {
std::set<std::string> outputsToInstall;
+3 -33
View File
@@ -524,36 +524,6 @@ ref<Installable> SourceExprCommand::parseInstallable(
return installables.front();
}
static kj::Promise<Result<SingleBuiltPath>> getBuiltPath(ref<Store> evalStore, ref<Store> store, const SingleDerivedPath & b)
try {
auto handlers = overloaded{
[&](const SingleDerivedPath::Opaque & bo) -> kj::Promise<Result<SingleBuiltPath>> {
return {SingleBuiltPath::Opaque { bo.path }};
},
// NOLINTNEXTLINE(cppcoreguidelines-avoid-capturing-lambda-coroutines)
[&](const SingleDerivedPath::Built & bfd) -> kj::Promise<Result<SingleBuiltPath>> {
try {
auto drvPath = TRY_AWAIT(getBuiltPath(evalStore, store, *bfd.drvPath));
// Resolving this instead of `bfd` will yield the same result, but avoid duplicative work.
SingleDerivedPath::Built truncatedBfd {
.drvPath = makeConstantStorePathRef(drvPath.outPath()),
.output = bfd.output,
};
auto outputPath = TRY_AWAIT(resolveDerivedPath(*store, truncatedBfd, &*evalStore));
co_return SingleBuiltPath::Built {
.drvPath = make_ref<SingleBuiltPath>(std::move(drvPath)),
.output = { bfd.output, outputPath },
};
} catch (...) {
co_return result::current_exception();
}
},
};
co_return TRY_AWAIT(std::visit(handlers, b.raw()));
} catch (...) {
co_return result::current_exception();
}
std::vector<BuiltPathWithResult> Installable::build(
EvalState & state,
ref<Store> evalStore,
@@ -642,7 +612,7 @@ std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> Installable::build
state.aio.blockOn(resolveDerivedPath(*store, bfd, &*evalStore));
res.push_back({aux.installable, {
.path = BuiltPath::Built {
.drvPath = make_ref<SingleBuiltPath>(state.aio.blockOn(getBuiltPath(evalStore, store, *bfd.drvPath))),
.drvPath = bfd.drvPath,
.outputs = outputs,
},
.info = aux.info}});
@@ -674,7 +644,7 @@ std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> Installable::build
outputs.emplace(outputName, realisation.outPath);
res.push_back({aux.installable, {
.path = BuiltPath::Built {
.drvPath = make_ref<SingleBuiltPath>(state.aio.blockOn(getBuiltPath(evalStore, store, *bfd.drvPath))),
.drvPath = bfd.drvPath,
.outputs = outputs,
},
.info = aux.info,
@@ -789,7 +759,7 @@ StorePathSet Installable::toDerivations(
: throw Error("argument '%s' did not evaluate to a derivation", i->what()));
},
[&](const DerivedPath::Built & bfd) {
drvPaths.insert(state.aio.blockOn(resolveDerivedPath(*store, *bfd.drvPath)));
drvPaths.insert(bfd.drvPath.path);
},
}, b.path.raw());
+46 -23
View File
@@ -3,44 +3,65 @@
#include "lix/libutil/finally.hh"
#include "lix/libutil/terminal.hh"
#include <cstdlib>
#include <iterator>
#include <new>
#include <regex>
#include <sys/queue.h>
#include <lowdown.h>
namespace nix {
std::string renderMarkdownToTerminal(std::string_view markdown)
static const std::string DOCROOT = "@docroot@";
static const std::string DOCROOT_URL = "https://docs.lix.systems/manual/lix/stable";
static void processLinks(struct lowdown_node * node)
{
if (node->type == LOWDOWN_LINK) {
struct lowdown_buf *link = &node->rndr_link.link;
if (link && link->size && std::string_view(link->data, link->size).starts_with(DOCROOT)) {
// link starts with @docroot@, replace that and check the path extension too.
static std::regex mdRewrite{"\\.md(#.*)?$"}; // NOLINT(lix-foreign-exceptions)
auto oldLink = std::string_view(link->data, link->size).substr(DOCROOT.size());
std::string newLink = DOCROOT_URL;
std::regex_replace(
std::back_inserter(newLink), oldLink.begin(), oldLink.end(), mdRewrite, ".html$1"
);
if (link->maxsize < newLink.size()) {
// the existing link buffer doesn't have enough space for the new string
char *newData;
if (!(newData = static_cast<char *>(std::realloc(link->data, newLink.size())))) {
throw std::bad_alloc();
}
link->data = newData;
link->maxsize = newLink.size();
}
newLink.copy(link->data, newLink.size());
link->size = newLink.size();
}
} else {
// recurse into children
struct lowdown_node *child;
TAILQ_FOREACH(child, &node->children, entries)
processLinks(child);
}
}
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,
#else
.maxdepth = 20,
.cols = lowdown_cols,
.cols = (size_t) std::max(windowWidth - 5, 60),
.hmargin = 0,
.vmargin = 0,
#endif /* LOWDOWN_SEPARATE_TERM_OPTS */
.feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES,
#ifdef LOWDOWN_CONSOLIDATED_OFLAGS
.oflags = LOWDOWN_NOLINK,
#else
.oflags = LOWDOWN_TERM_NOLINK,
#endif /* LOWDOWN_CONSOLIDATED_OFLAGS */
};
if (!shouldANSI()) {
if (!shouldANSI(fileno)) {
opts.oflags |= LOWDOWN_TERM_NOANSI;
}
@@ -55,6 +76,8 @@ std::string renderMarkdownToTerminal(std::string_view markdown)
throw Error("cannot parse Markdown document");
Finally freeNode([&]() { lowdown_node_free(node); });
processLinks(node);
auto renderer = lowdown_term_new(&opts);
if (!renderer)
throw Error("cannot allocate Markdown renderer");
+2 -1
View File
@@ -1,10 +1,11 @@
#pragma once
///@file
#include "lix/libutil/terminal.hh"
#include "lix/libutil/types.hh"
namespace nix {
std::string renderMarkdownToTerminal(std::string_view markdown);
std::string renderMarkdownToTerminal(std::string_view markdown, StandardOutputStream fileno = StandardOutputStream::Stdout);
}
+26 -17
View File
@@ -3,7 +3,6 @@
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <climits>
#include <string_view>
#include "lix/libutil/box_ptr.hh"
@@ -760,7 +759,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
state.aio.blockOn(evaluator.store->buildPaths({
DerivedPath::Built {
.drvPath = makeConstantStorePathRef(drvPath),
.drvPath = makeConstantStorePath(drvPath),
.outputs = OutputsSpec::All { },
},
}));
@@ -1008,7 +1007,15 @@ Value * NixRepl::replOverlays()
for (auto path : evalSettings.replOverlays.get()) {
debug("Loading '%1%' path '%2%'...", "repl-overlays", path);
SourcePath sourcePath((CanonPath(path)));
// XXX(jade): This is a somewhat unsatisfying solution to
// https://git.lix.systems/lix-project/lix/issues/777 which means that
// the top level item in the repl-overlays file (that is, the lambda)
// gets evaluated with pure eval off. This means that if you want to do
// impure eval stuff, you will have to force it with builtins.seq.
bool prevPureEval = evalSettings.pureEval.get();
auto replInit = evalFile(sourcePath);
evalSettings.pureEval.setDefault(prevPureEval);
if (!replInit->isLambda()) {
evaluator.errors.make<TypeError>(
@@ -1056,19 +1063,24 @@ template<typename T, typename NameFn, typename ValueFn>
void NixRepl::addToScope(T && things, NameFn nameFn, ValueFn valueFn)
{
size_t added = 0;
for (auto && thing : things) {
if (displ + 1 >= envSize)
throw Error("environment full; cannot add more variables");
const auto name = nameFn(thing);
staticEnv->vars.emplace_back(name, displ);
env->values[displ++] = valueFn(thing);
varNames.emplace(evaluator.symbols[name]);
added++;
}
staticEnv->vars.unsafe_insert_bulk([&] (auto & map) {
auto oldSize = map.size();
for (auto && thing : things) {
if (displ + 1 >= envSize)
throw Error("environment full; cannot add more variables");
const auto name = nameFn(thing);
map.emplace_back(name, displ);
env->values[displ++] = valueFn(thing);
varNames.emplace(evaluator.symbols[name]);
added++;
}
// safety: we sort the range that we inserted so that we don't have to push that
// invariant up to the caller
std::sort(map.begin() + oldSize, map.end());
});
staticEnv->sort();
staticEnv->deduplicate();
if (added > 0) {
notice("Added %1% variables.", added);
}
@@ -1093,14 +1105,11 @@ void NixRepl::addVarToScope(const Symbol name, Value & v)
{
if (displ >= envSize)
throw Error("environment full; cannot add more variables");
if (auto oldVar = staticEnv->find(name); oldVar != staticEnv->vars.end()) {
staticEnv->vars.erase(oldVar);
if (staticEnv->vars.insert_or_assign(name, displ).second) {
notice("Updated %s.", evaluator.symbols[name]);
} else {
notice("Added %s.", evaluator.symbols[name]);
}
staticEnv->vars.emplace_back(name, displ);
staticEnv->sort();
env->values[displ++] = &v;
varNames.emplace(evaluator.symbols[name]);
}
+63
View File
@@ -48,6 +48,69 @@ attribute with the following attributes (all except `url` optional):
With this argument being true, it's possible to load a `rev` from *any* `ref`
(by default only `rev`s from the specified `ref` are supported).
- `narHash`
If given, the source is first looked-up in the Nix store and the [substituters](@docroot@/command-ref/conf-file.md#conf-substituters), and only fetched if not available.
The return value is an attrset containing the following keys:
- `lastModified` (`integer`)
Unix timestamp of the last update.
This corresponds to the timestamp of the "committer" timestamp embedded in the fetched commit.
- `lastModifiedDate` (`string`)
Textual representation of the `lastModified` timestamp in UTC (the timezone embedded in the git commit is discarded).
- `outPath` (`string`)
Resulting store path of the fetch process.
- `narHash` (`string`)
SRI representation of the hash of the `outPath`.
- `rev` (`string`)
The full-length revision fetched from the remote.
For further information see the `rev` input parameter.
This will usually be the output of `git rev-parse <rev>` (or `ref` when no `rev` is provided as an input parameter).
- `revCount` (`integer`)
Number of revisions in the history of the revision fetched.
For a repository with a single commit (the root) this number equals 1.
Fetches of shallow repositories report a value of 0.
- `shortRev` (`string`)
A short representation of the `rev`.
This string is a *truncated* version of the `rev`.
It is of fixed length and therefore not guaranteed to be unique (unlike the output of `git rev-parse --short`).
Future versions of Lix may change the length of this string only as part of a breaking change.
For maximum reproducibility and interoperability it is recommended to not rely on this value and to truncate the returned `rev` to an appropriate value instead.
- `submodules` (`boolean`)
Indicates whether submodules have been fetched.
If this value is set to `true`, any submodules are already checked out in the resulting `outPath`.
A full example of the output:
```nix
{
lastModified = 1746827286;
lastModifiedDate = "20250509214806";
narHash = "sha256-qCRBy8Bbh5XhPalPkhonxNgfsbw3lP0UIXBLSrhxAvI=";
outPath = "/nix/store/2qdnzhzccspwm70mni7jkvrfkpwcb3jn-source";
rev = "dcb0a97000d50b2868ed4f8d9fd465c5a5b8eb3a";
revCount = 17845;
shortRev = "dcb0a97";
submodules = false;
}
```
Here are some examples of how to use `fetchGit`.
- To fetch a private repository over SSH:
-23
View File
@@ -1,23 +0,0 @@
---
name: outputOf
args: [derivation-reference, output-name]
experimentalFeature: dynamic-derivations
---
Return the output path of a derivation, literally or using a placeholder if needed.
If the derivation has a statically-known output path (i.e. the derivation output is input-addressed, or fixed content-addressed), the output path will just be returned.
But if the derivation is content-addressed or if the derivation is itself not-statically produced (i.e. is the output of another derivation), a placeholder will be returned instead.
*`derivation reference`* must be a string that may contain a regular store path to a derivation, or may be a placeholder reference. If the derivation is produced by a derivation, you must explicitly select `drv.outPath`.
This primop can be chained arbitrarily deeply.
For instance,
```nix
builtins.outputOf
(builtins.outputOf myDrv "out)
"out"
```
will return a placeholder for the output of the output of `myDrv`.
This primop corresponds to the `^` sigil for derivable paths, e.g. as part of installable syntax on the command line.
+1 -1
View File
@@ -580,7 +580,7 @@ string_t AttrCursor::getStringWithContext(EvalState & state)
return d.drvPath;
},
[&](const NixStringContextElem::Built & b) -> const StorePath & {
return b.drvPath->getBaseStorePath();
return b.drvPath.path;
},
[&](const NixStringContextElem::Opaque & o) -> const StorePath & {
return o.path;
-1
View File
@@ -73,7 +73,6 @@ void EvalState::forceValue(Value & v, const PosIdx pos)
Expr & expr = *v.thunk.expr;
try {
v.mkBlackhole();
//checkInterrupt();
expr.eval(*this, *env, v);
} catch (...) {
v.mkThunk(env, expr);
+37 -46
View File
@@ -14,7 +14,6 @@
#include "lix/libutil/types.hh"
#include "lix/libstore/store-api.hh"
#include "lix/libstore/derivations.hh"
#include "lix/libstore/downstream-placeholder.hh"
#include "lix/libexpr/gc-alloc.hh"
#include "lix/libstore/filetransfer.hh"
#include "lix/libexpr/function-trace.hh"
@@ -549,7 +548,7 @@ void EvalBuiltins::addConstant(const std::string & name, Value * v, Constant inf
assert(info.type == gotType);
/* Install value the base environment. */
staticEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
staticEnv->vars.insert_or_assign(symbols.create(name), baseEnvDispl);
env.values[baseEnvDispl++] = v;
env.values[0]->attrs->push_back(Attr(symbols.create(name2), v));
}
@@ -584,7 +583,7 @@ Value * EvalBuiltins::addPrimOp(PrimOp && primOp)
Value * v = mem.allocValue();
v->mkPrimOp(new PrimOp(primOp));
staticEnv->vars.emplace_back(envName, baseEnvDispl);
staticEnv->vars.insert_or_assign(auto(envName), baseEnvDispl);
env.values[baseEnvDispl++] = v;
env.values[0]->attrs->push_back(Attr(symbols.create(primOp.name), v));
return v;
@@ -888,29 +887,18 @@ void EvalPaths::mkStorePathString(const StorePath & p, Value & v)
std::string EvalState::mkOutputStringRaw(
const SingleDerivedPath::Built & b,
std::optional<StorePath> optStaticOutputPath,
const ExperimentalFeatureSettings & xpSettings)
const StorePath & staticOutputPath)
{
/* In practice, this is testing for the case of CA derivations, or
dynamic derivations. */
return optStaticOutputPath
? ctx.store->printStorePath(std::move(*optStaticOutputPath))
/* Downstream we would substitute this for an actual path once
we build the floating CA derivation */
: DownstreamPlaceholder::fromSingleDerivedPathBuilt(b, xpSettings).render();
return ctx.store->printStorePath(staticOutputPath);
}
void EvalState::mkOutputString(
Value & value,
const SingleDerivedPath::Built & b,
std::optional<StorePath> optStaticOutputPath,
const ExperimentalFeatureSettings & xpSettings)
const StorePath & staticOutputPath)
{
value.mkString(
mkOutputStringRaw(b, optStaticOutputPath, xpSettings),
NixStringContext { b });
value.mkString(mkOutputStringRaw(staticOutputPath), NixStringContext { b });
}
@@ -922,19 +910,12 @@ std::string EvalState::mkSingleDerivedPathStringRaw(
return ctx.store->printStorePath(o.path);
},
[&](const SingleDerivedPath::Built & b) {
auto optStaticOutputPath = std::visit(overloaded {
[&](const SingleDerivedPath::Opaque & o) {
auto drv = aio.blockOn(ctx.store->readDerivation(o.path));
auto i = drv.outputs.find(b.output);
if (i == drv.outputs.end())
throw Error("derivation '%s' does not have output '%s'", b.drvPath->to_string(*ctx.store), b.output);
return i->second.path(*ctx.store, drv.name, b.output);
},
[&](const SingleDerivedPath::Built & o) -> std::optional<StorePath> {
return std::nullopt;
},
}, b.drvPath->raw());
return mkOutputStringRaw(b, optStaticOutputPath);
auto drv = aio.blockOn(ctx.store->readDerivation(b.drvPath.path));
auto i = drv.outputs.find(b.output);
if (i == drv.outputs.end())
throw Error("derivation '%s' does not have output '%s'", b.drvPath.to_string(*ctx.store), b.output);
auto staticOutputPath = i->second.path(*ctx.store, drv.name, b.output);
return mkOutputStringRaw(staticOutputPath);
}
}, p.raw());
}
@@ -1973,7 +1954,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
NixInt n{0};
NixFloat nf = 0;
bool first = !forceString;
bool first = !isInterpolation;
ValueType firstType = nString;
const auto str = [&] {
@@ -2036,12 +2017,17 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
state.ctx.errors.make<EvalError>("cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow();
} else {
if (s.empty()) s.reserve(es.size());
/* If we are coercing inside of an interpolation, we may allow slightly more comfort by coercing things like integers. */
auto coercionMode = isInterpolation && featureSettings.isEnabled(Xp::CoerceIntegers)
? StringCoercionMode::Interpolation : StringCoercionMode::Strict;
/* skip canonization of first path, which would only be not
canonized in the first place if it's coming from a ./${foo} type
path */
auto part = state.coerceToString(i_pos, vTmp, context,
"while evaluating a path segment",
false, firstType == nString, !first);
coercionMode, firstType == nString, !first);
sSize += part->size();
s.emplace_back(std::move(part));
}
@@ -2274,7 +2260,7 @@ bool EvalState::isDerivation(Value & v)
std::optional<std::string> EvalState::tryAttrsToString(const PosIdx pos, Value & v,
NixStringContext & context, bool coerceMore, bool copyToStore)
NixStringContext & context, StringCoercionMode mode, bool copyToStore)
{
auto i = v.attrs->find(ctx.s.toString);
if (i != v.attrs->end()) {
@@ -2283,7 +2269,7 @@ std::optional<std::string> EvalState::tryAttrsToString(const PosIdx pos, Value &
callFunction(*i->value, v, v1, i->pos);
return coerceToString(pos, v1, context,
"while evaluating the result of the `__toString` attribute",
coerceMore, copyToStore).toOwned();
mode, copyToStore).toOwned();
} catch (EvalError & e) {
e.addTrace(ctx.positions[pos], "while converting a set to string");
throw;
@@ -2298,7 +2284,7 @@ BackedStringView EvalState::coerceToString(
Value & v,
NixStringContext & context,
std::string_view errorCtx,
bool coerceMore,
StringCoercionMode mode,
bool copyToStore,
bool canonicalizePath)
{
@@ -2322,7 +2308,7 @@ BackedStringView EvalState::coerceToString(
}
if (v.type() == nAttrs) {
auto maybeString = tryAttrsToString(pos, v, context, coerceMore, copyToStore);
auto maybeString = tryAttrsToString(pos, v, context, mode, copyToStore);
if (maybeString)
return std::move(*maybeString);
auto i = v.attrs->find(ctx.s.outPath);
@@ -2336,24 +2322,29 @@ BackedStringView EvalState::coerceToString(
.debugThrow();
}
return coerceToString(pos, *i->value, context, errorCtx,
coerceMore, copyToStore, canonicalizePath);
mode, copyToStore, canonicalizePath);
}
if (v.type() == nExternal) {
try {
return v.external->coerceToString(*this, pos, context, coerceMore, copyToStore);
return v.external->coerceToString(*this, pos, context, mode, copyToStore);
} catch (Error & e) {
e.addTrace(nullptr, errorCtx);
throw;
}
}
if (coerceMore) {
/* Raito: Any addition to this mode is subject to extra scrutiny
* until we have better formatting tools. */
if (mode >= StringCoercionMode::Interpolation) {
if (v.type() == nInt) return std::to_string(v.integer.value);
}
if (mode >= StringCoercionMode::ToString) {
/* Note that `false' is represented as an empty string for
shell scripting convenience, just like `null'. */
if (v.type() == nBool && v.boolean) return "1";
if (v.type() == nBool && !v.boolean) return "";
if (v.type() == nInt) return std::to_string(v.integer.value);
if (v.type() == nFloat) return std::to_string(v.fpoint);
if (v.type() == nNull) return "";
@@ -2363,7 +2354,7 @@ BackedStringView EvalState::coerceToString(
try {
result += *coerceToString(pos, *v2, context,
"while evaluating one element of the list",
coerceMore, copyToStore, canonicalizePath);
mode, copyToStore, canonicalizePath);
} catch (Error & e) {
e.addTrace(ctx.positions[pos], errorCtx);
throw;
@@ -2420,7 +2411,7 @@ try {
SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext & context, std::string_view errorCtx)
{
auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
auto path = coerceToString(pos, v, context, errorCtx, StringCoercionMode::Strict, false, true).toOwned();
if (path == "" || path[0] != '/')
ctx.errors.make<EvalError>("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow();
return CanonPath(path);
@@ -2429,7 +2420,7 @@ SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext
StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, NixStringContext & context, std::string_view errorCtx)
{
auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
auto path = coerceToString(pos, v, context, errorCtx, StringCoercionMode::Strict, false, true).toOwned();
if (auto storePath = ctx.store->maybeParseStorePath(path))
return *storePath;
ctx.errors.make<EvalError>("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow();
@@ -2484,7 +2475,7 @@ SingleDerivedPath EvalState::coerceToSingleDerivedPath(const PosIdx pos, Value &
[&](const SingleDerivedPath::Built & b) {
ctx.errors.make<EvalError>(
"string '%s' has context with the output '%s' from derivation '%s', but the string is not the right placeholder for this derivation output. It should be '%s'",
s, b.output, b.drvPath->to_string(*ctx.store), sExpected)
s, b.output, b.drvPath.to_string(*ctx.store), sExpected)
.withTrace(pos, errorCtx).debugThrow(always_progresses);
}
}, derivedPath.raw());
@@ -2891,7 +2882,7 @@ try {
}
std::string ExternalValueBase::coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const
std::string ExternalValueBase::coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, StringCoercionMode mode, bool copyToStore) const
{
state.ctx.errors.make<TypeError>(
"cannot coerce %1% to a string: %2%", showType(), *this
+8 -18
View File
@@ -767,19 +767,18 @@ public:
bool isDerivation(Value & v);
std::optional<std::string> tryAttrsToString(const PosIdx pos, Value & v,
NixStringContext & context, bool coerceMore = false, bool copyToStore = true);
NixStringContext & context, StringCoercionMode mode = StringCoercionMode::Strict, bool copyToStore = true);
/**
* String coercion.
*
* Converts strings, paths and derivations to a
* string. If `coerceMore` is set, also converts nulls, integers,
* booleans and lists to a string. If `copyToStore` is set,
* string. If `copyToStore` is set,
* referenced paths are copied to the Nix store as a side effect.
*/
BackedStringView coerceToString(const PosIdx pos, Value & v, NixStringContext & context,
std::string_view errorCtx,
bool coerceMore = false, bool copyToStore = true,
StringCoercionMode mode = StringCoercionMode::Strict, bool copyToStore = true,
bool canonicalizePath = true);
/**
@@ -804,8 +803,7 @@ public:
/**
* Coerce to `SingleDerivedPath`.
*
* Must be a string which is either a literal store path or a
* "placeholder (see `DownstreamPlaceholder`).
* Must be a string which is either a literal store path.
*
* Even more importantly, the string context must be exactly one
* element, which is either a `NixStringContextElem::Opaque` or
@@ -870,19 +868,13 @@ public:
* @param b the drv whose output we are making a string for, and the
* output
*
* @param optStaticOutputPath Optional output path for that string.
* Must be passed if and only if output store object is
* input-addressed or fixed output. Will be printed to form string
* if passed, otherwise a placeholder will be used (see
* `DownstreamPlaceholder`).
*
* @param xpSettings Stop-gap to avoid globals during unit tests.
* @param staticOutputPath Output path for that string.
* Will be printed to form string.
*/
void mkOutputString(
Value & value,
const SingleDerivedPath::Built & b,
std::optional<StorePath> optStaticOutputPath,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
const StorePath & staticOutputPath);
/**
* Create a string representing a `SingleDerivedPath`.
@@ -902,9 +894,7 @@ private:
* string Value, which would also have a string context.
*/
std::string mkOutputStringRaw(
const SingleDerivedPath::Built & b,
std::optional<StorePath> optStaticOutputPath,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
const StorePath & staticOutputPath);
/**
* Like `mkSingleDerivedPathStringRaw` but just creates a raw string
+1
View File
@@ -34,6 +34,7 @@
executable ? false,
unpack ? false,
name ? baseNameOf (toString url),
# still translates to __impure to trigger derivationStrict error checks.
impure ? false,
}:
+1 -1
View File
@@ -298,7 +298,7 @@ static Flake getFlake(
NixStringContext emptyContext = {};
flake.config.settings.emplace(
state.ctx.symbols[setting.name],
state.coerceToString(setting.pos, *setting.value, emptyContext, "", false, true, true) .toOwned());
state.coerceToString(setting.pos, *setting.value, emptyContext, "", StringCoercionMode::Strict, true, true) .toOwned());
}
else if (setting.value->type() == nInt)
flake.config.settings.emplace(
+5
View File
@@ -7,6 +7,11 @@
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> {
-1
View File
@@ -115,7 +115,6 @@ builtin_definitions = files(
'builtins/mapAttrs.md',
'builtins/match.md',
'builtins/mul.md',
'builtins/outputOf.md',
'builtins/parseDrvName.md',
'builtins/parseFlakeRef.md',
'builtins/partition.md',
+21 -10
View File
@@ -19,6 +19,12 @@ std::ostream & operator <<(std::ostream & str, const SymbolStr & symbol)
return printIdentifier(str, s);
}
std::ostream & operator<<(std::ostream & str, const InternedSymbol & symbol)
{
str << SymbolStr(symbol);
return str;
}
AttrName::AttrName(PosIdx pos, Symbol s) : pos(pos), symbol(s)
{
}
@@ -273,7 +279,7 @@ JSON ExprConcatStrings::toJSON(const SymbolTable & symbols) const
parts.push_back(part->toJSON(symbols));
return {
{"_type", "ExprConcatStrings"},
{"forceString", forceString},
{"isInterpolation", isInterpolation},
{"es", parts}
};
}
@@ -465,7 +471,7 @@ void VarBinder::visit(ExprVar & e, std::unique_ptr<Expr> & ptr)
if (curEnv->isWith) {
if (withLevel == -1) withLevel = level;
} else {
auto i = curEnv->find(e.name);
auto i = curEnv->vars.find(e.name);
if (i != curEnv->vars.end()) {
if (e.needsRoot && !curEnv->isRoot) {
throw ParseError({
@@ -523,9 +529,12 @@ std::shared_ptr<const StaticEnv> ExprAttrs::buildRecursiveEnv(const std::shared_
{
auto newEnv = std::make_shared<StaticEnv>(nullptr, env.get(), attrs.size());
Displacement displ = 0;
for (auto & i : attrs)
newEnv->vars.emplace_back(i.first, i.second.displ = displ++);
// safety: the attrs is already sorted
newEnv->vars.unsafe_insert_bulk([&] (auto & map) {
Displacement displ = 0;
for (auto & i : attrs)
map.emplace_back(i.first, i.second.displ = displ++);
});
return newEnv;
}
@@ -662,7 +671,7 @@ void VarBinder::visit(ExprPos & e, std::unique_ptr<Expr> & ptr)
std::shared_ptr<const StaticEnv> SimplePattern::buildEnv(const StaticEnv * up)
{
auto newEnv = std::make_shared<StaticEnv>(nullptr, up, 1);
newEnv->vars.emplace_back(name, 0);
newEnv->vars.insert_or_assign(name, 0);
return newEnv;
}
@@ -677,12 +686,14 @@ std::shared_ptr<const StaticEnv> AttrsPattern::buildEnv(const StaticEnv * up)
Displacement displ = 0;
if (name) newEnv->vars.emplace_back(name, displ++);
if (name) newEnv->vars.insert_or_assign(name, displ++);
for (auto & i : formals)
newEnv->vars.emplace_back(i.name, displ++);
// safety: The formals are already sorted
newEnv->vars.unsafe_insert_bulk([&] (auto & map) {
for (auto & i : formals)
map.emplace_back(i.name, displ++);
});
newEnv->sort();
return newEnv;
}
+5 -31
View File
@@ -11,6 +11,7 @@
#include "lix/libexpr/eval-error.hh"
#include "lix/libexpr/pos-idx.hh"
#include "lix/libutil/strings.hh"
#include "lix/libutil/linear-map.hh"
namespace nix {
@@ -572,10 +573,10 @@ MakeBinOp(ExprOpConcatLists, "++")
struct ExprConcatStrings : Expr
{
bool forceString;
bool isInterpolation;
std::vector<std::pair<PosIdx, std::unique_ptr<Expr>>> es;
ExprConcatStrings(const PosIdx & pos, bool forceString, std::vector<std::pair<PosIdx, std::unique_ptr<Expr>>> es)
: Expr(pos), forceString(forceString), es(std::move(es)) { };
ExprConcatStrings(const PosIdx & pos, bool isInterpolation, std::vector<std::pair<PosIdx, std::unique_ptr<Expr>>> es)
: Expr(pos), isInterpolation(isInterpolation), es(std::move(es)) { };
JSON toJSON(const SymbolTable & symbols) const override;
void eval(EvalState & state, Env & env, Value & v) override;
void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) override { ev.visit(*this, ptr); }
@@ -607,9 +608,7 @@ struct StaticEnv
ExprWith * isWith;
const StaticEnv * up;
// Note: these must be in sorted order.
typedef std::vector<std::pair<Symbol, Displacement>> Vars;
Vars vars;
LinearMap<Symbol, Displacement> vars;
/* See ExprVar::needsRoot */
bool isRoot = false;
@@ -617,31 +616,6 @@ struct StaticEnv
StaticEnv(ExprWith * isWith, const StaticEnv * up, size_t expectedSize = 0) : isWith(isWith), up(up) {
vars.reserve(expectedSize);
};
void sort()
{
std::stable_sort(vars.begin(), vars.end(),
[](const Vars::value_type & a, const Vars::value_type & b) { return a.first < b.first; });
}
void deduplicate()
{
auto it = vars.begin(), jt = it, end = vars.end();
while (jt != end) {
*it = *jt++;
while (jt != end && it->first == jt->first) *it = *jt++;
it++;
}
vars.erase(it, end);
}
Vars::const_iterator find(Symbol name) const
{
Vars::value_type key(name, 0);
auto i = std::lower_bound(vars.begin(), vars.end(), key);
if (i != vars.end() && i->first == name) return i;
return vars.end();
}
};
+54 -125
View File
@@ -1,6 +1,5 @@
#include "lix/libutil/archive.hh"
#include "lix/libstore/derivations.hh"
#include "lix/libstore/downstream-placeholder.hh"
#include "lix/libexpr/eval.hh"
#include "lix/libexpr/eval-settings.hh"
#include "lix/libexpr/extra-primops.hh"
@@ -37,6 +36,10 @@
namespace nix {
/*
* Used for `builtins.groupBy`
*/
using ValueVectorMap = std::map<Symbol, ValueVector>;
/*************************************************************
* Miscellaneous
@@ -58,7 +61,7 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
.drvPath = b.drvPath,
.outputs = OutputsSpec::Names { b.output },
});
return ensureValid(b.drvPath->getBaseStorePath());
return ensureValid(b.drvPath.path);
},
[&](const NixStringContextElem::Opaque & o) {
auto ctxS = ctx.store->printStorePath(o.path);
@@ -93,18 +96,6 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
auto outputs = aio.blockOn(resolveDerivedPath(*ctx.buildStore, drv, &*ctx.store));
for (auto & [outputName, outputPath] : outputs) {
outputsToCopyAndAllow.insert(outputPath);
/* Get all the output paths corresponding to the placeholders we had */
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
res.insert_or_assign(
DownstreamPlaceholder::fromSingleDerivedPathBuilt(
SingleDerivedPath::Built {
.drvPath = drv.drvPath,
.output = outputName,
}).render(),
ctx.buildStore->printStorePath(outputPath)
);
}
}
}
@@ -166,7 +157,7 @@ static void mkOutputString(
state.mkOutputString(
attrs.alloc(o.first),
SingleDerivedPath::Built {
.drvPath = makeConstantStorePathRef(drvPath),
.drvPath = makeConstantStorePath(drvPath),
.output = o.first,
},
o.second.path(*state.ctx.store, Derivation::nameFromPath(drvPath), o.first));
@@ -242,14 +233,14 @@ static void import(EvalState & state, Value & vPath, Value * vScope, Value & v)
nullptr, state.ctx.builtins.staticEnv.get(), vScope->attrs->size()
);
unsigned int displ = 0;
for (auto & attr : *vScope->attrs) {
staticEnv->vars.emplace_back(attr.name, displ);
env->values[displ++] = attr.value;
}
// No need to call staticEnv.sort(), because
// args[0]->attrs is already sorted.
staticEnv->vars.unsafe_insert_bulk([&] (auto & map) {
unsigned int displ = 0;
for (auto & attr : *vScope->attrs) {
// safety: args[0]->attrs is already sorted.
map.emplace_back(attr.name, displ);
env->values[displ++] = attr.value;
}
});
debug("evaluating file '%1%'", path);
Expr & e = state.ctx.parseExprFromFile(state.ctx.paths.resolveExprPath(path), staticEnv);
@@ -315,13 +306,13 @@ void prim_exec(EvalState & state, Value * * args, Value & v)
NixStringContext context;
auto program = state.coerceToString(noPos, *elems[0], context,
"while evaluating the first element of the argument passed to builtins.exec",
false, false).toOwned();
StringCoercionMode::Strict, false).toOwned();
Strings commandArgs;
for (unsigned int i = 1; i < args[0]->listSize(); ++i) {
commandArgs.push_back(
state.coerceToString(noPos, *elems[i], context,
"while evaluating an element of the argument passed to builtins.exec",
false, false).toOwned());
StringCoercionMode::Strict, false).toOwned());
}
try {
auto _ = state.realiseContext(context); // FIXME: Handle CA derivations
@@ -610,7 +601,7 @@ static void prim_addErrorContext(EvalState & state, Value * * args, Value & v)
NixStringContext context;
auto message = state.coerceToString(noPos, *args[0], context,
"while evaluating the error message passed to builtins.addErrorContext",
false, false).toOwned();
StringCoercionMode::Strict, false).toOwned();
e.addTrace(nullptr, HintFmt(message));
throw;
}
@@ -810,8 +801,6 @@ drvName, Bindings * attrs, Value & v)
NixStringContext context;
bool contentAddressed = false;
bool isImpure = false;
std::optional<std::string> outputHash;
std::string outputHashAlgo;
std::optional<ContentAddressMethod> ingestionMethod;
@@ -821,16 +810,13 @@ drvName, Bindings * attrs, Value & v)
for (auto & i : attrs->lexicographicOrder(state.ctx.symbols)) {
if (i->name == state.ctx.s.ignoreNulls) continue;
const std::string & key = state.ctx.symbols[i->name];
auto & key = state.ctx.symbols[i->name];
vomit("processing attribute '%1%'", key);
auto handleHashMode = [&](const std::string_view s, NeverAsync = {}) {
if (s == "recursive") ingestionMethod = FileIngestionMethod::Recursive;
else if (s == "flat") ingestionMethod = FileIngestionMethod::Flat;
else if (s == "text") {
experimentalFeatureSettings.require(Xp::DynamicDerivations);
ingestionMethod = TextIngestionMethod {};
} else
else
state.ctx.errors.make<EvalError>(
"invalid value '%s' for 'outputHashMode' attribute", s
).debugThrow();
@@ -868,13 +854,13 @@ drvName, Bindings * attrs, Value & v)
}
if (i->name == state.ctx.s.contentAddressed && state.forceBool(*i->value, noPos, context_below)) {
contentAddressed = true;
experimentalFeatureSettings.require(Xp::CaDerivations);
state.ctx.errors.make<EvalError>("ca derivations are not supported in Lix")
.debugThrow();
}
else if (i->name == state.ctx.s.impure && state.forceBool(*i->value, noPos, context_below)) {
isImpure = true;
experimentalFeatureSettings.require(Xp::ImpureDerivations);
state.ctx.errors.make<EvalError>("impure derivations are not supported in Lix")
.debugThrow();
}
/* The `args' attribute is special: it supplies the
@@ -884,7 +870,7 @@ drvName, Bindings * attrs, Value & v)
for (auto elem : i->value->listItems()) {
auto s = state.coerceToString(noPos, *elem, context,
"while evaluating an element of the argument list",
true).toOwned();
StringCoercionMode::ToString).toOwned();
drv.args.push_back(s);
}
}
@@ -933,7 +919,7 @@ drvName, Bindings * attrs, Value & v)
} else {
auto s = state.coerceToString(noPos, *i->value, context, context_below, true).toOwned();
auto s = state.coerceToString(noPos, *i->value, context, context_below, StringCoercionMode::ToString).toOwned();
drv.env.emplace(key, s);
if (i->name == state.ctx.s.builder) drv.builder = std::move(s);
else if (i->name == state.ctx.s.system) drv.platform = std::move(s);
@@ -975,13 +961,13 @@ drvName, Bindings * attrs, Value & v)
for (auto & j : refs) {
drv.inputSrcs.insert(j);
if (j.isDerivation()) {
drv.inputDrvs.map[j].value =
drv.inputDrvs[j] =
state.aio.blockOn(state.ctx.store->readDerivation(j)).outputNames();
}
}
},
[&](const NixStringContextElem::Built & b) {
drv.inputDrvs.ensureSlot(*b.drvPath).value.insert(b.output);
drv.inputDrvs[b.drvPath.path].insert(b.output);
},
[&](const NixStringContextElem::Opaque & o) {
drv.inputSrcs.insert(o.path);
@@ -999,15 +985,10 @@ drvName, Bindings * attrs, Value & v)
.debugThrow();
/* Check whether the derivation name is valid. */
if (isDerivation(drvName) &&
!(ingestionMethod == ContentAddressMethod { TextIngestionMethod { } } &&
outputs.size() == 1 &&
*(outputs.begin()) == "out"))
{
state.ctx.errors.make<EvalError>(
"derivation names are allowed to end in '%s' only if they produce a single derivation file",
drvExtension
).debugThrow();
if (isDerivation(drvName)) {
state.ctx.errors
.make<EvalError>("derivation names are not allowed to end in '%s'", drvExtension)
.debugThrow();
}
if (outputHash) {
@@ -1035,31 +1016,6 @@ drvName, Bindings * attrs, Value & v)
drv.outputs.insert_or_assign("out", std::move(dof));
}
else if (contentAddressed || isImpure) {
if (contentAddressed && isImpure)
state.ctx.errors.make<EvalError>("derivation cannot be both content-addressed and impure")
.debugThrow();
auto ht = parseHashTypeOpt(outputHashAlgo).value_or(HashType::SHA256);
auto method = ingestionMethod.value_or(FileIngestionMethod::Recursive);
for (auto & i : outputs) {
drv.env[i] = hashPlaceholder(i);
if (isImpure)
drv.outputs.insert_or_assign(i,
DerivationOutput::Impure {
.method = method,
.hashType = ht,
});
else
drv.outputs.insert_or_assign(i,
DerivationOutput::CAFloating {
.method = method,
.hashType = ht,
});
}
}
else {
/* Compute a hash over the "masked" store derivation, which is
the final one except that in the list of outputs, the
@@ -1070,34 +1026,25 @@ drvName, Bindings * attrs, Value & v)
for (auto & i : outputs) {
drv.env[i] = "";
drv.outputs.insert_or_assign(i,
DerivationOutput::Deferred { });
DerivationOutput::InputAddressed { .path = StorePath::dummy });
}
auto hashModulo =
state.aio.blockOn(hashDerivationModulo(*state.ctx.store, Derivation(drv), true));
switch (hashModulo.kind) {
case DrvHash::Kind::Regular:
for (auto & i : outputs) {
auto h = get(hashModulo.hashes, i);
if (!h)
state.ctx.errors.make<AssertionError>(
"derivation produced no hash for output '%s'",
i
).debugThrow();
auto outPath = state.ctx.store->makeOutputPath(i, *h, drvName);
drv.env[i] = state.ctx.store->printStorePath(outPath);
drv.outputs.insert_or_assign(
i,
DerivationOutput::InputAddressed {
.path = std::move(outPath),
});
}
break;
;
case DrvHash::Kind::Deferred:
for (auto & i : outputs) {
drv.outputs.insert_or_assign(i, DerivationOutput::Deferred {});
}
for (auto & i : outputs) {
auto h = get(hashModulo.hashes, i);
if (!h)
state.ctx.errors.make<AssertionError>(
"derivation produced no hash for output '%s'",
i
).debugThrow();
auto outPath = state.ctx.store->makeOutputPath(i, *h, drvName);
drv.env[i] = state.ctx.store->printStorePath(outPath);
drv.outputs.insert_or_assign(
i,
DerivationOutput::InputAddressed {
.path = std::move(outPath),
});
}
}
@@ -1232,7 +1179,7 @@ static void prim_baseNameOf(EvalState & state, Value * * args, Value & v)
NixStringContext context;
v.mkString(baseNameOf(*state.coerceToString(noPos, *args[0], context,
"while evaluating the first argument passed to builtins.baseNameOf",
false, false)), context);
StringCoercionMode::Strict, false)), context);
}
/* Return the directory of the given path, i.e., everything before the
@@ -1248,7 +1195,7 @@ static void prim_dirOf(EvalState & state, Value * * args, Value & v)
NixStringContext context;
auto path = state.coerceToString(noPos, *args[0], context,
"while evaluating the first argument passed to 'builtins.dirOf'",
false, false);
StringCoercionMode::Strict, false);
auto dir = dirOf(*path);
v.mkString(dir, context);
}
@@ -1309,7 +1256,7 @@ static void prim_findFile(EvalState & state, Value * * args, Value & v)
NixStringContext context;
auto path = state.coerceToString(noPos, *i->value, context,
"while evaluating the `path` attribute of an element of the list passed to builtins.findFile",
false, false).toOwned();
StringCoercionMode::Strict, false).toOwned();
try {
auto rewrites = state.realiseContext(context);
@@ -1402,21 +1349,6 @@ static void prim_readDir(EvalState & state, Value * * args, Value & v)
v.mkAttrs(attrs);
}
/* Extend single element string context with another output. */
static void prim_outputOf(EvalState & state, Value * * args, Value & v)
{
SingleDerivedPath drvPath = state.coerceToSingleDerivedPath(noPos, *args[0], "while evaluating the first argument to builtins.outputOf");
OutputNameView outputName = state.forceStringNoCtx(*args[1], noPos, "while evaluating the second argument to builtins.outputOf");
state.mkSingleDerivedPathString(
SingleDerivedPath::Built {
.drvPath = make_ref<SingleDerivedPath>(drvPath),
.output = std::string { outputName },
},
v);
}
/*************************************************************
* Creating files
*************************************************************/
@@ -1607,7 +1539,7 @@ static void prim_path(EvalState & state, Value * * args, Value & v)
state.forceAttrs(*args[0], noPos, "while evaluating the argument passed to 'builtins.path'");
for (auto & attr : *args[0]->attrs) {
auto n = state.ctx.symbols[attr.name];
auto & n = state.ctx.symbols[attr.name];
if (n == "path")
path.emplace(state.coerceToPath(attr.pos, *attr.value, context, "while evaluating the 'path' attribute passed to 'builtins.path'"));
else if (attr.name == state.ctx.s.name)
@@ -1650,7 +1582,7 @@ static void prim_attrNames(EvalState & state, Value * * args, Value & v)
size_t n = 0;
for (auto & i : *args[0]->attrs)
(v.listElems()[n++] = state.ctx.mem.allocValue())->mkString(state.ctx.symbols[i.name]);
v.listElems()[n++] = const_cast<Value *>(state.ctx.symbols[i.name].toValuePtr());
std::sort(v.listElems(), v.listElems() + n,
[](Value * v1, Value * v2) { return strcmp(v1->string.s, v2->string.s) < 0; });
@@ -1949,9 +1881,8 @@ static void prim_mapAttrs(EvalState & state, Value * * args, Value & v)
auto attrs = state.ctx.buildBindings(args[1]->attrs->size());
for (auto & i : *args[1]->attrs) {
Value * vName = state.ctx.mem.allocValue();
Value * vFun2 = state.ctx.mem.allocValue();
vName->mkString(state.ctx.symbols[i.name]);
auto vName = const_cast<Value *>(state.ctx.symbols[i.name].toValuePtr());
vFun2->mkApp(args[0], vName);
attrs.alloc(i.name).mkApp(vFun2, i.value);
}
@@ -1997,8 +1928,7 @@ static void prim_zipAttrsWith(EvalState & state, Value * * args, Value & v)
}
for (auto & attr : *v.attrs) {
auto name = state.ctx.mem.allocValue();
name->mkString(state.ctx.symbols[attr.name]);
auto name = const_cast<Value *>(state.ctx.symbols[attr.name].toValuePtr());
auto call1 = state.ctx.mem.allocValue();
call1->mkApp(args[0], name);
auto call2 = state.ctx.mem.allocValue();
@@ -2488,7 +2418,7 @@ static void prim_toString(EvalState & state, Value * * args, Value & v)
NixStringContext context;
auto s = state.coerceToString(noPos, *args[0], context,
"while evaluating the first argument passed to builtins.toString",
true, false);
StringCoercionMode::ToString, false);
v.mkString(*s, context);
}
@@ -2870,7 +2800,6 @@ void EvalBuiltins::createBaseEnv(const SearchPath & searchPath, const Path & sto
because attribute lookups expect it to be sorted. */
env.values[0]->attrs->sort();
staticEnv->sort();
staticEnv->isRoot = true;
}
+2 -4
View File
@@ -127,9 +127,7 @@ void prim_getContext(EvalState & state, Value * * args, Value & v)
contextInfos[std::move(d.drvPath)].allOutputs = true;
},
[&](NixStringContextElem::Built && b) {
// FIXME should eventually show string context as is, no
// resolving here.
auto drvPath = state.aio.blockOn(resolveDerivedPath(*state.ctx.store, *b.drvPath));
auto drvPath = b.drvPath.path;
contextInfos[std::move(drvPath)].outputs.emplace_back(std::move(b.output));
},
[&](NixStringContextElem::Opaque && o) {
@@ -219,7 +217,7 @@ static void prim_appendContext(EvalState & state, Value * * args, Value & v)
for (auto elem : iter->value->listItems()) {
auto outputName = state.forceStringNoCtx(*elem, iter->pos, "while evaluating an output name within a string context");
context.emplace(NixStringContextElem::Built {
.drvPath = makeConstantStorePathRef(namePath),
.drvPath = makeConstantStorePath(namePath),
.output = std::string { outputName },
});
}
+2 -2
View File
@@ -22,7 +22,7 @@ static void prim_fetchMercurial(EvalState & state, Value * * args, Value & v)
if (n == "url")
url = state.coerceToString(attr.pos, *attr.value, context,
"while evaluating the `url` attribute passed to builtins.fetchMercurial",
false, false).toOwned();
StringCoercionMode::Strict, false).toOwned();
else if (n == "rev") {
// Ugly: unlike fetchGit, here the "rev" attribute can
// be both a revision or a branch/tag name.
@@ -44,7 +44,7 @@ static void prim_fetchMercurial(EvalState & state, Value * * args, Value & v)
} else
url = state.coerceToString(noPos, *args[0], context,
"while evaluating the first argument passed to builtins.fetchMercurial",
false, false).toOwned();
StringCoercionMode::Strict, false).toOwned();
// FIXME: git externals probably can be used to bypass the URI
// whitelist. Ah well.
+9 -2
View File
@@ -139,7 +139,7 @@ static void fetchTree(
if (attr.name == state.ctx.s.type) continue;
state.forceValue(*attr.value, attr.pos);
if (attr.value->type() == nPath || attr.value->type() == nString) {
auto s = state.coerceToString(attr.pos, *attr.value, context, "", false, false).toOwned();
auto s = state.coerceToString(attr.pos, *attr.value, context, "", StringCoercionMode::Strict, false).toOwned();
attrs.emplace(state.ctx.symbols[attr.name],
state.ctx.symbols[attr.name] == "url"
? type == "git"
@@ -169,11 +169,18 @@ static void fetchTree(
"attribute 'name' isnt supported in call to 'fetchTree'"
).atPos(pos).debugThrow();
// HACK: When using `fetchGit`, locking with only the hash should happen
// as we don't care about flake shenanigans about `lastModified`
if (type == "git" && attrs.contains("narHash")) {
using namespace std::literals::string_literals;
attrs["type"] = "\0git-locked"s;
}
input = fetchers::Input::fromAttrs(std::move(attrs));
} else {
auto url = state.coerceToString(pos, *args[0], context,
"while evaluating the first argument passed to the fetcher",
false, false).toOwned();
StringCoercionMode::Strict, false).toOwned();
if (type == "git") {
fetchers::Attrs attrs;
+65 -147
View File
@@ -6,164 +6,82 @@
namespace nix {
#if HAVE_TOML11_4
/**
* This is what toml11 < 4.0 did when choosing the subsecond precision.
* TOML 1.0.0 spec doesn't define how sub-millisecond ranges should be handled and calls it
* implementation defined behavior. For a lack of a better choice we stick with what older versions
* of toml11 did [1].
*
* [1]:
* https://github.com/ToruNiina/toml11/blob/dcfe39a783a94e8d52c885e5883a6fbb21529019/toml/datetime.hpp#L282
*/
static size_t normalizeSubsecondPrecision(toml::local_time lt)
void prim_fromTOML(EvalState & state, Value * * args, Value & val)
{
auto millis = lt.millisecond;
auto micros = lt.microsecond;
auto nanos = lt.nanosecond;
if (millis != 0 || micros != 0 || nanos != 0) {
if (micros != 0 || nanos != 0) {
if (nanos != 0) {
return 9;
}
return 6;
}
return 3;
}
return 0;
}
/**
* Normalize date/time formats to serialize to the same strings as versions prior to toml11 4.0.
*
* Several things to consider:
*
* 1. Sub-millisecond range is represented the same way as in toml11 versions prior to 4.0.
* Precision is rounded towards the next multiple of 3 or capped at 9 digits.
* 2. Seconds must be specified. This may become optional in (yet unreleased) TOML 1.1.0, but 1.0.0
* defined local time in terms of RFC3339 [1].
* 3. date-time separator (`t`, `T` or space ` `) is canonicalized to an upper T. This is compliant
* with RFC3339 [1] 5.6: > Applications that generate this format SHOULD use upper case letters.
*
* [1]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
*/
static void normalizeDatetimeFormat(toml::value & t)
{
if (t.is_local_datetime()) {
auto & ldt = t.as_local_datetime();
t.as_local_datetime_fmt() = {
.delimiter = toml::datetime_delimiter_kind::upper_T,
// https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
.has_seconds = true, // Mandated by TOML 1.0.0
.subsecond_precision = normalizeSubsecondPrecision(ldt.time),
};
return;
}
if (t.is_offset_datetime()) {
auto & odt = t.as_offset_datetime();
t.as_offset_datetime_fmt() = {
.delimiter = toml::datetime_delimiter_kind::upper_T,
// https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
.has_seconds = true, // Mandated by TOML 1.0.0
.subsecond_precision = normalizeSubsecondPrecision(odt.time),
};
return;
}
if (t.is_local_time()) {
auto & lt = t.as_local_time();
t.as_local_time_fmt() = {
.has_seconds = true, // Mandated by TOML 1.0.0
.subsecond_precision = normalizeSubsecondPrecision(lt),
};
return;
}
}
#endif
void prim_fromTOML(EvalState & state, Value ** args, Value & val)
{
auto toml = state.forceStringNoCtx(
*args[0], noPos, "while evaluating the argument passed to builtins.fromTOML"
);
auto toml = state.forceStringNoCtx(*args[0], noPos, "while evaluating the argument passed to builtins.fromTOML");
std::istringstream tomlStream(std::string{toml});
auto visit = [&](this const auto & self, Value & v, toml::value t) -> void {
switch (t.type()) {
case toml::value_t::table: {
auto table = toml::get<toml::table>(t);
auto attrs = state.ctx.buildBindings(table.size());
std::function<void(Value &, toml::value)> visit;
for (auto & elem : table) {
self(attrs.alloc(elem.first), elem.second);
}
visit = [&](Value & v, toml::value t) {
v.mkAttrs(attrs);
} break;
case toml::value_t::array: {
auto array = toml::get<std::vector<toml::value>>(t);
switch(t.type())
{
case toml::value_t::table:
{
auto table = toml::get<toml::table>(t);
size_t size = 0;
for (auto & i : table) { (void) i; size++; }
auto attrs = state.ctx.buildBindings(size);
for(auto & elem : table)
visit(attrs.alloc(elem.first), elem.second);
v.mkAttrs(attrs);
}
break;;
case toml::value_t::array:
{
auto array = toml::get<std::vector<toml::value>>(t);
size_t size = array.size();
v = state.ctx.mem.newList(size);
for (size_t i = 0; i < size; ++i)
visit(*(v.listElems()[i] = state.ctx.mem.allocValue()), array[i]);
}
break;;
case toml::value_t::boolean:
v.mkBool(toml::get<bool>(t));
break;;
case toml::value_t::integer:
v.mkInt(toml::get<int64_t>(t));
break;;
case toml::value_t::floating:
v.mkFloat(toml::get<NixFloat>(t));
break;;
case toml::value_t::string:
v.mkString(toml::get<std::string>(t));
break;;
case toml::value_t::local_datetime:
case toml::value_t::offset_datetime:
case toml::value_t::local_date:
case toml::value_t::local_time:
{
if (experimentalFeatureSettings.isEnabled(Xp::ParseTomlTimestamps)) {
auto attrs = state.ctx.buildBindings(2);
attrs.alloc("_type").mkString("timestamp");
std::ostringstream s;
s << t;
attrs.alloc("value").mkString(s.str());
v.mkAttrs(attrs);
} else {
// NOLINTNEXTLINE(lix-foreign-exceptions)
throw std::runtime_error("Dates and times are not supported");
}
}
break;;
case toml::value_t::empty:
v.mkNull();
break;;
size_t size = array.size();
v = state.ctx.mem.newList(size);
for (size_t i = 0; i < size; ++i) {
self(*(v.listElems()[i] = state.ctx.mem.allocValue()), array[i]);
}
} break;
case toml::value_t::boolean:
v.mkBool(toml::get<bool>(t));
break;
case toml::value_t::integer:
v.mkInt(toml::get<int64_t>(t));
break;
case toml::value_t::floating:
v.mkFloat(toml::get<NixFloat>(t));
break;
case toml::value_t::string:
v.mkString(toml::get<std::string>(t));
break;
case toml::value_t::local_datetime:
case toml::value_t::offset_datetime:
case toml::value_t::local_date:
case toml::value_t::local_time: {
if (experimentalFeatureSettings.isEnabled(Xp::ParseTomlTimestamps)) {
#if HAVE_TOML11_4
normalizeDatetimeFormat(t);
#endif
auto attrs = state.ctx.buildBindings(2);
attrs.alloc("_type").mkString("timestamp");
std::ostringstream s;
s << t;
attrs.alloc("value").mkString(s.str());
v.mkAttrs(attrs);
} else {
// NOLINTNEXTLINE(lix-foreign-exceptions)
throw std::runtime_error("Dates and times are not supported");
}
} break;
case toml::value_t::empty:
v.mkNull();
break;
}
};
try {
visit(
val,
toml::parse(
tomlStream,
"fromTOML" /* the "filename" */
#if HAVE_TOML11_4
,
toml::spec::v(
1, 0, 0
) // Be explicit that we are parsing TOML 1.0.0 without extensions
#endif
)
);
visit(val, toml::parse(tomlStream, "fromTOML" /* the "filename" */));
} catch (std::exception & e) { // NOLINT(lix-foreign-exceptions) // TODO: toml::syntax_error
state.ctx.errors.make<EvalError>("while parsing TOML: %s", e.what()).debugThrow();
}
+1
View File
@@ -1,6 +1,7 @@
#pragma once
///@file
#include "lix/libexpr/symbol-table.hh"
#include "lix/libexpr/value.hh"
namespace nix {
+1 -1
View File
@@ -287,7 +287,7 @@ private:
AttrVec sorted;
for (auto & i : *v.attrs)
sorted.emplace_back(std::pair(state.ctx.symbols[i.name], i.value));
sorted.emplace_back(state.ctx.symbols[i.name], i.value);
if (options.maxAttrs == std::numeric_limits<size_t>::max())
std::sort(sorted.begin(), sorted.end());
+1 -1
View File
@@ -2,7 +2,7 @@
name: ignore-try
internalName: ignoreExceptionsDuringTry
type: bool
default: false
default: true
---
If set to true, ignore exceptions inside 'tryEval' calls when evaluating nix expressions in
debug mode (using the --debugger flag). By default the debugger will pause on all exceptions.
+70 -8
View File
@@ -6,6 +6,8 @@
#include "lix/libutil/types.hh"
#include "lix/libutil/chunked-vector.hh"
#include "lix/libexpr/value.hh"
namespace nix {
/**
@@ -16,6 +18,7 @@ namespace nix {
class SymbolStr
{
friend class SymbolTable;
friend class InternedSymbol;
private:
const std::string * s;
@@ -41,6 +44,59 @@ public:
friend std::ostream & operator <<(std::ostream & os, const SymbolStr & symbol);
};
class InternedSymbol
{
private:
/*
* The type that actually stores the string contained inside of the Value.
*/
std::string contents;
/*
* A value containing a string that can be immediately passed to the evaluator.
*/
Value underlyingValue;
public:
explicit InternedSymbol(std::string_view s)
: contents(s)
, underlyingValue(NewValueAs::string, contents.c_str(), nullptr)
{
}
InternedSymbol(InternedSymbol &&) = default;
InternedSymbol & operator=(InternedSymbol &&) = default;
KJ_DISALLOW_COPY(InternedSymbol);
operator SymbolStr() const
{
return SymbolStr(contents);
}
bool operator==(std::string_view s2) const
{
return contents == s2;
}
operator const std::string &() const
{
return contents;
}
operator std::string_view() const
{
return contents;
}
const Value * toValuePtr() const
{
return &underlyingValue;
}
friend std::ostream & operator<<(std::ostream & os, const InternedSymbol & symbol);
};
/**
* Symbols have the property that they can be compared efficiently
* (using an equality test), because the symbol table stores only one
@@ -72,13 +128,17 @@ public:
class SymbolTable
{
private:
std::unordered_map<std::string_view, std::pair<const std::string *, uint32_t>> symbols;
ChunkedVector<std::string, 8192> store{16};
/**
* Map from string view (backed by ChunkedVector) -> offset into the store.
* ChunkedVector references are never invalidated.
*/
std::unordered_map<std::string_view, uint32_t> symbols;
ChunkedVector<InternedSymbol, 8192> store{16};
public:
/**
* converts a string into a symbol.
* Converts a string into a symbol.
*/
Symbol create(std::string_view s)
{
@@ -88,18 +148,20 @@ public:
// on the original implementation using unordered_set
// FIXME: make this thread-safe.
auto it = symbols.find(s);
if (it != symbols.end()) return Symbol(it->second.second + 1);
if (it != symbols.end()) {
return Symbol(it->second + 1);
}
const auto & [rawSym, idx] = store.add(std::string(s));
symbols.emplace(rawSym, std::make_pair(&rawSym, idx));
const auto & [rawSym, idx] = store.add(s);
symbols.emplace(rawSym, idx);
return Symbol(idx + 1);
}
SymbolStr operator[](Symbol s) const
const InternedSymbol & operator[](Symbol s) const
{
if (s.id == 0 || s.id > store.size())
abort();
return SymbolStr(store[s.id - 1]);
return store[s.id - 1];
}
size_t size() const
+1 -1
View File
@@ -46,7 +46,7 @@ JSON printValueAsJSON(EvalState & state, bool strict,
break;
case nAttrs: {
auto maybeString = state.tryAttrsToString(pos, v, context, false, false);
auto maybeString = state.tryAttrsToString(pos, v, context, StringCoercionMode::Strict, false);
if (maybeString) {
out = *maybeString;
break;
+22 -5
View File
@@ -8,8 +8,8 @@
#include <span>
#include "lix/libexpr/gc-alloc.hh"
#include "lix/libexpr/symbol-table.hh"
#include "lix/libexpr/value/context.hh"
#include "lix/libutil/logging.hh"
#include "lix/libutil/source-path.hh"
#include "lix/libexpr/print-options.hh"
#include "lix/libutil/checked-arithmetic.hh"
@@ -59,13 +59,32 @@ typedef enum {
nExternal
} ValueType;
/**
* Modes of string coercion.
*
* Determines how permissive the coercion functions are when converting
* values to strings.
*
* - Strict: Only allow coercion of values that are already strings,
* paths, or derivations.
* - Interpolation: Additionally allow coercion of unambiguously printable values in a string, for
* now: integers. This mode is meant as a stopgap measure until we get better formatting tools.
* - ToString: Additionally allow coercion of integers, booleans, null,
* and lists to strings.
*/
enum class StringCoercionMode {
Strict,
Interpolation,
ToString,
};
class Bindings;
struct Env;
struct Expr;
struct ExprLambda;
struct ExprBlackHole;
struct PrimOp;
class Symbol;
class PosIdx;
struct Pos;
class StorePath;
@@ -106,7 +125,7 @@ class ExternalValueBase
* Coerce the value to a string. Defaults to uncoercable, i.e. throws an
* error.
*/
virtual std::string coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const;
virtual std::string coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, StringCoercionMode mode, bool copyToStore) const;
/**
* Compare to another value of the same type. Defaults to uncomparable,
@@ -839,8 +858,6 @@ public:
};
using ValueVector = GcVector<Value *>;
using ValueMap = GcMap<Symbol, Value *>;
using ValueVectorMap = std::map<Symbol, ValueVector>;
/**
* A value allocated in traceable memory.
+18 -47
View File
@@ -2,33 +2,10 @@
namespace nix {
NixStringContextElem NixStringContextElem::parse(
std::string_view s0,
const ExperimentalFeatureSettings & xpSettings)
NixStringContextElem NixStringContextElem::parse(std::string_view s0)
{
std::string_view s = s0;
std::function<SingleDerivedPath()> parseRest;
parseRest = [&]() -> SingleDerivedPath {
// Case on whether there is a '!'
size_t index = s.find("!");
if (index == std::string_view::npos) {
return SingleDerivedPath::Opaque {
.path = StorePath { s },
};
} else {
std::string output { s.substr(0, index) };
// Advance string to parse after the '!'
s = s.substr(index + 1);
auto drv = make_ref<SingleDerivedPath>(parseRest());
drvRequireExperiment(*drv, xpSettings);
return SingleDerivedPath::Built {
.drvPath = std::move(drv),
.output = std::move(output),
};
}
};
if (s.size() == 0) {
throw BadNixStringContextElem(s0,
"String context element should never be an empty string");
@@ -40,14 +17,20 @@ NixStringContextElem NixStringContextElem::parse(
s = s.substr(1);
// Find *second* '!'
if (s.find("!") == std::string_view::npos) {
size_t index = s.find("!");
if (index == std::string_view::npos) {
throw BadNixStringContextElem(s0,
"String content element beginning with '!' should have a second '!'");
}
return std::visit(
[&](auto x) -> NixStringContextElem { return std::move(x); },
parseRest());
std::string output { s.substr(0, index) };
// Advance string to parse after the '!'
s = s.substr(index + 1);
auto drv = SingleDerivedPath::Opaque{StorePath{s}};
return SingleDerivedPath::Built{
.drvPath = std::move(drv),
.output = std::move(output),
};
}
case '=': {
return NixStringContextElem::DrvDeep {
@@ -60,9 +43,9 @@ NixStringContextElem NixStringContextElem::parse(
throw BadNixStringContextElem(s0,
"String content element not beginning with '!' should not have a second '!'");
}
return std::visit(
[&](auto x) -> NixStringContextElem { return std::move(x); },
parseRest());
return SingleDerivedPath::Opaque{
.path = StorePath{s},
};
}
}
}
@@ -71,27 +54,15 @@ std::string NixStringContextElem::to_string() const
{
std::string res;
std::function<void(const SingleDerivedPath &)> toStringRest;
toStringRest = [&](auto & p) {
std::visit(overloaded {
[&](const SingleDerivedPath::Opaque & o) {
res += o.path.to_string();
},
[&](const SingleDerivedPath::Built & o) {
res += o.output;
res += '!';
toStringRest(*o.drvPath);
},
}, p.raw());
};
std::visit(overloaded {
[&](const NixStringContextElem::Built & b) {
res += '!';
toStringRest(b);
res += b.output;
res += '!';
res += b.drvPath.path.to_string();
},
[&](const NixStringContextElem::Opaque & o) {
toStringRest(o);
res += o.path.to_string();
},
[&](const NixStringContextElem::DrvDeep & d) {
res += '=';
+1 -5
View File
@@ -69,12 +69,8 @@ struct NixStringContextElem {
* - <path>
* - =<path>
* - !<name>!<path>
*
* @param xpSettings Stop-gap to avoid globals during unit tests.
*/
static NixStringContextElem parse(
std::string_view s,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
static NixStringContextElem parse(std::string_view s);
std::string to_string() const;
};
+1
View File
@@ -10,6 +10,7 @@ std::unique_ptr<InputScheme> makePathInputScheme();
std::unique_ptr<InputScheme> makeFileInputScheme();
std::unique_ptr<InputScheme> makeTarballInputScheme();
std::unique_ptr<InputScheme> makeGitInputScheme();
std::unique_ptr<InputScheme> makeGitLockedInputScheme();
std::unique_ptr<InputScheme> makeMercurialInputScheme();
std::unique_ptr<InputScheme> makeGitHubInputScheme();
std::unique_ptr<InputScheme> makeGitLabInputScheme();
+19
View File
@@ -22,6 +22,7 @@ void initLibFetchers()
registerInputScheme(makeTarballInputScheme());
registerInputScheme(makeFileInputScheme());
registerInputScheme(makeGitInputScheme());
registerInputScheme(makeGitLockedInputScheme());
registerInputScheme(makeMercurialInputScheme());
registerInputScheme(makeGitHubInputScheme());
registerInputScheme(makeGitLabInputScheme());
@@ -304,6 +305,24 @@ std::optional<time_t> Input::getLastModified() const
return maybeGetIntAttr(attrs, "lastModified");
}
std::optional<Input> InputScheme::inputFromAttrs(const Attrs & attrs) const
{
if (maybeGetStrAttr(attrs, "type") != schemeType()) return {};
Attrs finalAttrs = preprocessAttrs(attrs);
for (auto & [name, value] : finalAttrs)
// All attrs need to accept a `type` and `narHash` key, the rest is scheme-specific
if (name != "type" && name != "narHash" && !allowedAttrs().contains(name))
throw UnsupportedAttributeError("unsupported input attribute '%s' for the '%s' scheme", name, schemeType());
Input input;
input.attrs = finalAttrs;
return input;
}
ParsedURL InputScheme::toURL(const Input & input) const
{
throw Error("don't know how to convert input '%s' to a URL", attrsToJSON(input.attrs));
+12 -2
View File
@@ -2,6 +2,7 @@
///@file
#include "lix/libstore/content-address.hh"
#include "lix/libutil/error.hh"
#include "lix/libutil/result.hh"
#include "lix/libutil/types.hh"
#include "lix/libutil/hash.hh"
@@ -19,6 +20,8 @@ namespace nix { class Store; }
namespace nix::fetchers {
MakeError(UnsupportedAttributeError, Error);
struct Tree
{
Path actualPath;
@@ -125,7 +128,6 @@ public:
std::optional<time_t> getLastModified() const;
};
/**
* The InputScheme represents a type of fetcher. Each fetcher
* registers with nix at startup time. When processing an input for a
@@ -142,7 +144,12 @@ struct InputScheme
virtual std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const = 0;
virtual std::optional<Input> inputFromAttrs(const Attrs & attrs) const = 0;
virtual Attrs preprocessAttrs(const Attrs & attrs) const = 0;
// The scheme type, which is used to match attributes to a specific scheme
virtual std::string schemeType() const = 0;
virtual std::optional<Input> inputFromAttrs(const Attrs & attrs) const;
virtual ParsedURL toURL(const Input & input) const;
@@ -174,6 +181,9 @@ struct InputScheme
virtual bool isLockedByRev() const { return true; }
protected:
// The set of allowed attributes for this specific fetcher
virtual const std::set<std::string> & allowedAttrs() const = 0;
void emplaceURLQueryIntoAttrs(
const ParsedURL & parsedURL,
Attrs & attrs,
+56 -17
View File
@@ -165,11 +165,19 @@ WorkdirInfo getWorkdirInfo(const Input & input, const Path & workdir)
/* Check whether HEAD points to something that looks like a commit,
since that is the refrence we want to use later on. */
auto result = runProgram(RunOptions {
auto result = runProgram(RunOptions{
.program = "git",
.args = { "-C", workdir, "--git-dir", gitDir, "rev-parse", "--verify", "--no-revs", "HEAD^{commit}" },
.args =
{"-C",
workdir,
"--git-dir",
gitDir,
"rev-parse",
"--verify",
"--no-revs",
"HEAD^{commit}"},
.environment = env,
.redirections = {{.from = STDERR_FILENO, .to = STDOUT_FILENO}},
.redirections = {{.dup = STDERR_FILENO, .from = STDOUT_FILENO}},
});
auto exitCode = WEXITSTATUS(result.first);
auto errorMessage = result.second;
@@ -289,8 +297,28 @@ static std::optional<Path> resolveRefToCachePath(
return std::nullopt;
}
static const std::set<std::string> allowedGitAttrs = {
"allRefs",
"dirtyRev",
"dirtyShortRev",
"lastModified",
"name",
"ref",
"rev",
"revCount",
"shallow",
"submodules",
"url",
};
struct GitInputScheme : InputScheme
{
std::string schemeType() const override { return "git"; }
const std::set<std::string> & allowedAttrs() const override {
return allowedGitAttrs;
}
std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
{
if (url.scheme != "git" &&
@@ -317,14 +345,7 @@ struct GitInputScheme : InputScheme
return inputFromAttrs(attrs);
}
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
{
if (maybeGetStrAttr(attrs, "type") != "git") return {};
for (auto & [name, value] : attrs)
if (name != "type" && name != "url" && name != "ref" && name != "rev" && name != "shallow" && name != "submodules" && name != "lastModified" && name != "revCount" && name != "narHash" && name != "allRefs" && name != "name" && name != "dirtyRev" && name != "dirtyShortRev")
throw Error("unsupported Git input attribute '%s'", name);
Attrs preprocessAttrs(const Attrs & attrs) const override {
parseURL(getStrAttr(attrs, "url"));
maybeGetBoolAttr(attrs, "shallow");
maybeGetBoolAttr(attrs, "submodules");
@@ -335,9 +356,7 @@ struct GitInputScheme : InputScheme
throw BadURL("invalid Git branch/tag name '%s'", *ref);
}
Input input;
input.attrs = attrs;
return input;
return attrs;
}
ParsedURL toURL(const Input & input) const override
@@ -699,10 +718,12 @@ struct GitInputScheme : InputScheme
AutoDelete delTmpDir(tmpDir, true);
PathFilter filter = defaultPathFilter;
auto result = runProgram(RunOptions {
auto result = runProgram(RunOptions{
.program = "git",
.args = { "-C", repoDir, "--git-dir", gitDir, "cat-file", "commit", input.getRev()->gitRev() },
.redirections = {{.from = STDERR_FILENO, .to = STDOUT_FILENO}},
.args =
{"-C", repoDir, "--git-dir", gitDir, "cat-file", "commit", input.getRev()->gitRev()
},
.redirections = {{.dup = STDERR_FILENO, .from = STDOUT_FILENO}},
});
if (WEXITSTATUS(result.first) == 128
&& result.second.find("bad file") != std::string::npos)
@@ -813,4 +834,22 @@ std::unique_ptr<InputScheme> makeGitInputScheme()
return std::make_unique<GitInputScheme>();
}
struct GitLockedInputScheme : GitInputScheme {
std::string schemeType() const override {
using namespace std::literals::string_literals;
return "\0git-locked"s;
}
bool hasAllInfo(const Input & input) const override {
return true;
}
};
std::unique_ptr<InputScheme> makeGitLockedInputScheme()
{
return std::make_unique<GitLockedInputScheme>();
}
}
+21 -22
View File
@@ -1,7 +1,5 @@
#include "lix/libfetchers/attrs.hh"
#include "lix/libstore/filetransfer.hh"
#include "lix/libfetchers/cache.hh"
#include "lix/libstore/globals.hh"
#include "lix/libfetchers/builtin-fetchers.hh"
#include "lix/libstore/store-api.hh"
#include "lix/libutil/async.hh"
@@ -24,19 +22,30 @@ struct DownloadUrl
Headers headers;
};
static const std::set<std::string> allowedGitArchiveAttrs = {
"host",
"lastModified",
"owner",
"ref",
"repo",
"rev",
};
// A github, gitlab, or sourcehut host
const static std::string hostRegexS = "[a-zA-Z0-9.-]*"; // FIXME: check
std::regex hostRegex = regex::parse(hostRegexS, std::regex::ECMAScript);
struct GitArchiveInputScheme : InputScheme
{
virtual std::string type() const = 0;
const std::set<std::string> & allowedAttrs() const override {
return allowedGitArchiveAttrs;
}
virtual std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const = 0;
std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
{
if (url.scheme != type()) return {};
if (url.scheme != schemeType()) return {};
auto path = tokenizeString<std::vector<std::string>>(url.path, "/");
@@ -63,7 +72,7 @@ struct GitArchiveInputScheme : InputScheme
throw BadURL("URL '%s' is invalid", url.url);
Attrs attrs;
attrs.emplace("type", type());
attrs.emplace("type", schemeType());
attrs.emplace("owner", path[0]);
attrs.emplace("repo", path[1]);
@@ -93,20 +102,18 @@ struct GitArchiveInputScheme : InputScheme
return inputFromAttrs(attrs);
}
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
Attrs preprocessAttrs(const Attrs & attrs) const override
{
// Attributes can contain refOrRev and it needs to be figured out
// which one it is (see inputFromURL for when that may happen).
// The correct one (ref or rev) will be written into finalAttrs and
// it needs to be mutable for that.
Attrs finalAttrs(attrs);
auto type_ = maybeGetStrAttr(finalAttrs, "type");
if (type_ != type()) return {};
auto owner = getStrAttr(finalAttrs, "owner");
auto repo = getStrAttr(finalAttrs, "repo");
auto url = fmt("%s:%s/%s", *type_, owner, repo);
auto url = fmt("%s:%s/%s", schemeType(), owner, repo);
if (auto host = maybeGetStrAttr(finalAttrs, "host")) {
if (!std::regex_match(*host, hostRegex)) {
throw BadURL("URL '%s' contains an invalid instance host", url);
@@ -132,15 +139,7 @@ struct GitArchiveInputScheme : InputScheme
}
}
for (auto & [name, value] : finalAttrs) {
if (name != "type" && name != "owner" && name != "repo" && name != "ref" && name != "rev" && name != "narHash" && name != "lastModified" && name != "host") {
throw Error("unsupported input attribute '%s'", name);
}
}
Input input;
input.attrs = finalAttrs;
return input;
return finalAttrs;
}
ParsedURL toURL(const Input & input) const override
@@ -154,7 +153,7 @@ struct GitArchiveInputScheme : InputScheme
if (ref) path += "/" + *ref;
if (rev) path += "/" + rev->to_string(Base::Base16, false);
return ParsedURL {
.scheme = type(),
.scheme = schemeType(),
.path = path,
};
}
@@ -239,7 +238,7 @@ struct GitArchiveInputScheme : InputScheme
struct GitHubInputScheme : GitArchiveInputScheme
{
std::string type() const override { return "github"; }
std::string schemeType() const override { return "github"; }
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
{
@@ -329,7 +328,7 @@ struct GitHubInputScheme : GitArchiveInputScheme
struct GitLabInputScheme : GitArchiveInputScheme
{
std::string type() const override { return "gitlab"; }
std::string schemeType() const override { return "gitlab"; }
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
{
@@ -405,7 +404,7 @@ struct GitLabInputScheme : GitArchiveInputScheme
struct SourceHutInputScheme : GitArchiveInputScheme
{
std::string type() const override { return "sourcehut"; }
std::string schemeType() const override { return "sourcehut"; }
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
{
+24 -11
View File
@@ -8,8 +8,20 @@ namespace nix::fetchers {
std::regex flakeRegex = regex::parse("[a-zA-Z][a-zA-Z0-9_-]*", std::regex::ECMAScript);
static const std::set<std::string> allowedIndirectAttrs = {
"id",
"ref",
"rev",
};
struct IndirectInputScheme : InputScheme
{
std::string schemeType() const override { return "indirect"; }
const std::set<std::string> & allowedAttrs() const override {
return allowedIndirectAttrs;
}
std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
{
if (url.scheme != "flake") return {};
@@ -47,14 +59,7 @@ struct IndirectInputScheme : InputScheme
return inputFromAttrs(attrs);
}
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
{
if (maybeGetStrAttr(attrs, "type") != "indirect") return {};
for (auto & [name, value] : attrs)
if (name != "type" && name != "id" && name != "ref" && name != "rev" && name != "narHash")
throw Error("unsupported indirect input attribute '%s'", name);
Attrs preprocessAttrs(const Attrs & attrs) const override {
auto id = getStrAttr(attrs, "id");
if (!std::regex_match(id, flakeRegex))
throw BadURL("'%s' is not a valid flake ID", id);
@@ -71,9 +76,17 @@ struct IndirectInputScheme : InputScheme
}
}
Input input;
input.direct = false;
input.attrs = attrs;
return attrs;
}
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
{
std::optional<Input> input = InputScheme::inputFromAttrs(attrs);
if (input) {
input->direct = false;
}
return input;
}
+16 -10
View File
@@ -44,8 +44,22 @@ static std::string runHg(const Strings & args)
return res.second;
}
static const std::set<std::string> allowedMercurialAttrs = {
"name",
"ref",
"rev",
"revCount",
"url",
};
struct MercurialInputScheme : InputScheme
{
std::string schemeType() const override { return "hg"; }
const std::set<std::string> & allowedAttrs() const override {
return allowedMercurialAttrs;
}
std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
{
if (url.scheme != "hg+http" &&
@@ -67,14 +81,8 @@ struct MercurialInputScheme : InputScheme
return inputFromAttrs(attrs);
}
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
Attrs preprocessAttrs(const Attrs & attrs) const override
{
if (maybeGetStrAttr(attrs, "type") != "hg") return {};
for (auto & [name, value] : attrs)
if (name != "type" && name != "url" && name != "ref" && name != "rev" && name != "revCount" && name != "narHash" && name != "name")
throw Error("unsupported Mercurial input attribute '%s'", name);
parseURL(getStrAttr(attrs, "url"));
if (auto ref = maybeGetStrAttr(attrs, "ref")) {
@@ -82,9 +90,7 @@ struct MercurialInputScheme : InputScheme
throw BadURL("invalid Mercurial branch/tag name '%s'", *ref);
}
Input input;
input.attrs = attrs;
return input;
return attrs;
}
ParsedURL toURL(const Input & input) const override
+19 -17
View File
@@ -6,8 +6,25 @@
namespace nix::fetchers {
/* Allow the user to pass in "fake" tree info
attributes. This is useful for making a pinned tree
work the same as the repository from which is exported
(e.g. path:/nix/store/...-source?lastModified=1585388205&rev=b0c285...). */
static const std::set<std::string> allowedPathAttrs = {
"lastModified",
"path",
"rev",
"revCount",
};
struct PathInputScheme : InputScheme
{
std::string schemeType() const override { return "path"; }
const std::set<std::string> & allowedAttrs() const override {
return allowedPathAttrs;
}
std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
{
if (url.scheme != "path") return {};
@@ -34,26 +51,11 @@ struct PathInputScheme : InputScheme
return input;
}
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
Attrs preprocessAttrs(const Attrs & attrs) const override
{
if (maybeGetStrAttr(attrs, "type") != "path") return {};
getStrAttr(attrs, "path");
for (auto & [name, value] : attrs)
/* Allow the user to pass in "fake" tree info
attributes. This is useful for making a pinned tree
work the same as the repository from which is exported
(e.g. path:/nix/store/...-source?lastModified=1585388205&rev=b0c285...). */
if (name == "type" || name == "rev" || name == "revCount" || name == "lastModified" || name == "narHash" || name == "path")
// checked in Input::fromAttrs
;
else
throw Error("unsupported path input attribute '%s'", name);
Input input;
input.attrs = attrs;
return input;
return attrs;
}
bool isLockedByRev() const override { return false; }
+26 -21
View File
@@ -85,9 +85,9 @@ try {
FileTransferResult res;
std::string data;
try {
auto [meta, content] = getFileTransfer()->download(url, headers);
auto [meta, content] = TRY_AWAIT(getFileTransfer()->download(url, headers));
res = std::move(meta);
data = content->drain();
data = TRY_AWAIT(content->drain());
} catch (FileTransferError & e) {
if (cached) {
warn("%s; using cached version", e.msg());
@@ -230,12 +230,25 @@ try {
co_return result::current_exception();
}
// FIXME: some of these only apply to TarballInputScheme.
static const std::set<std::string> allowedCurlAttrs = {
"lastModified",
"name",
"rev",
"revCount",
"unpack",
"url",
};
// An input scheme corresponding to a curl-downloadable resource.
struct CurlInputScheme : InputScheme
{
virtual const std::string inputType() const = 0;
const std::set<std::string> transportUrlSchemes = {"file", "http", "https"};
const std::set<std::string> & allowedAttrs() const override {
return allowedCurlAttrs;
}
bool hasTarballExtension(std::string_view path) const
{
return path.ends_with(".zip") || path.ends_with(".tar")
@@ -254,7 +267,7 @@ struct CurlInputScheme : InputScheme
auto url = _url;
Attrs attrs;
attrs.emplace("type", inputType());
attrs.emplace("type", schemeType());
url.scheme = parseUrlScheme(url.scheme).transport;
@@ -264,24 +277,16 @@ struct CurlInputScheme : InputScheme
return inputFromAttrs(attrs);
}
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
Attrs preprocessAttrs(const Attrs & attrs) const override
{
auto type = maybeGetStrAttr(attrs, "type");
if (type != inputType()) return {};
// FIXME: some of these only apply to TarballInputScheme.
std::set<std::string> allowedNames = {"type", "url", "narHash", "name", "unpack", "rev", "revCount", "lastModified"};
for (auto & [name, value] : attrs)
if (!allowedNames.count(name))
throw Error("unsupported %s input attribute '%s'. If you wanted to fetch a tarball with a query parameter, please use '{ type = \"tarball\"; url = \"...\"; }'", *type, name);
if (name != "type" && name != "narHash" && !allowedAttrs().contains(name))
throw UnsupportedAttributeError("unsupported tarball input attribute '%s'. If you wanted to fetch a tarball with a query parameter, please use '{ type = \"tarball\"; url = \"...\"; }'", name);
Input input;
input.attrs = attrs;
//input.locked = (bool) maybeGetStrAttr(input.attrs, "hash");
return input;
return attrs;
}
ParsedURL toURL(const Input & input) const override
{
auto url = parseURL(getStrAttr(input.attrs, "url"));
@@ -303,14 +308,14 @@ struct CurlInputScheme : InputScheme
struct FileInputScheme : CurlInputScheme
{
const std::string inputType() const override { return "file"; }
std::string schemeType() const override { return "file"; }
bool isValidURL(const ParsedURL & url, bool requireTree) const override
{
auto parsedUrlScheme = parseUrlScheme(url.scheme);
return transportUrlSchemes.count(std::string(parsedUrlScheme.transport))
&& (parsedUrlScheme.application
? parsedUrlScheme.application.value() == inputType()
? parsedUrlScheme.application.value() == schemeType()
: (!requireTree && !hasTarballExtension(url.path)));
}
@@ -328,7 +333,7 @@ struct FileInputScheme : CurlInputScheme
struct TarballInputScheme : CurlInputScheme
{
const std::string inputType() const override { return "tarball"; }
std::string schemeType() const override { return "tarball"; }
bool isValidURL(const ParsedURL & url, bool requireTree) const override
{
@@ -336,7 +341,7 @@ struct TarballInputScheme : CurlInputScheme
return transportUrlSchemes.count(std::string(parsedUrlScheme.transport))
&& (parsedUrlScheme.application
? parsedUrlScheme.application.value() == inputType()
? parsedUrlScheme.application.value() == schemeType()
: (requireTree || hasTarballExtension(url.path)));
}
+2 -28
View File
@@ -1,11 +1,9 @@
#include "lix/libmain/crash-handler.hh"
#include "lix/libutil/error.hh"
#include "lix/libutil/fmt.hh"
#include "lix/libutil/logging.hh"
#include <boost/core/demangle.hpp>
#include <exception>
#include <source_location>
namespace nix {
@@ -23,38 +21,14 @@ void onTerminate()
} else {
logFatal("std::terminate() called without exception");
}
} catch (const ForeignException & ex) {
asyncTrace = ex.asyncTrace();
logFatal(fmt("Exception: %s: %s", boost::core::demangle(ex.innerType.name()), ex.what()));
} catch (const BaseException & ex) {
asyncTrace = ex.asyncTrace();
logFatal(fmt("Exception: %s: %s", boost::core::demangle(typeid(ex).name()), ex.what()));
logException("Exception", ex);
} catch (const std::exception & ex) { // NOLINT(lix-foreign-exceptions)
logFatal(fmt("Exception: %s: %s", boost::core::demangle(typeid(ex).name()), ex.what()));
logException("Exception", ex);
} catch (...) {
logFatal("Unknown exception! Spooky.");
}
logFatal("Stack trace:");
logFatal(getStackTrace());
if (asyncTrace && !asyncTrace->empty()) {
logFatal("Async task trace (probably incomplete):");
for (auto [i, frame] : enumerate(*asyncTrace)) {
logFatal(
fmt("#%i: %s (%s:%i:%i)",
i,
frame.location.function_name(),
frame.location.file_name(),
frame.location.line(),
frame.location.column())
);
if (frame.description) {
logFatal(fmt("\t%s", *frame.description));
}
}
}
std::abort();
}
}
+4 -9
View File
@@ -130,12 +130,6 @@ static void sigHandler(int signo) { }
void initNix()
{
// kj needs a signal for internal use. no system lix habitually runs on causes
// kj to actually *use* this signal, but better safe than sorry—and since some
// OSes (*cough* macos) don't support realtime signals we must use SIGUSR2 for
// this, thus "consuming" both USR signals. at some point we will change this.
kj::UnixEventPort::setReservedSignal(SIGUSR2);
registerCrashHandler();
/* Turn on buffering for cerr. */
@@ -155,9 +149,11 @@ void initNix()
if (sigaction(SIGCHLD, &act, 0))
throw SysError("resetting SIGCHLD");
/* Install a dummy SIGUSR1 handler for use with pthread_kill(). */
/* Install a dummy INTERRUPT_NOTIFY_SIGNAL handler for use with pthread_kill(). */
act.sa_handler = sigHandler;
if (sigaction(SIGUSR1, &act, 0)) throw SysError("handling SIGUSR1");
if (sigaction(INTERRUPT_NOTIFY_SIGNAL, &act, 0)) {
throw SysError("handling interrupt notify signal %i", INTERRUPT_NOTIFY_SIGNAL);
}
#if __APPLE__
/* HACK: on darwin, we need cant use sigprocmask with SIGWINCH.
@@ -191,7 +187,6 @@ void initNix()
umask(0022);
}
LegacyArgs::LegacyArgs(AsyncIoRoot & aio, const std::string & programName,
std::function<bool(Strings::iterator & arg, const Strings::iterator & end)> parseArg)
: MixCommonArgs(programName), aio_(aio), parseArg(parseArg)
+96 -77
View File
@@ -1,6 +1,7 @@
#include "lix/libutil/archive.hh"
#include "lix/libstore/binary-cache-store.hh"
#include "lix/libutil/async-io.hh"
#include "lix/libutil/async.hh"
#include "lix/libutil/compression.hh"
#include "lix/libstore/derivations.hh"
#include "lix/libstore/fs-accessor.hh"
@@ -18,6 +19,7 @@
#include "lix/libutil/strings.hh"
#include <chrono>
#include <functional>
#include <regex>
#include <fstream>
#include <sstream>
@@ -38,9 +40,11 @@ kj::Promise<Result<void>> BinaryCacheStore::init()
try {
std::string cacheInfoFile = "nix-cache-info";
auto cacheInfo = getFileContents(cacheInfoFile);
auto cacheInfo = TRY_AWAIT(getFileContents(cacheInfoFile));
if (!cacheInfo) {
upsertFile(cacheInfoFile, "StoreDir: " + config().storeDir + "\n", "text/x-nix-cache-info");
TRY_AWAIT(upsertFile(
cacheInfoFile, "StoreDir: " + config().storeDir + "\n", "text/x-nix-cache-info"
));
} else {
for (auto & line : tokenizeString<Strings>(*cacheInfo, "\n")) {
size_t colon= line.find(':');
@@ -63,20 +67,26 @@ try {
co_return result::current_exception();
}
void BinaryCacheStore::upsertFile(const std::string & path,
std::string && data,
const std::string & mimeType)
{
upsertFile(path, std::make_shared<std::stringstream>(std::move(data)), mimeType);
kj::Promise<Result<void>> BinaryCacheStore::upsertFile(
const std::string & path, std::string && data, const std::string & mimeType
)
try {
TRY_AWAIT(upsertFile(path, std::make_shared<std::stringstream>(std::move(data)), mimeType));
co_return result::success();
} catch (...) {
co_return result::current_exception();
}
std::optional<std::string> BinaryCacheStore::getFileContents(const std::string & path)
{
kj::Promise<Result<std::optional<std::string>>>
BinaryCacheStore::getFileContents(const std::string & path)
try {
try {
return getFile(path)->drain();
co_return TRY_AWAIT(TRY_AWAIT(getFile(path))->drain());
} catch (NoSuchBinaryCacheFile &) {
return std::nullopt;
co_return std::nullopt;
}
} catch (...) {
co_return result::current_exception();
}
std::string BinaryCacheStore::narInfoFileFor(const StorePath & storePath)
@@ -88,7 +98,7 @@ kj::Promise<Result<void>> BinaryCacheStore::writeNarInfo(ref<NarInfo> narInfo)
try {
auto narInfoFile = narInfoFileFor(narInfo->path);
upsertFile(narInfoFile, narInfo->to_string(*this), "text/x-nix-narinfo");
TRY_AWAIT(upsertFile(narInfoFile, narInfo->to_string(*this), "text/x-nix-narinfo"));
{
auto state_(co_await state.lock());
@@ -178,7 +188,21 @@ try {
{"root", listNar(narIndex)},
};
upsertFile(std::string(info.path.hashPart()) + ".ls", j.dump(), "application/json");
try {
TRY_AWAIT(
upsertFile(std::string(info.path.hashPart()) + ".ls", j.dump(), "application/json")
);
} catch (ForeignException & exc) {
if (exc.is<JSON::exception>()) {
warn(
"Skipping NAR listing for path '%1%' due to serialization failure: %2%",
printStorePath(narInfo->path),
exc.what()
);
} else {
throw exc;
}
}
}
/* Optionally maintain an index of DWARF debug info files
@@ -190,7 +214,6 @@ try {
auto * buildIdDir = std::get_if<nar_index::Directory>(&narIndex);
for (auto subdir : { "lib", "debug", ".build-id" }) {
if (!buildIdDir) break;
// get returns nullptr subdir does not exist, and std::get_if propagates it.
buildIdDir = std::get_if<nar_index::Directory>(get(buildIdDir->contents, subdir));
}
@@ -199,20 +222,23 @@ try {
ThreadPool threadPool("write debuginfo pool", 25);
auto doFile = [&](std::string member, std::string key, std::string target) {
checkInterrupt();
auto doFile = [&](AsyncIoRoot & aio,
std::string member,
std::string key,
std::string target) {
JSON json;
json["archive"] = target;
json["member"] = member;
// FIXME: or should we overwrite? The previous link may point
// to a GC'ed file, so overwriting might be useful...
if (fileExists(key)) return;
if (aio.blockOn(fileExists(key))) {
return;
}
printMsg(lvlTalkative, "creating debuginfo link from '%s' to '%s'", key, target);
upsertFile(key, json.dump(), "application/json");
aio.blockOn(upsertFile(key, json.dump(), "application/json"));
};
std::regex regex1 = regex::parse("^[0-9a-f]{2}$");
@@ -236,7 +262,9 @@ try {
std::string key = "debuginfo/" + buildId;
std::string target = "../" + narInfo->url;
threadPool.enqueue(std::bind(doFile, std::string(debugPath, 1), key, target));
threadPool.enqueueWithAio(std::bind(
doFile, std::placeholders::_1, std::string(debugPath, 1), key, target
));
}
}
@@ -245,13 +273,16 @@ try {
}
/* Atomically write the NAR file. */
if (repair || !fileExists(narInfo->url)) {
if (repair || !TRY_AWAIT(fileExists(narInfo->url))) {
stats.narWrite++;
upsertFile(narInfo->url,
TRY_AWAIT(upsertFile(
narInfo->url,
std::make_shared<std::fstream>(fnTemp, std::ios_base::in | std::ios_base::binary),
"application/x-nix-nar");
} else
"application/x-nix-nar"
));
} else {
stats.narWriteAverted++;
}
stats.narWriteBytes += info.narSize;
stats.narWriteCompressedBytes += fileSize;
@@ -332,7 +363,7 @@ try {
// FIXME: this only checks whether a .narinfo with a matching hash
// part exists. So f4kb...-foo matches f4kb...-bar, even
// though they shouldn't. Not easily fixed.
co_return fileExists(narInfoFileFor(storePath));
co_return TRY_AWAIT(fileExists(narInfoFileFor(storePath)));
} catch (...) {
co_return result::current_exception();
}
@@ -351,34 +382,47 @@ try {
co_return result::current_exception();
}
kj::Promise<Result<box_ptr<Source>>> BinaryCacheStore::narFromPath(const StorePath & storePath)
kj::Promise<Result<box_ptr<AsyncInputStream>>>
BinaryCacheStore::narFromPath(const StorePath & storePath)
try {
struct NarFromPath : AsyncInputStream
{
Stats<std::atomic> & stats;
box_ptr<AsyncInputStream> decompressed;
uint64_t total;
NarFromPath(
Stats<std::atomic> & stats, const std::string & method, box_ptr<AsyncInputStream> file
)
: stats(stats)
, decompressed(makeDecompressionStream(method, std::move(file)))
{
}
kj::Promise<Result<size_t>> read(void * buffer, size_t size) override
{
return decompressed->read(buffer, size).then([&](auto r) {
if (r.has_value()) {
if (r.value() > 0) {
total += r.value();
} else {
stats.narRead++;
// stats.narReadCompressedBytes += nar->size(); // FIXME
stats.narReadBytes += total;
}
}
return r;
});
}
};
auto info_ = TRY_AWAIT(queryPathInfo(storePath)).try_cast<const NarInfo>();
assert(info_ && "binary cache queryPathInfo didn't return a NarInfo");
auto & info = *info_;
try {
auto file = getFile(info->url);
co_return make_box_ptr<GeneratorSource>(
[](auto info, auto file, auto & stats) -> WireFormatGenerator {
constexpr size_t buflen = 65536;
auto buf = std::make_unique<char[]>(buflen);
size_t total = 0;
auto decompressor = makeDecompressionSource(info->compression, *file);
try {
while (true) {
const auto len = decompressor->read(buf.get(), buflen);
co_yield std::span{buf.get(), len};
total += len;
}
} catch (EndOfFile &) {
}
stats.narRead++;
// stats.narReadCompressedBytes += nar->size(); // FIXME
stats.narReadBytes += total;
}(std::move(info), std::move(file), stats)
);
auto file = TRY_AWAIT(getFile(info->url));
co_return make_box_ptr<NarFromPath>(stats, info->compression, std::move(file));
} catch (NoSuchBinaryCacheFile & e) {
throw SubstituteGone(std::move(e.info()));
}
@@ -397,7 +441,7 @@ try {
auto narInfoFile = narInfoFileFor(storePath);
auto data = getFileContents(narInfoFile);
auto data = TRY_AWAIT(getFileContents(narInfoFile));
if (!data) co_return result::success(nullptr);
@@ -506,32 +550,6 @@ try {
co_return result::current_exception();
}
kj::Promise<Result<std::shared_ptr<const Realisation>>>
BinaryCacheStore::queryRealisationUncached(const DrvOutput & id)
try {
auto outputInfoFilePath = realisationsPrefix + "/" + id.to_string() + ".doi";
auto data = getFileContents(outputInfoFilePath);
if (!data) co_return result::success(nullptr);
auto realisation = Realisation::fromJSON(
json::parse(*data), outputInfoFilePath);
co_return std::make_shared<const Realisation>(realisation);
} catch (...) {
co_return result::current_exception();
}
kj::Promise<Result<void>> BinaryCacheStore::registerDrvOutput(const Realisation& info)
try {
if (diskCache)
diskCache->upsertRealisation(getUri(), info);
auto filePath = realisationsPrefix + "/" + info.id.to_string() + ".doi";
upsertFile(filePath, info.toJSON().dump(), "application/json");
co_return result::success();
} catch (...) {
co_return result::current_exception();
}
ref<FSAccessor> BinaryCacheStore::getFSAccessor()
{
return make_ref<RemoteFSAccessor>(ref<Store>(*this), config().localNarCache);
@@ -564,7 +582,7 @@ try {
debug("fetching build log from binary cache '%s/%s'", getUri(), logPath);
co_return getFileContents(logPath);
co_return TRY_AWAIT(getFileContents(logPath));
} catch (...) {
co_return result::current_exception();
}
@@ -574,10 +592,11 @@ BinaryCacheStore::addBuildLog(const StorePath & drvPath, std::string_view log)
try {
assert(drvPath.isDerivation());
upsertFile(
TRY_AWAIT(upsertFile(
"log/" + std::string(drvPath.to_string()),
(std::string) log, // FIXME: don't copy
"text/plain; charset=utf-8");
"text/plain; charset=utf-8"
));
co_return result::success();
} catch (...) {
co_return result::current_exception();
+13 -13
View File
@@ -73,23 +73,28 @@ public:
BinaryCacheStoreConfig & config() override = 0;
const BinaryCacheStoreConfig & config() const override = 0;
virtual bool fileExists(const std::string & path) = 0;
virtual kj::Promise<Result<bool>> fileExists(const std::string & path) = 0;
virtual void upsertFile(const std::string & path,
virtual kj::Promise<Result<void>> upsertFile(
const std::string & path,
std::shared_ptr<std::basic_iostream<char>> istream,
const std::string & mimeType) = 0;
const std::string & mimeType
) = 0;
void upsertFile(const std::string & path,
kj::Promise<Result<void>> upsertFile(
const std::string & path,
// FIXME: use std::string_view
std::string && data,
const std::string & mimeType);
const std::string & mimeType
);
/**
* Dump the contents of the specified file to a sink.
*/
virtual box_ptr<Source> getFile(const std::string & path) = 0;
virtual kj::Promise<Result<box_ptr<AsyncInputStream>>> getFile(const std::string & path) = 0;
virtual std::optional<std::string> getFileContents(const std::string & path);
virtual kj::Promise<Result<std::optional<std::string>>> getFileContents(const std::string & path
);
public:
@@ -146,12 +151,7 @@ public:
const StorePathSet & references,
RepairFlag repair) override;
kj::Promise<Result<void>> registerDrvOutput(const Realisation & info) override;
kj::Promise<Result<std::shared_ptr<const Realisation>>>
queryRealisationUncached(const DrvOutput &) override;
kj::Promise<Result<box_ptr<Source>>> narFromPath(const StorePath & path) override;
kj::Promise<Result<box_ptr<AsyncInputStream>>> narFromPath(const StorePath & path) override;
ref<FSAccessor> getFSAccessor() override;
+55 -315
View File
@@ -1,5 +1,6 @@
#include "lix/libstore/build/derivation-goal.hh"
#include "lix/libutil/async.hh"
#include "lix/libutil/file-descriptor.hh"
#include "lix/libutil/file-system.hh"
#include "lix/libstore/build/hook-instance.hh"
#include "lix/libstore/build/worker.hh"
@@ -10,7 +11,6 @@
#include "lix/libstore/common-protocol-impl.hh" // IWYU pragma: keep
#include "lix/libstore/local-store.hh" // TODO remove, along with remaining downcasts
#include "lix/libstore/build/substitution-goal.hh"
#include "lix/libstore/build/drv-output-substitution-goal.hh"
#include "lix/libutil/result.hh"
#include "lix/libutil/strings.hh"
@@ -74,7 +74,7 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath,
{
name = fmt(
"building of '%s' from .drv file",
DerivedPath::Built { makeConstantStorePathRef(drvPath), wantedOutputs }.to_string(worker.store));
DerivedPath::Built { makeConstantStorePath(drvPath), wantedOutputs }.to_string(worker.store));
trace("created");
mcExpectedBuilds = worker.expectedBuilds.addTemporarily(1);
@@ -93,7 +93,7 @@ DerivationGoal::DerivationGoal(DrvHasRoot, const StorePath & drvPath, const Basi
name = fmt(
"building of '%s' from in-memory derivation",
DerivedPath::Built { makeConstantStorePathRef(drvPath), drv.outputNames() }.to_string(worker.store));
DerivedPath::Built { makeConstantStorePath(drvPath), drv.outputNames() }.to_string(worker.store));
trace("created");
mcExpectedBuilds = worker.expectedBuilds.addTemporarily(1);
@@ -199,8 +199,6 @@ try {
/* Get the derivation. It is probably in the eval store, but it might be inthe main store:
- Resolved derivation are resolved against main store realisations, and so must be stored there.
- Dynamic derivations are built, and so are found in the main store.
*/
for (auto * drvStore : { &worker.evalStore, &worker.store }) {
if (TRY_AWAIT(drvStore->isValidPath(drvPath))) {
@@ -222,34 +220,8 @@ try {
parsedDrv = std::make_unique<ParsedDerivation>(drvPath, *drv);
if (!drv->type().hasKnownOutputPaths())
experimentalFeatureSettings.require(Xp::CaDerivations);
if (!drv->type().isPure()) {
experimentalFeatureSettings.require(Xp::ImpureDerivations);
for (auto & [outputName, output] : drv->outputs) {
auto randomPath = StorePath::random(outputPathName(drv->name, outputName));
assert(!TRY_AWAIT(worker.store.isValidPath(randomPath)));
initialOutputs.insert({
outputName,
InitialOutput {
.wanted = true,
.outputHash = impureOutputHash,
.known = InitialOutputStatus {
.path = randomPath,
.status = PathStatus::Absent
}
}
});
}
co_return co_await gaveUpOnSubstitution();
}
for (auto & i : drv->outputsAndOptPaths(worker.store))
if (i.second.second)
TRY_AWAIT(worker.store.addTempRoot(*i.second.second));
for (auto & i : drv->outputsAndPaths(worker.store))
TRY_AWAIT(worker.store.addTempRoot(i.second.second));
auto outputHashes = TRY_AWAIT(staticOutputHashes(worker.evalStore, *drv));
for (auto & [outputName, outputHash] : outputHashes)
@@ -277,14 +249,13 @@ try {
if (parsedDrv->substitutesAllowed()) {
for (auto & [outputName, status] : initialOutputs) {
if (!status.wanted) continue;
if (!status.known)
dependencies.add(
worker.goalFactory().makeDrvOutputSubstitutionGoal(
DrvOutput{status.outputHash, outputName},
buildMode == bmRepair ? Repair : NoRepair
)
if (!status.known) {
// TODO remove somehow
throw Error(
"congrats, you hit vestigial CA code. sigh.\n"
"please report a bug at https://git.lix.systems/lix-project/lix/issues"
);
else {
} else {
auto * cap = getDerivationCA(*drv);
dependencies.add(worker.goalFactory().makePathSubstitutionGoal(
status.known->path,
@@ -309,8 +280,6 @@ kj::Promise<Result<Goal::WorkResult>> DerivationGoal::outputsSubstitutionTried()
try {
trace("all outputs substituted (maybe)");
assert(drv->type().isPure());
if (nrFailed > 0 && nrFailed > nrNoSubstituters + nrIncompleteClosure && !settings.tryFallback)
{
co_return done(
@@ -397,34 +366,18 @@ try {
/* The inputs must be built before we can build this goal. */
inputDrvOutputs.clear();
if (useDerivation) {
std::function<void(ref<SingleDerivedPath>, const DerivedPathMap<StringSet>::ChildNode &)> addWaiteeDerivedPath;
addWaiteeDerivedPath = [&](ref<SingleDerivedPath> inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
if (!inputNode.value.empty())
auto addWaiteeDerivedPath = [&](DerivedPathOpaque inputDrv, const StringSet & inputNode) {
if (!inputNode.empty())
dependencies.add(worker.goalFactory().makeGoal(
DerivedPath::Built {
.drvPath = inputDrv,
.outputs = inputNode.value,
.outputs = inputNode,
},
buildMode == bmRepair ? bmRepair : bmNormal));
for (const auto & [outputName, childNode] : inputNode.childMap)
addWaiteeDerivedPath(
make_ref<SingleDerivedPath>(SingleDerivedPath::Built { inputDrv, outputName }),
childNode);
};
for (const auto & [inputDrvPath, inputNode] : dynamic_cast<Derivation *>(drv.get())->inputDrvs.map) {
/* Ensure that pure, non-fixed-output derivations don't
depend on impure derivations. */
if (experimentalFeatureSettings.isEnabled(Xp::ImpureDerivations) && drv->type().isPure() && !drv->type().isFixed()) {
auto inputDrv = TRY_AWAIT(worker.evalStore.readDerivation(inputDrvPath));
if (!inputDrv.type().isPure())
throw Error("pure derivation '%s' depends on impure derivation '%s'",
worker.store.printStorePath(drvPath),
worker.store.printStorePath(inputDrvPath));
}
addWaiteeDerivedPath(makeConstantStorePathRef(inputDrvPath), inputNode);
for (const auto & [inputDrvPath, inputNode] : dynamic_cast<Derivation *>(drv.get())->inputDrvs) {
addWaiteeDerivedPath(makeConstantStorePath(inputDrvPath), inputNode);
}
}
@@ -460,8 +413,6 @@ try {
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::repairClosure() noexcept
try {
assert(drv->type().isPure());
/* If we're repairing, we now know that our own outputs are valid.
Now check whether the other paths in the outputs closure are
good. If not, then start derivation goals for the derivations
@@ -488,10 +439,9 @@ try {
for (auto & i : inputClosure)
if (i.isDerivation()) {
auto depOutputs =
TRY_AWAIT(worker.store.queryPartialDerivationOutputMap(i, &worker.evalStore));
TRY_AWAIT(worker.store.queryDerivationOutputMap(i, &worker.evalStore));
for (auto & j : depOutputs)
if (j.second)
outputsToDrv.insert_or_assign(*j.second, i);
outputsToDrv.insert_or_assign(j.second, i);
}
/* Check each path (slow!). */
@@ -507,7 +457,7 @@ try {
else
dependencies.add(worker.goalFactory().makeGoal(
DerivedPath::Built {
.drvPath = makeConstantStorePathRef(drvPath2->second),
.drvPath = makeConstantStorePath(drvPath2->second),
.outputs = OutputsSpec::All { },
},
bmRepair));
@@ -572,68 +522,8 @@ try {
if (useDerivation) {
auto & fullDrv = *dynamic_cast<Derivation *>(drv.get());
auto drvType = fullDrv.type();
bool resolveDrv = std::visit(overloaded {
[&](const DerivationType::InputAddressed & ia) {
/* must resolve if deferred. */
return ia.deferred;
},
[&](const DerivationType::ContentAddressed & ca) {
return !fullDrv.inputDrvs.map.empty() && (
ca.fixed
/* Can optionally resolve if fixed, which is good
for avoiding unnecessary rebuilds. */
? experimentalFeatureSettings.isEnabled(Xp::CaDerivations)
/* Must resolve if floating and there are any inputs
drvs. */
: true);
},
[&](const DerivationType::Impure &) {
return true;
}
}, drvType.raw);
if (resolveDrv && !fullDrv.inputDrvs.map.empty()) {
experimentalFeatureSettings.require(Xp::CaDerivations);
/* We are be able to resolve this derivation based on the
now-known results of dependencies. If so, we become a
stub goal aliasing that resolved derivation goal. */
std::optional attempt = TRY_AWAIT(fullDrv.tryResolve(worker.store, inputDrvOutputs));
if (!attempt) {
/* TODO (impure derivations-induced tech debt) (see below):
The above attempt should have found it, but because we manage
inputDrvOutputs statefully, sometimes it gets out of sync with
the real source of truth (store). So we query the store
directly if there's a problem. */
attempt = TRY_AWAIT(fullDrv.tryResolve(worker.store, &worker.evalStore));
}
assert(attempt);
Derivation drvResolved { std::move(*attempt) };
auto pathResolved = TRY_AWAIT(writeDerivation(worker.store, drvResolved));
auto msg = fmt("resolved derivation: '%s' -> '%s'",
worker.store.printStorePath(drvPath),
worker.store.printStorePath(pathResolved));
act = std::make_unique<Activity>(*logger, lvlInfo, actBuildWaiting, msg,
Logger::Fields {
worker.store.printStorePath(drvPath),
worker.store.printStorePath(pathResolved),
});
auto dependency = worker.goalFactory().makeDerivationGoal(
pathResolved, wantedOutputs, buildMode);
resolvedDrvGoal = dependency.first;
TRY_AWAIT(waitForGoals(std::move(dependency)));
co_return co_await resolvedFinished();
}
std::function<kj::Promise<Result<void>>(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accumInputPaths;
// NOLINTNEXTLINE(cppcoreguidelines-avoid-capturing-lambda-coroutines)
accumInputPaths = [&](const StorePath & depDrvPath, const DerivedPathMap<StringSet>::ChildNode & inputNode) -> kj::Promise<Result<void>> {
auto accumInputPaths = [&](const StorePath & depDrvPath, const StringSet & inputNode) -> kj::Promise<Result<void>> {
try {
/* Add the relevant output closures of the input derivation
`i' as input paths. Only add the closures of output paths
@@ -676,21 +566,19 @@ try {
}
};
for (auto & outputName : inputNode.value) {
for (auto & outputName : inputNode) {
TRY_AWAIT(
worker.store.computeFSClosure(TRY_AWAIT(getOutput(outputName)), inputPaths)
);
}
for (auto & [outputName, childNode] : inputNode.childMap)
TRY_AWAIT(accumInputPaths(TRY_AWAIT(getOutput(outputName)), childNode));
co_return result::success();
} catch (...) {
co_return result::current_exception();
}
};
for (auto & [depDrvPath, depNode] : fullDrv.inputDrvs.map)
for (auto & [depDrvPath, depNode] : fullDrv.inputDrvs)
TRY_AWAIT(accumInputPaths(depDrvPath, depNode));
}
@@ -735,8 +623,6 @@ retry:
other goal can start a build, and if not, the main loop will sleep a few
seconds and then retry this goal. */
PathSet lockFiles;
/* FIXME: Should lock something like the drv itself so we don't build same
CA drv concurrently */
if (dynamic_cast<LocalStore *>(&worker.store)) {
/* If we aren't a local store, we might need to use the local store as
a build remote, but that would cause a deadlock. */
@@ -745,13 +631,8 @@ retry:
/* FIXME: find some way to lock for scheduling for the other stores so
a forking daemon with --store still won't farm out redundant builds.
*/
for (auto & i : drv->outputsAndOptPaths(worker.store)) {
if (i.second.second)
lockFiles.insert(worker.store.Store::toRealPath(*i.second.second));
else
lockFiles.insert(
worker.store.Store::toRealPath(drvPath) + "." + i.first
);
for (auto & i : drv->outputsAndPaths(worker.store)) {
lockFiles.insert(worker.store.Store::toRealPath(i.second.second));
}
}
@@ -885,7 +766,11 @@ void replaceValidPath(const Path & storePath, const Path & tmpPath)
we're repairing (say) Glibc, we end up with a broken system. */
Path oldPath;
if (pathExists(storePath)) {
oldPath = makeTempSiblingPath(storePath);
do {
oldPath = makeTempPath(storePath, ".old");
// store paths are often directories so we can't just unlink() it
// let's make sure the path doesn't exist before we try to use it
} while (pathExists(oldPath));
movePath(storePath, oldPath);
}
@@ -1007,7 +892,7 @@ void runPostBuildHook(
.program = settings.postBuildHook,
.environment = hookEnvironment,
.captureStdout = true,
.redirections = {{.from = STDERR_FILENO, .to = STDOUT_FILENO}},
.redirections = {{.dup = STDERR_FILENO, .from = STDOUT_FILENO}},
});
Finally const _wait([&] {
try {
@@ -1145,88 +1030,6 @@ try {
co_return result::current_exception();
}
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::resolvedFinished() noexcept
try {
trace("resolved derivation finished");
assert(resolvedDrvGoal);
auto resolvedDrv = *resolvedDrvGoal->drv;
auto & resolvedResult = resolvedDrvGoal->buildResult;
SingleDrvOutputs builtOutputs;
if (resolvedResult.success()) {
auto resolvedHashes = TRY_AWAIT(staticOutputHashes(worker.store, resolvedDrv));
StorePathSet outputPaths;
for (auto & outputName : resolvedDrv.outputNames()) {
auto initialOutput = get(initialOutputs, outputName);
auto resolvedHash = get(resolvedHashes, outputName);
if ((!initialOutput) || (!resolvedHash))
throw Error(
"derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/resolvedFinished,resolve)",
worker.store.printStorePath(drvPath), outputName);
// NOLINTNEXTLINE(cppcoreguidelines-avoid-capturing-lambda-coroutines)
auto realisation = TRY_AWAIT([&]() -> kj::Promise<Result<Realisation>> {
try {
auto take1 = get(resolvedResult.builtOutputs, outputName);
if (take1) co_return *take1;
/* The above `get` should work. But sateful tracking of
outputs in resolvedResult, this can get out of sync with the
store, which is our actual source of truth. For now we just
check the store directly if it fails. */
auto take2 = TRY_AWAIT(
worker.evalStore.queryRealisation(DrvOutput{*resolvedHash, outputName})
);
if (take2) co_return *take2;
throw Error(
"derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/resolvedFinished,realisation)",
worker.store.printStorePath(resolvedDrvGoal->drvPath), outputName);
} catch (...) {
co_return result::current_exception();
}
}());
if (drv->type().isPure()) {
auto newRealisation = realisation;
newRealisation.id = DrvOutput { initialOutput->outputHash, outputName };
newRealisation.signatures.clear();
if (!drv->type().isFixed()) {
auto & drvStore = TRY_AWAIT(worker.evalStore.isValidPath(drvPath))
? worker.evalStore
: worker.store;
newRealisation.dependentRealisations = TRY_AWAIT(
drvOutputReferences(worker.store, *drv, realisation.outPath, &drvStore)
);
}
signRealisation(newRealisation);
TRY_AWAIT(worker.store.registerDrvOutput(newRealisation));
}
outputPaths.insert(realisation.outPath);
builtOutputs.emplace(outputName, realisation);
}
runPostBuildHook(
worker.store,
*logger,
drvPath,
outputPaths
);
}
auto status = resolvedResult.status;
if (status == BuildResult::AlreadyValid)
status = BuildResult::ResolvesToAlreadyValid;
co_return done(status, std::move(builtOutputs));
} catch (...) {
co_return result::current_exception();
}
HookReply DerivationGoal::tryBuildHook()
{
if (!worker.hook.available || !useDerivation) return HookReply::Decline{};
@@ -1237,13 +1040,10 @@ HookReply DerivationGoal::tryBuildHook()
try {
/* Send the request to the hook. */
worker.hook.instance->sink
<< "try"
<< (slotToken.valid() ? 1 : 0)
<< drv->platform
<< worker.store.printStorePath(drvPath)
<< parsedDrv->getRequiredSystemFeatures();
worker.hook.instance->sink.flush();
*worker.hook.instance->sink << "try" << (slotToken.valid() ? 1 : 0) << drv->platform
<< worker.store.printStorePath(drvPath)
<< parsedDrv->getRequiredSystemFeatures();
worker.hook.instance->sink->flush();
/* Read the first line of input, which should be a word indicating
whether the hook wishes to perform the build. */
@@ -1306,7 +1106,7 @@ HookReply DerivationGoal::tryBuildHook()
/* Tell the hook all the inputs that have to be copied to the
remote system. */
hook->sink << CommonProto::write(worker.store, {}, inputPaths);
*hook->sink << CommonProto::write({worker.store}, inputPaths);
/* Tell the hooks the missing outputs that have to be copied back
from the remote system. */
@@ -1317,10 +1117,10 @@ HookReply DerivationGoal::tryBuildHook()
if (buildMode != bmCheck && status.known && status.known->isValid()) continue;
missingOutputs.insert(outputName);
}
hook->sink << CommonProto::write(worker.store, {}, missingOutputs);
*hook->sink << CommonProto::write({worker.store}, missingOutputs);
}
hook->sink = FdSink();
hook->sink = nullptr;
hook->toHook.reset();
/* Create the log file and pipe. */
@@ -1405,13 +1205,7 @@ struct DerivationGoal::InputStream final : private kj::AsyncObject
: fd(fd)
, observer(ep, fd, kj::UnixEventPort::FdObserver::OBSERVE_READ)
{
int flags = fcntl(fd, F_GETFL);
if (flags < 0) {
throw SysError("fcntl(F_GETFL) failed on fd %i", fd);
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
throw SysError("fcntl(F_SETFL) failed on fd %i", fd);
}
makeNonBlocking(fd);
}
kj::Promise<std::string_view> read(kj::ArrayPtr<char> buffer)
@@ -1614,43 +1408,12 @@ void DerivationGoal::flushLine()
}
kj::Promise<Result<std::map<std::string, std::optional<StorePath>>>> DerivationGoal::queryPartialDerivationOutputMap()
try {
assert(drv->type().isPure());
if (!useDerivation || drv->type().hasKnownOutputPaths()) {
std::map<std::string, std::optional<StorePath>> res;
for (auto & [name, output] : drv->outputs)
res.insert_or_assign(name, output.path(worker.store, drv->name, name));
co_return res;
} else {
for (auto * drvStore : {&worker.evalStore, &worker.store}) {
if (TRY_AWAIT(drvStore->isValidPath(drvPath))) {
co_return TRY_AWAIT(worker.store.queryPartialDerivationOutputMap(drvPath, drvStore)
);
}
}
assert(false);
}
} catch (...) {
co_return result::current_exception();
}
kj::Promise<Result<OutputPathMap>> DerivationGoal::queryDerivationOutputMap()
try {
assert(drv->type().isPure());
if (!useDerivation || drv->type().hasKnownOutputPaths()) {
OutputPathMap res;
for (auto & [name, output] : drv->outputsAndOptPaths(worker.store))
res.insert_or_assign(name, *output.second);
co_return res;
} else {
for (auto * drvStore : {&worker.evalStore, &worker.store}) {
if (TRY_AWAIT(drvStore->isValidPath(drvPath))) {
co_return TRY_AWAIT(worker.store.queryDerivationOutputMap(drvPath, drvStore));
}
}
assert(false);
}
OutputPathMap res;
for (auto & [name, output] : drv->outputsAndPaths(worker.store))
res.insert_or_assign(name, output.second);
co_return res;
} catch (...) {
co_return result::current_exception();
}
@@ -1658,8 +1421,6 @@ try {
kj::Promise<Result<std::pair<bool, SingleDrvOutputs>>> DerivationGoal::checkPathValidity()
try {
if (!drv->type().isPure()) co_return { false, SingleDrvOutputs{} };
bool checkHash = buildMode == bmRepair;
auto wantedOutputsLeft = std::visit(overloaded {
[&](const OutputsSpec::All &) {
@@ -1671,7 +1432,7 @@ try {
}, wantedOutputs.raw);
SingleDrvOutputs validOutputs;
for (auto & i : TRY_AWAIT(queryPartialDerivationOutputMap())) {
for (auto & i : TRY_AWAIT(queryDerivationOutputMap())) {
auto initialOutput = get(initialOutputs, i.first);
if (!initialOutput)
// this is an invalid output, gets catched with (!wantedOutputsLeft.empty())
@@ -1680,37 +1441,16 @@ try {
info.wanted = wantedOutputs.contains(i.first);
if (info.wanted)
wantedOutputsLeft.erase(i.first);
if (i.second) {
auto outputPath = *i.second;
info.known = {
.path = outputPath,
.status = !TRY_AWAIT(worker.store.isValidPath(outputPath))
? PathStatus::Absent
: !checkHash || TRY_AWAIT(worker.pathContentsGood(outputPath))
? PathStatus::Valid
: PathStatus::Corrupt,
};
}
auto & outputPath = i.second;
info.known = {
.path = outputPath,
.status = !TRY_AWAIT(worker.store.isValidPath(outputPath))
? PathStatus::Absent
: !checkHash || TRY_AWAIT(worker.pathContentsGood(outputPath))
? PathStatus::Valid
: PathStatus::Corrupt,
};
auto drvOutput = DrvOutput{info.outputHash, i.first};
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
if (auto real = TRY_AWAIT(worker.store.queryRealisation(drvOutput))) {
info.known = {
.path = real->outPath,
.status = PathStatus::Valid,
};
} else if (info.known && info.known->isValid()) {
// We know the output because it's a static output of the
// derivation, and the output path is valid, but we don't have
// its realisation stored (probably because it has been built
// without the `ca-derivations` experimental flag).
TRY_AWAIT(worker.store.registerDrvOutput(
Realisation {
drvOutput,
info.known->path,
}
));
}
}
if (info.known && info.known->isValid())
validOutputs.emplace(i.first, Realisation { drvOutput, info.known->path });
}
@@ -1807,13 +1547,13 @@ void DerivationGoal::waiteeDone(GoalPtr waitee)
auto & fullDrv = *dynamic_cast<Derivation *>(drv.get());
auto * nodeP = fullDrv.inputDrvs.findSlot(DerivedPath::Opaque { .path = dg->drvPath });
auto * nodeP = get(fullDrv.inputDrvs, dg->drvPath);
if (!nodeP) return;
auto & outputs = nodeP->value;
auto & outputs = *nodeP;
for (auto & outputName : outputs) {
auto buildResult = dg->buildResult.restrictTo(DerivedPath::Built {
.drvPath = makeConstantStorePathRef(dg->drvPath),
.drvPath = makeConstantStorePath(dg->drvPath),
.outputs = OutputsSpec::Names { outputName },
});
if (buildResult.success()) {
+1 -16
View File
@@ -87,11 +87,6 @@ struct DerivationGoal : public Goal
/** The path of the derivation. */
StorePath drvPath;
/**
* The goal for the corresponding resolved derivation
*/
std::shared_ptr<DerivationGoal> resolvedDrvGoal;
/**
* The specific outputs that we need to build.
*/
@@ -279,8 +274,6 @@ struct DerivationGoal : public Goal
virtual kj::Promise<Result<WorkResult>> tryLocalBuild() noexcept;
kj::Promise<Result<WorkResult>> buildDone() noexcept;
kj::Promise<Result<WorkResult>> resolvedFinished() noexcept;
/**
* Is the build hook willing to perform the build?
*/
@@ -299,11 +292,6 @@ struct DerivationGoal : public Goal
*/
Path openLogFile();
/**
* Sign the newly built realisation if the store allows it
*/
virtual void signRealisation(Realisation&) {}
/**
* Close the log file.
*/
@@ -338,11 +326,10 @@ protected:
public:
/**
* Wrappers around the corresponding Store methods that first consult the
* Wrappers around the corresponding Store method that first consults the
* derivation. This is currently needed because when there is no drv file
* there also is no DB entry.
*/
kj::Promise<Result<std::map<std::string, std::optional<StorePath>>>> queryPartialDerivationOutputMap();
kj::Promise<Result<OutputPathMap>> queryDerivationOutputMap();
/**
@@ -380,8 +367,6 @@ public:
return false;
}
StorePathSet exportReferences(const StorePathSet & storePaths);
JobCategory jobCategory() const override {
return JobCategory::Build;
};
@@ -1,164 +0,0 @@
#include "lix/libstore/build/drv-output-substitution-goal.hh"
#include "lix/libstore/build-result.hh"
#include "lix/libutil/async.hh"
#include "lix/libutil/finally.hh"
#include "lix/libstore/build/worker.hh"
#include "lix/libstore/build/substitution-goal.hh"
#include "lix/libutil/signals.hh"
#include <kj/array.h>
#include <kj/async.h>
#include <kj/vector.h>
namespace nix {
DrvOutputSubstitutionGoal::DrvOutputSubstitutionGoal(
const DrvOutput & id,
Worker & worker,
bool isDependency,
RepairFlag repair,
std::optional<ContentAddress> ca)
: Goal(worker, isDependency)
, id(id)
{
name = fmt("substitution of '%s'", id.to_string());
trace("created");
}
kj::Promise<Result<Goal::WorkResult>> DrvOutputSubstitutionGoal::workImpl() noexcept
try {
trace("init");
/* If the derivation already exists, were done */
if (TRY_AWAIT(worker.store.queryRealisation(id))) {
co_return WorkResult{ecSuccess};
}
subs = settings.useSubstitutes ? TRY_AWAIT(getDefaultSubstituters()) : std::list<ref<Store>>();
co_return co_await tryNext();
} catch (...) {
co_return result::current_exception();
}
kj::Promise<Result<Goal::WorkResult>> DrvOutputSubstitutionGoal::tryNext() noexcept
try {
trace("trying next substituter");
if (!slotToken.valid()) {
slotToken = co_await worker.substitutions.acquire();
}
maintainRunningSubstitutions = worker.runningSubstitutions.addTemporarily(1);
if (subs.size() == 0) {
/* None left. Terminate this goal and let someone else deal
with it. */
debug("derivation output '%s' is required, but there is no substituter that can provide it", id.to_string());
if (substituterFailed) {
worker.failedSubstitutions++;
}
/* Hack: don't indicate failure if there were no substituters.
In that case the calling derivation should just do a
build. */
co_return WorkResult{substituterFailed ? ecFailed : ecNoSubstituters};
}
sub = subs.front();
subs.pop_front();
/* The async call to a curl download below can outlive `this` (if
some other error occurs), so it must not touch `this`. So put
the shared state in a separate refcounted object. */
downloadState = std::make_shared<DownloadState>();
auto pipe = kj::newPromiseAndCrossThreadFulfiller<void>();
downloadState->outPipe = kj::mv(pipe.fulfiller);
downloadState->result =
std::async(std::launch::async, [downloadState{downloadState}, id{id}, sub{sub}] {
Finally updateStats([&]() { downloadState->outPipe->fulfill(); });
ReceiveInterrupts receiveInterrupts;
AsyncIoRoot aio;
return aio.blockOn(sub->queryRealisation(id));
});
co_await pipe.promise;
co_return co_await realisationFetched();
} catch (...) {
co_return result::current_exception();
}
kj::Promise<Result<Goal::WorkResult>> DrvOutputSubstitutionGoal::realisationFetched() noexcept
try {
maintainRunningSubstitutions.reset();
slotToken = {};
try {
outputInfo = downloadState->result.get();
} catch (std::exception & e) { // NOLINT(lix-foreign-exceptions)
printError(e.what());
substituterFailed = true;
}
if (!outputInfo) {
co_return co_await tryNext();
}
kj::Vector<std::pair<GoalPtr, kj::Promise<Result<WorkResult>>>> dependencies;
for (const auto & [depId, depPath] : outputInfo->dependentRealisations) {
if (depId != id) {
if (auto localOutputInfo = TRY_AWAIT(worker.store.queryRealisation(depId));
localOutputInfo && localOutputInfo->outPath != depPath) {
warn(
"substituter '%s' has an incompatible realisation for '%s', ignoring.\n"
"Local: %s\n"
"Remote: %s",
sub->getUri(),
depId.to_string(),
worker.store.printStorePath(localOutputInfo->outPath),
worker.store.printStorePath(depPath)
);
co_return co_await tryNext();
}
dependencies.add(worker.goalFactory().makeDrvOutputSubstitutionGoal(depId));
}
}
dependencies.add(worker.goalFactory().makePathSubstitutionGoal(outputInfo->outPath));
if (!dependencies.empty()) {
TRY_AWAIT(waitForGoals(dependencies.releaseAsArray()));
}
co_return co_await outPathValid();
} catch (...) {
co_return result::current_exception();
}
kj::Promise<Result<Goal::WorkResult>> DrvOutputSubstitutionGoal::outPathValid() noexcept
try {
assert(outputInfo);
trace("output path substituted");
if (nrFailed > 0) {
debug("The output path of the derivation output '%s' could not be substituted", id.to_string());
co_return WorkResult{
nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed,
};
}
TRY_AWAIT(worker.store.registerDrvOutput(*outputInfo));
co_return TRY_AWAIT(finished());
} catch (...) {
co_return result::current_exception();
}
kj::Promise<Result<Goal::WorkResult>> DrvOutputSubstitutionGoal::finished() noexcept
try {
trace("finished");
return {WorkResult{ecSuccess}};
} catch (...) {
return {result::current_exception()};
}
}
@@ -1,80 +0,0 @@
#pragma once
///@file
#include "lix/libutil/notifying-counter.hh"
#include "lix/libstore/store-api.hh"
#include "lix/libstore/build/goal.hh"
#include "lix/libstore/realisation.hh"
#include <future>
namespace nix {
class Worker;
/**
* Substitution of a derivation output.
* This is done in three steps:
* 1. Fetch the output info from a substituter
* 2. Substitute the corresponding output path
* 3. Register the output info
*/
class DrvOutputSubstitutionGoal : public Goal {
/**
* The drv output we're trying to substitute
*/
DrvOutput id;
/**
* The realisation corresponding to the given output id.
* Will be filled once we can get it.
*/
std::shared_ptr<const Realisation> outputInfo;
/**
* The remaining substituters.
*/
std::list<ref<Store>> subs;
/**
* The current substituter.
*/
std::shared_ptr<Store> sub;
NotifyingCounter<uint64_t>::Bump maintainRunningSubstitutions;
struct DownloadState
{
kj::Own<kj::CrossThreadPromiseFulfiller<void>> outPipe;
std::future<std::shared_ptr<const Realisation>> result;
};
std::shared_ptr<DownloadState> downloadState;
/**
* Whether a substituter failed.
*/
bool substituterFailed = false;
public:
DrvOutputSubstitutionGoal(
const DrvOutput & id,
Worker & worker,
bool isDependency,
RepairFlag repair = NoRepair,
std::optional<ContentAddress> ca = std::nullopt
);
kj::Promise<Result<WorkResult>> tryNext() noexcept;
kj::Promise<Result<WorkResult>> realisationFetched() noexcept;
kj::Promise<Result<WorkResult>> outPathValid() noexcept;
kj::Promise<Result<WorkResult>> finished() noexcept;
kj::Promise<Result<WorkResult>> workImpl() noexcept override;
JobCategory jobCategory() const override {
return JobCategory::Substitution;
};
};
}
+2 -2
View File
@@ -93,7 +93,7 @@ try {
}));
auto & result = results.goals.begin()->second;
co_return result.result.restrictTo(DerivedPath::Built {
.drvPath = makeConstantStorePathRef(drvPath),
.drvPath = makeConstantStorePath(drvPath),
.outputs = OutputsSpec::All {},
});
} catch (Error & e) {
@@ -151,7 +151,7 @@ try {
Worker::Targets goals;
goals.emplace_back(gf.makeGoal(
DerivedPath::Built{
.drvPath = makeConstantStorePathRef(*info->deriver),
.drvPath = makeConstantStorePath(*info->deriver),
// FIXME: Should just build the specific output we need.
.outputs = OutputsSpec::All{},
},
+3 -3
View File
@@ -72,12 +72,12 @@ HookInstance::HookInstance()
toHook = std::move(toHook_.writeSide);
builderOut = std::move(builderOut_.readSide);
sink = FdSink(toHook.get());
sink = std::make_unique<FdSink>(toHook.get());
std::map<std::string, Config::SettingInfo> settings;
globalConfig.getSettings(settings, true);
for (auto & setting : settings)
sink << 1 << setting.first << setting.second.value;
sink << 0;
*sink << 1 << setting.first << setting.second.value;
*sink << 0;
}
+1 -1
View File
@@ -29,7 +29,7 @@ struct HookInstance
*/
Pid pid;
FdSink sink;
std::unique_ptr<FdSink> sink;
std::map<ActivityId, Activity> activities;

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