Compare commits

...
Author SHA1 Message Date
Jade Lovelace 9b8d10daa7 jade wip
Change-Id: I4d5a0bebeeefc35dac0bbc065c104c70c24aae17
2025-12-11 14:35:33 -08:00
Jade Lovelace 6f483e5f52 refactor: use std::unique_ptr for libarchive state, remove destructor
Change-Id: Ib16eefc17ae53874b295476fae515305525a7f2b
2025-12-10 14:29:35 -08:00
Tom Hubrecht 36a61d922c lix-clang-tidy: Avoid using llvm::Twine
> A Twine is not intended for use directly and should not be stored

Change-Id: Ie243d109bc3feb75867ed748c5e4cbcddfafd3e1
2025-12-09 17:13:36 +01:00
Tom Hubrecht 5d21b8262b clang-tidy: enable bugprone-multi-level-implicit-pointer-conversion
The default clang version in nixos 25.05 was llvm 19, we are now even
past that

Change-Id: Ieb62616fb87c4d2a8d892136a7164822aa1eceb6
2025-12-09 14:51:43 +00:00
Tom Hubrecht 50c47c340f misc: Remove unused private field, move unused variable in #if, remove virtual specifier
Those are new warnings that come with a more up-to-date llvm

Fixes #1066

Co-authored-by: eldritch horrors <pennae@lix.systems>

Change-Id: I305d0d810d12a5e8d31c7d89e0cdb3a82df71556
2025-12-09 14:29:35 +01:00
Qyriad 2c68244142 build/functional2: don't require installation for tests to run correctly
Change-Id: I9a2bd83c09c8a009e683c73bf2a669036a6a6964
2025-12-09 11:13:55 +01:00
Kate Temkin 15969c51c3 legacy/build-remote: fix a missing format string argument
Change-Id: Ic3156695a7f41367d413f2da3b5791d489576d47
2025-12-09 09:47:21 +00:00
Tom Hubrecht 3db533c637 clang-tidy: enable bugprone-implicit-widening-of-multiplication-result
Change-Id: I7c7bae6c27aa59da23097ec89305cfc60ec9e8e3
2025-12-09 08:32:37 +00:00
Qyriad b2bcd99d59 functional2: fix bug in physical_store_path_for()
I guess on aarch64-darwin dirs.store_dir can be a str?

Change-Id: If4baa1f48f641775c6e3ab9ce7a58e476a6a6964
2025-12-08 19:07:44 +01:00
Alois Wohlschlager a99842e0af nix-eval-jobs: asyncify the coordinator
Change-Id: I62007ed4d43029a4c793c0152c06490d6a6a6964
2025-12-08 18:32:12 +01:00
Raito Bezarius b5a8720a47 legacy/nix-shell: export NIX_LOG_FD
As far as I can tell, there's no harm to let know a nix-shell invocation
about NIX_LOG_FD being stderr.

Fixes #336.

Change-Id: Ifdb3591813251d4bc481158d28dc18e1489de72c
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-12-08 09:12:40 +00:00
Raito Bezarius 908acdfa2f nix3-develop: export NIX_LOG_FD
As far as I can tell, there's no harm to let stdenv know that they can
write to file descriptor 2 which is stderr inside of a nix3-develop
shell.

Contributes towards #336.

Change-Id: I51dcbcbc19a1698a0d1255f9d943d9ebd15d115d
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-12-08 09:12:40 +00:00
Maximilian Bosch be3e4bf78e tests/functional/output-cycles: move to functional2
Change-Id: I682f62a695ba7722e2b3063bf831fc50a19eb153
2025-12-07 21:47:10 +01:00
Maximilian Bosch 0a5f474a25 libstore: fix reporting output cycles on drvs with references to other drvs
Closes #1064

The culprit here is that `genGraphString` is only invoked with the
store-paths associated with the outputs of the derivation, so when
filling `dependents`, the `graph_data.find(p)` call would return the end
of the iterator when doing this for references to other store-paths.

As a result, the code wrote information behind the graph data-structure
causing a corruption. For me, this resulted in a SIGSEGV most of the
time and in a few cases in an uncaught `map::at`-exception as reported
by Niklas.

This patch changes two aspects of the original implementation:

* When filling `dependents` in the node-set, use `map.at()` instead of
  `map.find()->second`. The latter doesn't make any sense and was the
  cause of corrupting memory. The `at` would've made it far easier to
  spot this in the first place.

* Filter out store-paths that don't belong to a different output of the
  derivation when creating `outputGraph`. This variable is used on two
  places, `genGraphString` and for topological sorting.

  The latter already filters out store-paths from a different drv, so
  this is happening now when creating the variable in the first place
  such that `genGraphString` never ends up with corrupt data in the
  first place. This is the actual bugfix.

Implemented a regression-test for this case to be sure.

Change-Id: Ie02144d89c32b0a776cb1ece0601d0229315ebc3
2025-12-07 21:47:09 +01:00
eldritch horrors 6410748c9f libcmd: add raw arg access to legacy commands
we'll need this to modify argv for socket-activated daemons. this is our
replacement for the old savedArgv mechanism that was unscoped and fucky.

Change-Id: Ie048eb8ea99f1c9cd627a051292c836c83197068
2025-12-07 18:49:34 +00:00
eldritch horrors aba740a276 libcmd: remove unused savedArgv
this was only used in the pre-exec daemon days.

Change-Id: I3bbb113f9940e6980f01af60e6614a9656b0fd03
2025-12-07 18:49:34 +00:00
eldritch horrors 1e8f7c7c76 nix/daemon: remove settings copy from parent
the parent daemon does not change any settings before starting a child,
so there's nothing we may want to change that is not already set by the
config file. this also doesn't prevent changes of the config file being
applied to daemons where we do not expect it since it'll only restore a
setting to the parents' value if the child also has an override for it.

Change-Id: Ic5a9ef13458c103ec9979cb187ba8d3ce5e1e719
2025-12-07 18:49:34 +00:00
Qyriad 400b55a410 nix-eval-jobs: use C++23 to match Lix
Change-Id: Ia770f849fd43b360fc641d478abe084e6a6a6964
2025-12-07 18:34:24 +01:00
Qyriad 0714a21b03 perl-bindings: use C++23 to match Lix
Lix changed to C++23 in 4ea8c9d61¹, but the perl subproject wasn't
updated with it. This commit corrects that.

[1]: 4ea8c9d643

Change-Id: I5464138d206d87cf25762720b147487d6a6a6964
2025-12-07 18:34:24 +01:00
Commentator2.0andQyriad 0bcaffa640 functional2: add a hash_path shortcut to Nix
Co-authored-by: Qyriad <qyriad@qyriad.me>

Change-Id: If0b9213d264b60e687ceb2d4d232d23f6a6a6964
2025-12-07 15:34:03 +01:00
Qyriad f6f03d0a52 functional2: add helper to get actual store paths to Nix fixture
Co-authored-by: Commentator2.0 <lix@crystal-cavern.systems>

Change-Id: I13f8ba9d59ae863cf08109a01c638c7f6a6a6964
2025-12-07 15:34:03 +01:00
Tom Hubrecht 8ebe893854 clang-tidy: enable lix-fixincludes
The reorganization has been done

Change-Id: Ib8eec6656f375f8329fe4caefd009369aa36d8f2
2025-12-07 14:05:39 +00:00
Tom Hubrecht 1a304964aa package: Add libllvm to the inputs when linting
This should allow better stacktraces when clang-tidy fails miserably on
aarch64 machines

Change-Id: Ia795cf1dc012bd8f06b429d97184b88c80dd72cb
2025-12-07 11:40:03 +01:00
Tom Hubrecht 543ae33d26 functional2: Increase timeout in the cycles detection test
On a busy CI the 1s timeout is not always enough

Change-Id: Ie39be01ae8818df8c9026d28907f5b145a452e3b
2025-12-06 22:33:19 +00:00
Raito Bezarius 6928c6f952 libstore/store-api: offer more information about drv parsing errors
We offer the three usual options: verify, repair then delete and direct
people to report us corruption so we can fix if this is induced by us.

Fixes #447.

Change-Id: I0df61769d732d227333c206f312857c0593f7bce
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-12-06 20:30:38 +00:00
Raito Bezarius 7f32b71b6b libstore/derivations: expand error message for the first misparse
If the derivation does not start with D, do not return a simple
"expected string 'D'" error but a full error message.

This contributes towards #447.

Change-Id: Iee05f3918e4cc43e79f244ab2fd64a52cf2bb6d2
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-12-06 20:30:38 +00:00
Raito Bezarius d2ca1810b1 nix/upgrade-nix: disallow daemon connections for the store
Prior to I6a6a6964d2b5ad47ae5ea9eb11af9b6373ce2141 — `sudo nix
upgrade-nix` would perform direct store access.

This ensured a certain number of desireable properties for upgrading the
Lix binary itself.

We re-introduce direct store access for upgrading Lix binaries.

Fixes #1060.

Change-Id: I523c4d3023ed5fe9eff8fde9a266c56a0de47d8c
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-12-06 20:30:15 +00:00
Raito Bezarius 7fbb366ce2 lix/legacy/nix-build: save up 30ish chars in $NIX_BUILD_TOP
Alternative to cl/4661 discussed in #1044.

It can be assumed that `$tmpdir/build-top` can be created safely without
any risk, this way, we don't need to reuse the random directory creation
primitive.

Fixes #1044.

Change-Id: Iec52477f3047fc40959b183c607312d5a40fc8c9
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-12-06 19:48:08 +01:00
Maximilian Bosch 74f5d66b39 perl: unset NDEBUG after loading Perl headers
Otherwise, loading Perl bindings fails early with[1]

    undefined symbol: Perl_pad_sv at /nix/store/h2jsb5i4yfblr2f3ac2c7zpmlmj7zjym-perl-5.40.0/lib/perl5/5.40.0/XSLoader.pm line 94

Apparently, it's expected behavior by Perl that this symbol only exists
with `DEBUGGING` being set, hence it's used by the headers. However,
`pkgs.perl` from nixpkgs is apparently not built with `-DDEBUGGING`
causing this error.

Now, `NDEBUG` is manually unset after loading the Perl
headers rather than setting `DEBUGGING` causing the error mentioned
above.

I confirmed that this not only fixes the problem described above, but
running the Hydra tests with

    diff --git a/perl/lib/Nix/Store.xs b/perl/lib/Nix/Store.xs
    index dfdd64d28..14788266c 100644
    --- a/perl/lib/Nix/Store.xs
    +++ b/perl/lib/Nix/Store.xs
    @@ -27,6 +27,7 @@ using namespace nix;

     static AsyncIoRoot & aio()
     {
    +assert(false);
	 static thread_local AsyncIoRoot root;
	 return root;
     }

still results in assertion errors.

Finally, added a small install-check that importing `Nix::Store` works
fine.

[1] https://git.lix.systems/lix-project/hydra/issues/69

Change-Id: I58521777eb0f94b766a9813aa4bbd06f9052bd35
2025-12-05 18:36:35 +01:00
Commentator2.0 07dccd67d6 treefmt/ruff: remove EM rules as they cause unnecessary work with no real upside
As discussed in the F2 matrix channel, not being able to put the error
message inside of the thrown exception just results in one assigning an
additional variable to immediatly throw it. The original reason for
these rules to exist are that the message isn't printed twice, though
the line above the thrown exception is printed anyways in the
stacktrace, so we still get the error message twice but now with
additional work.

Hence this commit removes the said rules

Change-Id: I0b37c0b0861334703a5772c36353d31441f19dcd
2025-12-05 13:45:59 +00:00
Commentator2.0 f65b83df3b treefmt/ruff: extract ruff config from f2 to global state
So far, our ruff config was confined to the f2 package. This meant, that
when one added additional paths to the ruff formatter, those wouldn't
get the same rules applied as f2, resulting in inconsistent styling
thoughout the project.

Due to how configs are resolved, only the "closesed" pyproject toml is
considered. This means, we need to tell f2 to extend its configuration
with the base level one. Though no change is required for other parts of
the project, as long as they don't have their own pyproject.toml

Change-Id: I145c764e7b850194020b5560e1025f4aa80411ae
2025-12-05 13:45:47 +00:00
Commentator2.0andQyriad 85443e0fd8 tests/functional2: migrate completions.sh
Change-Id: I186a1ddd8d56386f9d1308430f517cdec483af14
2025-12-05 13:33:20 +01:00
Raito Bezarius 1e98c01912 libutil/users: support service cache directories
Nix daemon runs in a service context, usually with systemd.

If the Nix daemon unit adds `[Service] CacheDirectory=nix-daemon`, it should
successfully use `/var/cache/nix-daemon` automatically.

Instead, it uses `/root/.cache` right now, which is really bad.

By default, we add `[Service] CacheDirectory=nix` now which means that
caches are moved into `/var/cache/nix`.

Fixes #634.

Change-Id: I854b1045bfdce8f60110aea70bda1bf6657dfd7b
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-12-05 00:14:42 +01:00
Commentator2.0 8f5bf1e905 libstore/machines: add enable flag for toml machines
Provide a way to statically disable a machine.
This is fully static and not dynamic.

Change-Id: I394433ad533bca5dbf6a2f7fea22b8aa0f5aad3f
2025-12-04 12:11:23 +01:00
Commentator2.0andQyriad 3fcfedc216 libstore/machines: add toml parser
implements #854

Co-authored-by: Qyriad <qyriad@qyriad.me>
Change-Id: I958d082ccdf03179b35d5ab8a810ebafcff3b6c5
2025-12-03 21:36:53 +01:00
Commentator2.0 23c341d76d libstore/machines: move legacy parser to own namespace
Change-Id: I4ecf1e56e713cd32b6a443f9d195c1beb0cb2f7f
2025-12-03 20:10:35 +01:00
Commentator2.0 d0a655fc8a libstore/machines: refactor machine parsing
Change-Id: I216592e089328d68f9ca48203a19fd964356249b
2025-12-03 20:10:35 +01:00
Alois Wohlschlager 00dfcc81b4 doc: fix substitution with mdbook 0.4
Recently, in 54df89f601, support for mdbook 0.5
was introduced, including some logic to handle the `sections` -> `items`
rename. However, compatibility with 0.4's `sections` was only kept on the read
path, while writing 0.5's `items` unconditionally, which ends up in the bit
bucket on 0.4, effectively disabling substitution fully and leaving the include
directives in the final documentation. Restore writing into the `sections` when
they were there so that substitution works again.

Change-Id: Idd4d7653012660f3f7fc27f81f29b82d6a6a6964
2025-12-03 10:01:55 +00:00
Commentator2.0 8353b3cc6a tests/functional2: fix non-working failsafe and resulting warning
Silly me forgot to add the dependency responsible for handling
`@pytest.mark.timeout` failsafes to kill a test if it takes too long,
which results in a warning when running f2.
This commit adds the required dependency and hence removes the warning

Change-Id: I281cad05bb0cf50208f72080cb2bfd8cbfb09d0c
2025-12-02 19:52:38 +00:00
Zoe Zuser 06f987fb0c meson: fix libstore.pc
typo of aws-cpp-sdk-transfer as aws-cpp-std-transfer prevents linking
against lix

Change-Id: Id847eab2601698696030d31fcd51288aa5f3d274
2025-12-02 10:45:50 +00:00
K900 716a885def nix3: show logs by default
That's it, that's the change.

Change-Id: I00c55265ee0e9982b91626601236fe238edf480c
2025-12-02 01:02:44 +00:00
eldritch horrors 2ae74802c4 libexpr: AST-dump non-utf8 strings as byte arrays
our strings need not be utf8. json requires utf. -sigh-

fixes #1052

Change-Id: I50ecd9882252370bb81845b099b11a7190475d48
2025-12-01 21:11:25 +01:00
Justin ! 084a8c3711 libexpr: add a deprecation warning for builtins.importNative
First step for: https://git.lix.systems/lix-project/lix/issues/795

Change-Id: If375723c6829f4fb9c7c1c5bff6be3c9e164c2be
2025-12-01 13:02:13 -05:00
K900 97dd9ba1e2 flake: minimal fix for lowdown override failing on nixpkgs master
All of this can be yeeted once 25.05 is gone.

Change-Id: Id8d9528cf5d09d7697203dc7dde4be1f0ac5f5d1
2025-12-01 16:29:04 +00:00
Alois Wohlschlager f3ed72f30b tests/functional: include mount and PID namespaces in the sandbox check
Running the sandbox (as unprivileged user) requires all of user, mount and PID
namespacing. Previously, only the user namespacing support was checked.
Unfortunately, newer Ubuntu kernels impose restrictions on capabilities within
unprivileged user namespaces [1]; as observed by experimentation, in particular
(re-)mounting filesystems or changing mount propagation is prohibited even
inside a mount namespace, making it impossible to use the Lix sandbox. So any
tests requiring sandboxing should check for this breakage and be skipped if
detected.

`unshare --mount` performs the same problematic operations, so that's what gets
used for the test. PID namespaces are included as well for completeness, even
though currently no instance of breakage due to them specifically is known.

This fixes [2] for the functional test suite only.

[1] https://ubuntu.com/blog/ubuntu-23-10-restricted-unprivileged-user-namespaces
[2] https://git.lix.systems/lix-project/lix/issues/545

Change-Id: If95b527bf965ccb06371bbef7b6007666a6a6964
2025-12-01 14:25:47 +00:00
Alois Wohlschlager 31b6c823ae tests/functional: mark the remaining test requiring sandboxing
Diverted (chroot) store requires sandboxing, so the relevant test needs to be
marked as such.

Change-Id: I5c8ad5f90bb26f8e7088c7141066a18a6a6a6964
2025-12-01 14:25:47 +00:00
Alois Wohlschlager afd7faf385 libstore/build: disallow sandbox-fallback when using a diverted store
Builds using a diverted store strictly require sandboxing. Explicit disabling
by the user is already handled properly by force-enabling the sandbox anyway
(with a warning), but sandbox-fallback was still honored, predictably leading
to hilarious breakage (like the "confusing output" in [1]) when it actually
triggered. Cause a hard failure instead.

[1] https://git.lix.systems/lix-project/lix/issues/545

Change-Id: I61ea32c3bd8bca4e5ba278312b0a49326a6a6964
2025-12-01 14:25:47 +00:00
Alois Wohlschlager 7f176002c6 libstore/build: warn when the sandbox is force-enabled due to a diverted store
Builds using a diverted store strictly require sandboxing. It therefore makes
sense to automatically enable it even if otherwise explicitly disabled by the
user. However, they should still be informed that their choice is not being
respected.

Change-Id: Ia2cbdc24a4c55d8ecc49094e35e698e16a6a6964
2025-12-01 14:25:47 +00:00
Alois Wohlschlager b4e2abdf5c libstore/build: upgrade the sandbox-fallback message to a warning
Disabling build sandboxing is a significant change to build isolation. If this
is done automatically due to namespacing issues (and sandbox-fallback not being
disabled), show the user a loud warning without the need to opt into a lot of
other log spam.

Change-Id: I52daccdd6934873234223c798ca3dcff6a6a6964
2025-12-01 14:25:47 +00:00
Tom Hubrecht 1bfbbe2415 nix-shell: Add NIX_SHELL_LEVEL variable
This contains the depth of nix shells nesting.

Fixes #826

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

Change-Id: If584c9d02730d6c857636dafdeab0c01f4ec8e0f
2025-12-01 12:02:45 +00:00
Tom Hubrecht 0c6d299e16 libexpr/eval: Deprecate shadowing <nix/fetchurl.nix>
If the NIX_PATH shadows internal files, this will often break things,
hence we forbid it.

Fixes #998

Change-Id: I70e5d389532ada1c9f910c60281abe565e3ce6bb
2025-11-30 12:28:54 +00:00
Alois Wohlschlager 3981458d8c nix-eval-jobs: make the tests independent of the evaluation order
Some of the tests depended on the evaluation order chosen internally by
nix-eval-jobs, which will break when that order changes. In practice the order
is not guaranteed, and already not deterministic with multiple workers.

Change-Id: I2e85d096f4439a8075a6f4a08d31c0f16a6a6964
2025-11-29 12:01:20 +01:00
Alois Wohlschlager ecf0d4c5a4 nix-eval-jobs: compile with Clang
GCC still ICEs on coroutines, while Clang works properly. Use the latter, just
like for Lix itself.

Change-Id: I802d6df7279550a5f5355e6c1ea0533c6a6a6964
2025-11-29 12:01:20 +01:00
Alois Wohlschlager 7440c4ef75 nix-eval-jobs: split the collector loop
Previously the collector-side implementation of the worker interaction protocol
was a bunch of spaghetti code. Split it apart at places where it makes sense to
be easier to follow.

Change-Id: If3cc6b6fbf289dd778856b40b55316c76a6a6964
2025-11-29 12:01:20 +01:00
Alois Wohlschlager 24497d08bc nix-eval-jobs: use std::thread
As the worker processes are now properly spawned using fork-exec, they no
longer inherit the stack size from the non-main coordinator thread, but get a
main thread with reasonable stack size on their own. For this reason the
coordinator threads can use the default stack size, and turn std::thread can be
used as the only reason for the custom wrapper was to enlarge the stack.

Change-Id: I1192474885abe9b0625ac483840b6e1a6a6a6964
2025-11-29 11:25:19 +01:00
Alois Wohlschlager 258c5b76eb nix-eval-jobs: fork and exec the workers
Similar to how 7b37d5ea6a switched the subdaemons
from fork-only to fork-exec, do the same for the nix-eval-jobs workers. This is
the promised change that causes the signal handler thread to appear.

Change-Id: I590a5eb6bddfb546a331a01191fd788b6a6a6964
2025-11-29 11:25:19 +01:00
Alois Wohlschlager 9baa4b5b74 nix-eval-jobs: wait for the crashed worker synchronously
The retry added in 4c5efd4548 has never worked
properly, as releasing the pid the first time causes it to be -1 and thus
waiting for a random child on retry. While there is a good chance that this
really catches the crashed worker, this is by no means guaranteed, and it could
just as well have caught different restarting worker. Wait synchronously
instead. The worst possible outcome is a hang instead of an error message if
the worker manages to close its pipe but then fail to exit, which is not
supposed to happen in the first place.

Change-Id: I4e4c3f532ec15c3118f6f77821ab820a6a6a6964
2025-11-29 11:25:19 +01:00
Qyriad d5d03cd8de undefined behavior: -Wembedded-directive
warning: embedding a directive within macro arguments has undefined behavior

????? CLANG? why does this take -pedantic to show??

Change-Id: I6f4a9f3db1b89448f9f5d00d82b551e46a6a6964
2025-11-28 21:29:44 +01:00
Jade Lovelace 0423f0ef38 docs typo: ssh-keyscan takes a hostname not user@host
Change-Id: Ib823165bcababed2808a602368d6ab57762a49b3
2025-11-28 12:06:20 +00:00
Qyriad 94c9b43b4a docs/hacking: fix typo from LLD commit
Thank you horrors

Change-Id: Id792cfd19efd3f99b55809cdaae420c06a6a6964
2025-11-27 12:58:07 +00:00
Qyriad b92c85848f functional2: fix on macOS if user has PATH elems that don't exist
This change brought to you by my uninstalling go but forgetting to
remove ~/.local/opt/go/bin from my PATH.

Change-Id: I937675bab11ac7e0678c896cba78204b6a6a6964
2025-11-27 10:51:08 +00:00
helle 54df89f601 docs build system: Fixed manual to be compatible with mdbook 0.5.x
Maintains compatibility with mdbook 0.4.x.

Includes comments for what to remove once 0.5.x is the only we care
about. Some other changes technically could be changed at that point,
but currently serve to enable universal support of 0.4.x and 0.5.x

Fixes #1051.

Change-Id: Ic5b405038d180bcd357bbd9e5716879e0c26e5f5
2025-11-26 23:50:28 +01:00
Tom Hubrecht cb0f9b9590 libstore/transfer: Warn by default instead of throw for destination changes
Fixes #1004

Change-Id: I2d583fbad40b3c95e37b70167a81c8fb492d5668
2025-11-26 14:42:52 +01:00
Tom Hubrecht 0c70cc6259 libutil: Reintroduce compile assertion that was mistakenly dropped
https://git.lix.systems/lix-project/lix/commit/74513483bc5572d988a059b8e964662d66f1667f
removed this check which is actually load-bearing for Lix (c.f.
https://git.lix.systems/lix-project/lix/commit/96a36833084f66b79ba778e719862b8219557773
).

Fixes #677

Change-Id: I3cc9a16c64c5189f788c510609afcf8bad997741
2025-11-26 01:56:00 +00:00
Qyriad 9f12981b30 parser: improve error message for missing } in attrset
Change-Id: I2846dbd8d0c1beb23c885a2d6d388a4f6a6a6964
2025-11-25 19:50:24 +00:00
Qyriad 6bb532ed03 parser: improve error message for missing semicolon in let/{}
In the same vein as be18b7dc2¹.

Before:
  error: syntax error, expecting ';'

After:
  error: syntax error, expecting ';' to end binding

[1]: be18b7dc25

Change-Id: Iffbcb113d2b892a50c646c9875e970376a6a6964
2025-11-25 19:50:24 +00:00
Commentator2.0 3661f34aba tests/functional2: fix --accept-tests not propagating to infra tests correctly
gotta make sure to do things in the correct order next time -_-

Change-Id: I84ba6495100e658aa35fac0a0917fae9363fe4dd
2025-11-25 13:43:01 +01:00
Alois Wohlschlager df607dd39e nix-eval-jobs: print evaluation results using writeToStdout
Lix blocks SIGPIPE, and std::cout ignores EPIPE (actually it probably stashes
an error code in some flag that no one ever reads, with the same end result).
Consequently, nix-eval-jobs would waste resources by continuing to evaluate
even when the reader interested in the results has long gone away. Instead, use
writeToStdout, which throws an error on EPIPE, leading to the process
terminating as desired.

Reported-by: Winter <winter@winter.cafe>

Change-Id: I962c09bab582a8ed27dd41c01b1519876a6a6964
2025-11-25 11:50:59 +00:00
Alois Wohlschlager 441f7db5f1 nix-eval-jobs: handle worker interruption properly
Right now, the worker will not receive interrupts, as it has no signal handler
thread due to the way it forks, but this will change soon. Prepare to handle
worker interruption properly, by letting the worker exit successfully in this
case and adjusting the coordinator to handle this exit gracefully.

Change-Id: I9f79670b0d2004e7e2d8bf36cf67108e6a6a6964
2025-11-25 11:50:59 +00:00
Qyriad 9d01255046 build: use lld in devShell on Linux
Change-Id: If270e824cab74337c6c71fe92fd376c76a6a6964
2025-11-25 11:02:32 +01:00
piegames 27a94369a9 nix::parser::State::addAttr: Deep attribute merging
Closes #845

Change-Id: Ie14d0e5a7a9fb778325c4ad30d1e1bd73c60b4f9
2025-11-24 19:16:27 +00:00
piegames 8765266dae nix::parser::State::addAttr: Improve error message
For some reason that eludes me, it used to only print the last attribute
instead of the full chain.

Change-Id: I2f5f6e85e2b1ab3ab20a9c99bf06d1e45a4d09bb
2025-11-24 19:16:27 +00:00
piegames 5484351523 nix::parser::State::addAttr: Sanitize
`e`, `ae`, `j`, `jAttrs` … holy fuck this function was so much worse
than it needed to be. This refactoring includes:

- Descriptive variable names where possible
- More code comments indicating what even is happening
- Shuffled the control flow around to be more linear (move early return
conditions up, etc.) and have less rightwards-drift, to increase
readability
- Extracted `mergeAttrs` function for future changes and readability

Change-Id: I7253b47ce4910fdc67aa7d6edff2f3c0bb8fa55b
2025-11-24 19:16:27 +00:00
piegames 4ce8d809ea libexpr/parser: Fix small error position mistake
In one of the cases it pointed to the first instead of the second
occurence, which is a bit confusing

Change-Id: Ie508d1f84feb434708804d45aa8a9c8b1e3c5f69
2025-11-24 19:16:27 +00:00
piegames 6da3519297 tests/functional2/lang: Move some tests around
Change-Id: I7d87c6c210015b33091876e6656f7c0116bc12ea
2025-11-24 16:31:29 +01:00
piegames 45e4bec59e tests/functional2/lang: Refactor test outputs
How long do we have the value printer already? It's time to stop
concatenating strings like it's 2005

Change-Id: I3f5074de2439a1ad78af94de877bb141bc9f1d82
2025-11-24 16:31:29 +01:00
Tom Hubrecht 0167bf56f5 libstore/getDefaultSubstituters: Add information when a substituters cannot be setup
Fixes #1034

Change-Id: Ic65ef955c4eb44766926e9b3ef4dcb4b94820a16
2025-11-24 12:08:30 +00:00
Tom Hubrecht 26de2b79c2 nix/develop: Set default outputs attribute
When using `builtins.derivation`, the `outputs` list can be omitted, and
in that case, it defaults to `[ "out" ]`, set this default when building
the shell derivation for `nix develop`

Fixes: #556
Change-Id: I4dc77d060f30ee499e186ec919de30bd3e5c53bb
2025-11-24 09:56:14 +00:00
Tom Hubrecht 395fe68127 tests/clang-tidy: Run on all supported platforms
Fixes #1013

Change-Id: I7388e4eb4728c34f2e21756438020e28b4f1cfca
2025-11-24 07:22:15 +00:00
Alois Wohlschlager 0f3a66f856 libstore: stop duplicating the unit in the retry message
Commit 5dc847b47b introduced usage of
std::chrono::milliseconds, which prints with the unit, leading to its
duplication. Remove this duplication again. Specifically, the explicitly
printed one is kept, due to better typography.

Change-Id: I724ea6be6f2a1349fe799602c3429f096a6a6964
2025-11-23 11:45:34 +01:00
Tom Hubrecht 748a975aa0 libexpr/builtins: Fixup scopedImport documentation
Change-Id: I01cbc628a0334a1d1e6347d987089262c08e7cee
2025-11-23 08:58:17 +01:00
Tom Hubrecht ea99f26b25 libexpr/builtins: Document fetchMercurial
Change-Id: Ic849bd6dd10374b717d3fc257da25208f2c45525
2025-11-23 08:58:17 +01:00
Tom Hubrecht 3cf5ad0164 libexpr/builtins: Document unsafeDiscardStringContext
Co-authored-by: eldritch horrors <pennae@lix.systems>

Change-Id: I4ebbbc9d32152f296a2553f2fd324dacf8af02d8
2025-11-23 08:58:17 +01:00
Tom Hubrecht fd273186c6 libexpr/builtins: Document appendContext
Change-Id: Ic45e0d4f6f1646552a7162bd8916f63fc11e5e62
2025-11-23 07:26:15 +00:00
Qyriad be18b7dc25 parser: improve error message for missing semicolon in inherit
Before:
  error: syntax error, expecting ';'

After:
  error: syntax error, expecting ';' to end 'inherit' bindings


Change-Id: I47c633cb5c696b646840c58e03270a7d6a6a6964
2025-11-22 16:48:53 +01:00
Qyriad b3e24cb3e5 new option: abort-on-warn
Co-authored-by: Emilia Bopp <contact@ebopp.de>

Change-Id: I3c3347e51d8543fbeb2b4e6ed12b0f556a6a6964
2025-11-22 16:48:53 +01:00
Qyriad 2d85d4c7b6 new option: debugger-on-warn
Co-authored-by: Emilia Bopp <contact@ebopp.de>

Change-Id: I5e20304538dd3e27fb36c32c0a6133cd6a6a6964
2025-11-22 16:48:53 +01:00
Tom Hubrecht 94ca4fd7b4 treefmt: Add meson/clang-tidy to the list of files checked by ruff
Change-Id: I3141286069844588a721cd4e523a284ef3a359c8
2025-11-22 13:19:40 +01:00
Commentator2.0 b707403a30 tests/functional2: migrate eval.sh
Change-Id: Ib5fb9e3b5d76612e7259ec17fb890298198fc885
2025-11-21 17:35:47 +00:00
Commentator2.0 40416d103f tests/functional: remove the now fully migrated lang framework
We have done it!
The functional/lang framework has fully been migrated to functional2 :D

closes: #856

Change-Id: I63ad8d7dbcd9b5267ca04af68df73b1ffa3d6461
2025-11-21 17:02:35 +00:00
Tom Hubrecht 8558cf781a libexpr/builtins: Document derivationStrict
Co-authored-by: eldritch horrors <pennae@lix.systems>
Co-authored-by: Qyriad <qyriad@qyriad.me>

Change-Id: I4cf95683da54efe830514f3a6795057cadd223c6
2025-11-21 14:59:03 +01:00
Tom Hubrecht 428a0bcb5e nix/run: Move argument parsing in the ifdef
Most of the arguments are only used on linux platforms

Change-Id: Iab79a3edf346257a2cd1c6e18ead2f1f77c7cc6d
2025-11-21 11:56:38 +00:00
Commentator2.0 9be0c2ee15 releng: issue references are now lix issues by default
After multiple cycles of deprecation, we now use the lix repositorty as
a default, when no prefix is defined

Change-Id: I29b445fee39fb20c0ff024e3b95402f459144b62
2025-11-20 18:03:45 +00:00
Qyriad 6192cbebac libexpr: refactor fallibly doing stuff on debug traces (NFC)
Change-Id: I009ce2ea424938507223fc6b3cf3b1236a6a6964
2025-11-20 17:52:29 +01:00
Emilia BoppandQyriad 0ade82d23a libexpr: add builtins.warn
Added builtins.warn` which takes two arguments: a message that is
displayed as a warning during evaluation which must be a string and a
value that is returned from the expression.

The next commits add new settings to control the behavior of the new
builtin: `debugger-on-warn` allows the user to start the debugger and
`abort-on-warn` aborts evaluation with an error.

Unlike upstream, I chose not to mark evaluation warnings from
`builtins.warn` as distinct from other warnings because that breaks the
commonly expected logging format `level: message`.

Co-authored-by: Qyriad <qyriad@qyriad.me>
Upstream-PR: https://github.com/NixOS/nix/pull/10592
Fixes: https://git.lix.systems/lix-project/lix/issues/579
Change-Id: I8658c88e5c27952b65e8b9f5525a572e0680cc1f
2025-11-20 17:52:29 +01:00
chris dd3ba39384 docs: fix broken link in basic package management section
Change-Id: I278e46b77a53af9ac1a65b213d5289fb6e63e7c2
2025-11-20 14:47:51 +00:00
Tom Hubrecht 170f092ce2 gc: Unify UX across the 3 cli ways to collect garbage
There are currently 4 different ways to run garbage collection using
lix:
- `nix-collect-garbage`
- `nix-store --gc`
- `nix store gc`
- (using the daemon directly)

As they were written all at different times, their output varies (and is
broken in some case). This unifies the display of informations in the
following ways:

- The list of paths in the results is always printed (in the nix3 cli it
is hidden unless `-v` is passed)
- The number of paths in the result set is always displayed
- The size of deleted paths is only shown when actually deleting things
(as it would have been 0B in any case)

Fixes #905

Change-Id: I40d9ec7c6d76795f6c6dd30df196d1e855bdb9db
2025-11-20 11:53:18 +00:00
Tom Hubrecht ff231b9b52 libexpr/builtins: Document addErrorContext
Change-Id: I94eee0ccba3fcdc587783800d59ac572ed44f69a
2025-11-20 09:25:10 +00:00
Tom HubrechtandEelco Dolstra f01555105a libexpr/builtins: Document scopedImport
This also allows removing a static initialization of a primop.

Co-authored-by: Eelco Dolstra <edolstra@gmail.com>
Change-Id: I74da68205630f658b239e4327bf5e27bfc1b91da
2025-11-20 09:25:10 +00:00
Justin ! 2a308bca00 libexpr: remove fetchClosure exp feature
fixes #1010

Change-Id: I149cd01bf08655c91dc2d3817954937c6a6a6964
2025-11-20 08:45:29 +00:00
Lunaphied 2c73f3c492 cq: improve derivation printing configuration
Change-Id: I7f16afa921cf2a33f750cb0f5cc53fa36a6a6964
2025-11-19 20:52:38 +01:00
Lunaphied f3b2f3496b repl: allow :p to print derivation attrs.
Change-Id: I6a6a6964a58c3340acca9272c616c079c5218d6e
2025-11-19 17:46:52 +01:00
Tom Hubrecht 04a4b15991 libcmd/copy: Check the validity of the options if required
This is done at the start of the command call, which allows not building
expensive things if the arguments are incorrect anyway.

Fixes #687

Change-Id: Ic924fe2115cf760684c6fdf7987279e96ab00286
2025-11-19 14:45:52 +00:00
Qyriad 1fa8df82df libutil: include LIX_MAJOR, LIX_MINOR, and LIX_PATCH macros
Fixes #1038

Change-Id: I7d8a4648890fce7ff15695876c9b9d3a6a6a6964
2025-11-19 11:47:20 +01:00
Commentator2.0 1b7ad3a7ad tests/functional2: Migrate symlink-resolution
the newly added f1/lang tests is required for the f1/lang framework
dismanteling to happen in a separate commit

Change-Id: Ic419c515262294c51a46d1513daa7848e4b71405
2025-11-18 20:01:23 +01:00
Commentator2.0 2497e0101e tests/functional2/lang: Migrate builtins.pathExists
Change-Id: Ib31b7dddc71ae36924822f300b028bf354f63874
2025-11-18 20:01:23 +01:00
Commentator2.0 3eeb5d10ba tests/functional2/lang: fix warnings
While the err file should always be empty, we prefer not have (easily
avoidable) warnings in the log, in order for actual warnings to be
spotted more easily.
Additionally this way no additional changes are required in case they
make use of some depreacted features in the future.

Change-Id: Ie078e2a851b2035d839f6b95d4188e475eb96b2d
2025-11-18 20:01:23 +01:00
Commentator2.0 c589a95546 tests/functional2/lang: Migrate builtins.getEnv
Change-Id: I9e30d9bf897854e8843194779034fd98d172bcf1
2025-11-18 19:52:17 +01:00
Commentator2.0 4da70ab3bd tests/functional2/lang: Migrate eval-okay-xml
Change-Id: Ib626c3f4ca38b618bab368f0d23829debc5ec38b
2025-11-18 19:52:17 +01:00
Commentator2.0 68a39b1d6e tests/functional2/lang: Migrate builtins.derivationStrict
Change-Id: I42781af8b5b4a2352e867a07712a752ddfdeaa35
2025-11-18 19:45:15 +01:00
Commentator2.0 86964fb440 tests/functional2/lang: Migrate path-string-interpolation
Change-Id: I6f5d1830363c8dff4fa6b368550c8c4247766609
2025-11-18 19:45:15 +01:00
Commentator2.0 8d75a61f73 tests/functional2/lang: Migrate builtins.addDrvOutputDependencies
This also marks the end of eval-fail-* migrations

Change-Id: If06a60cd9d222bba5fc4f9a9b93de125e7fa2994
2025-11-18 19:45:15 +01:00
Commentator2.0 89a9d6e529 tests/functional2/lang: Migrate string coersion for integers
Change-Id: I428c8935119b3184109b3e3f760ed72054b0cd00
2025-11-18 19:45:15 +01:00
piegames daefbb8a44 tests/functional2/lang: Migrate more builtins
Change-Id: Ib981a5c544748475cf98a8daae0609a52c9c6eb9
2025-11-18 13:48:32 +00:00
piegames 2df8dc1178 tests/functional2/lang: Migrate string-context tests
Change-Id: I8c8f691f77eb87b52343d52a1c2a8a255ea2c887
2025-11-18 13:48:32 +00:00
piegames dbb6098ff4 tests/functional2/lang: Migrate attrs tests
Change-Id: I8eee261d76d34c4a138bc24d9c2b9a0dfb944c7f
2025-11-18 13:48:32 +00:00
piegames a852ceb0c8 tests/functional2/lang: Migrate autoargs
autoargs get their own folder and are not allowed with the other
function args tests, just like they deserve their own ring of hell

It shall be noted though that this test is particularly pointless, as it
merely tests the normal argument passing (which is fine), and not the
magic autoargs actually provide

Change-Id: Ia2dc00754a13c02e4926200141f90bc797820a15
2025-11-18 13:48:32 +00:00
piegames d470530ec7 tests/functional2/lang: Migrate lists
That one is a bit confusing, as I renamed the old `in` test to `in-2`
(file identities in Git when)

Change-Id: Id0e17de414dbc1b1b616d456175704f98657a16a
2025-11-18 13:48:32 +00:00
piegames 68fdce3548 tests/functional2/lang: Migrate eval-okay-patterns
Change-Id: I46e155ed2cc1ffcb75935fc1744c7938af22c0c6
2025-11-17 22:10:11 +00:00
piegames db59bb63c8 tests/functional2/lang: Migrate eval-okay-print
Change-Id: I70936bcf324dbb8b45a3c65567b6c25f409d7953
2025-11-17 22:10:11 +00:00
piegames 1dc174b4df tests/functional2/lang: Rename ind-string → string-indented
To make it easier to find them and increase locality

Change-Id: I6365e91d2c79362c05b766415cc1972346d7a610
2025-11-17 22:10:11 +00:00
piegames 7a005ebe5d tests/functional2/lang: Migrate eval-okay-backslash-newline-*
It's a string test, so in the string ~~hole~~ folder it goes

Change-Id: Id70da8a7a3de328bc94d2106a53eb7484c4ef122
2025-11-17 22:10:11 +00:00
piegames e6a672d024 tests/functional2/lang: Migrate builtins.toXML
Change-Id: I31f47b9953ef0370dd8e57f6e43ee32d278c3663
2025-11-17 22:10:11 +00:00
piegames ab764d9831 tests/functional2/lang: Migrate builtins.compareVersions, builtins.parseDrvName
Change-Id: I82bbe7f406aa37bc5493c58a8f8bfa5820503632
2025-11-17 22:10:11 +00:00
piegames 776bb81e28 tests/functional2/lang: Migrate builtins.readFile
Change-Id: Ib4d6b0af7e012297692b602cbaafef6a0480129b
2025-11-17 22:10:11 +00:00
piegames 4aedfd346f tests/functional2/lang: Migrate builtins.readDir
Change-Id: Ic70e4ef7780281f403d0421d63ecf849e083c01e
2025-11-17 22:10:11 +00:00
piegames c350bf8b5e tests/functional2/lang: Migrate builtins.readFileType
Change-Id: I7f6c4cddb8d969cb45652e1f72c0001a0ebd2d38
2025-11-17 22:10:11 +00:00
piegames e7bd41ece6 tests/functional2/lang: Migrate search-path test
Change-Id: If7513252cff92831822438753a78c2e9f80ac06b
2025-11-17 22:10:11 +00:00
piegames ca41ea5e3b tests/functional2/lang: Migrate builtins.seq
Change-Id: Ifeb975c0569b196e44054d23c20d22c0481ea3af
2025-11-17 22:10:11 +00:00
piegames 8b33caa506 tests/functional2/lang: Migrate builtins.path
Change-Id: Iefe96d38f89bc730b8e673069f9eb0bd3546accf
2025-11-17 22:10:11 +00:00
piegames 449ddbe324 tests/functional2/lang: Migrate builtins.toJSON
Change-Id: Ic740ab1fe0792c7e7596ae8a9beb9e76b37d6284
2025-11-17 22:10:11 +00:00
piegames eba84b51d6 tests/functional2/lang: Migrate builtins.fromTOML
Change-Id: I80d4058f3b74e9815425499e21afce5fc5dc6375
2025-11-17 22:10:11 +00:00
piegames 721db28037 tests/functional2/lang: Migrate eof tests
Call it a bug, call it a feature, but the old testing framework
ungracefully shits itself when it has no tests to run … in other words,
we finally migrated all the parse-fail tests 🎉

Change-Id: Ibd7f1c04d9a396a20f14361af7924f0074d1ac23
2025-11-17 22:10:11 +00:00
piegames cbac63cfe5 tests/functional2/lang: Migrate builtins.fromJSON
Change-Id: I7828e006eb31f7e4976fcac10ca3529b6b29a700
2025-11-17 22:10:11 +00:00
piegames cb29f3b060 tests/functional2/lang: Migrate merge-attrs tests
Change-Id: Ib8b8e4505454c5061d596dd490db43471bfb754b
2025-11-17 22:10:11 +00:00
piegames 5f7e032d28 tests/functional2/lang: Migrate eol tests
Also slightly refactor the existing crlf test to better fit in

Change-Id: Ib4dfa4b3318b3dd55a12d99a9f4908cdf1bce08b
2025-11-17 22:10:11 +00:00
piegames b912f91f62 tests/functional2/lang: Migrate with tests
Change-Id: I3a6c2fb38569711d717f8488c7a949a4afbea675
2025-11-17 22:10:11 +00:00
piegames 1a5fddbf99 tests/functional2/lang: Merge tests
Change-Id: Ia62b1b558e67b18c1998207824271826b9a707db
2025-11-17 22:10:11 +00:00
piegames 59dd03bc39 tests/functional2/lang: Migrate undefined-var tests
The second test has also been fixed. I'd do it in two commits if
functional1 wasn't such a PITA (guess why we're doing the migration …).

Basically, the introduced syntax got changed later on, but the test
never got updated, and back then tests didn't test the output so nobody
ever noticed it now failing for a wrong reason.

Change-Id: I5b66127d507c65676b8dd4a82d1e1f2857fef145
2025-11-17 22:10:11 +00:00
piegames 108beda9ae tests/functional2/lang: Migrate another weird regression test
A link to an issue would have come really handy …

Change-Id: I44495a5e193c0bfbf9e905271025b7321daa84c6
2025-11-17 22:10:11 +00:00
piegames fef822494e tests/functional2/lang: Drop trivial "parse-okay-1"
This really looks like one of the first tests ever written, it doesn't
seem to be actively testing anything interesting

Change-Id: I442840db932203c25da2d8400ada791f8aed04ce
2025-11-17 22:10:11 +00:00
piegames d864159f34 tests/functional2/lang: Migrate url-literals test
Change-Id: I9096037b3e246dc6e63adcc8f327398bf14518a5
2025-11-17 22:10:11 +00:00
piegames 5bbd2946fa tests/functional2/lang: Migrate let tests
Change-Id: Ia3fc85dafbfd706d719f1766544f836df23ef7c9
2025-11-17 22:10:11 +00:00
piegames ad5d044b35 tests/functional2/lang: Migrate builtins.foldl'
Change-Id: I244751eee4881794f7a5b810e19e7d19f1257999
2025-11-17 22:10:11 +00:00
piegames 7bb79d0ae6 tests/functional2/lang: Migrate rec-set-overrides tests
Change-Id: I2ffc949773188d9b98629df87ac0d2fbf7b2ee6f
2025-11-17 22:10:11 +00:00
piegames fdfe21954f tests/functional2/lang: Migrate builtins.unsafeGetAttrPos tests
Change-Id: I6b1bd29bd03467715c985ae7ecb50c584ed48e11
2025-11-17 22:10:11 +00:00
piegames d0ef150349 tests/functional2/lang: Migrate builtins.hashFile tests
Change-Id: I2e1c1af59daf8c976877f5ec09153464e7480aea
2025-11-17 22:10:11 +00:00
538 changed files with 4371 additions and 2841 deletions
+2 -7
View File
@@ -8,16 +8,12 @@ Checks:
- -bugprone-narrowing-conversions
# kind of nonsense
- -bugprone-easily-swappable-parameters
# too many warnings for now
- -bugprone-implicit-widening-of-multiplication-result
# Lix's exception handling is Questionable
- -bugprone-empty-catch
# many warnings
- -bugprone-unchecked-optional-access
# many warnings, seems like a questionable lint
- -bugprone-branch-clone
# extremely noisy before clang 19: https://github.com/llvm/llvm-project/issues/93959
- -bugprone-multi-level-implicit-pointer-conversion
# we don't compile out our asserts
- -bugprone-assert-side-effect
# FIXME(jade): figure out if this warning is any good
@@ -29,9 +25,6 @@ Checks:
# crimes must be appropriately declared as crimes
- cppcoreguidelines-pro-type-cstyle-cast
- lix-*
# This can not yet be applied to Lix itself since we need to do source
# reorganization so that lix/ include paths work.
- -lix-fixincludes
# This lint is included as an example, but the lib function it replaces is
# already gone.
- -lix-hasprefixsuffix
@@ -40,3 +33,5 @@ Checks:
CheckOptions:
bugprone-reserved-identifier.AllowedIdentifiers: '__asan_default_options'
bugprone-unused-return-value.AllowCastToVoid: true
ExtraArgs: ["-Werror=unnecessary-virtual-specifier"]
+3 -3
View File
@@ -24,8 +24,8 @@ def map_contents_recursively(transformer):
def process_command:
.[0] as $context |
.[1] as $body |
$body + {
sections: $body.sections | map(map_contents_recursively(if $context.renderer == "html" then transform_anchors_html else transform_anchors_strip end)),
};
# XXX FUTURE: drop sections once mdBook is at 0.5.0 or above in nixpkgs
$body | (.items? // .sections) |= map(map_contents_recursively(if $context.renderer == "html" then transform_anchors_html else transform_anchors_strip end))
;
process_command
+6 -5
View File
@@ -22,20 +22,21 @@ fold.level = 30
# not want to disable the links preprocessor entirely though because that requires
# disabling *all* built-in preprocessors and selectively reenabling those we want.
[preprocessor.substitute]
command = "python3 doc/manual/substitute.py"
command = "python3 substitute.py"
before = ["anchors", "links"]
[preprocessor.anchors]
renderers = ["html"]
command = "jq --from-file doc/manual/anchors.jq"
command = "jq --from-file anchors.jq"
[output.markdown]
[output.linkcheck]
# XXX FUTURE: may be reenabled once mdBook 0.5.0 or above and matching mdbook-linkchecker are in nixpkgs
#[output.linkcheck]
# no Internet during the build (in the sandbox)
follow-web-links = false
#follow-web-links = false
# mdbook-linkcheck does not understand [foo]{#bar} style links, resulting in
# excessive "Potential incomplete link" warnings. No other kind of warning was
# produced at the time of writing.
warning-policy = "ignore"
#warning-policy = "ignore"
+15
View File
@@ -61,6 +61,11 @@ cole-h:
display_name: Cole Helbling
github: cole-h
commentator2.0:
display_name: Commentator2.0 (Rutile)
forgejo: commentatorforall
github: CommentatorForAll
delan:
display_name: delan
forgejo: delan
@@ -125,6 +130,11 @@ jade:
just1602:
forgejo: just1602
k900:
display_name: K900
forgejo: K900
github: K900
kasimeka:
display_name: ورد
forgejo: janw4ld
@@ -176,6 +186,11 @@ midnightveil:
forgejo: midnightveil
github: midnightveil
milibopp:
display_name: Emilia Bopp
forgejo: milibopp
github: milibopp
nan-git:
display_name: NaN-git
github: NaN-git
+4 -2
View File
@@ -42,8 +42,9 @@ manual = custom_target(
'-c',
'''
@0@ @INPUT0@ @CURRENT_SOURCE_DIR@ > @DEPFILE@
cd @3@
@1@ build . -d @2@ | { grep -Fv "because fragment resolution isn't implemented" || :; }
cd @SOURCE_ROOT@
@1@ build doc/manual -d @2@ | { grep -Fv "because fragment resolution isn't implemented" || :; }
rm -rf @2@/manual
mv @2@/html @2@/manual
find @2@/manual -iname meson.build -delete
@@ -51,6 +52,7 @@ manual = custom_target(
python.full_path(),
mdbook.full_path(),
meson.current_build_dir(),
meson.current_source_dir()
),
],
input : [
@@ -81,7 +83,7 @@ manual = custom_target(
depfile : 'manual.d',
env : {
'RUST_LOG': 'info',
'MDBOOK_SUBSTITUTE_SEARCH': meson.current_build_dir() / 'src',
'MANUAL_SUBSTITUTE_SEARCH': meson.current_build_dir() / 'src',
},
)
manual_md = manual[1]
@@ -0,0 +1,13 @@
---
synopsis: "Nix shells' $NIX_BUILD_TOP are shorter"
cls: [4663]
issues: [fj#1044]
category: "Fixes"
credits: [raito]
---
Following the changes in 2.94.0 to shorten build directory paths, aimed at [resolving UNIX domain socket length issues](https://gerrit.lix.systems/c/lix/+/4168/13) and [improving nix-shell](https://git.lix.systems/lix-project/lix/issues/940), we inadvertently introduced an excessively long path for the `$NIX_BUILD_TOP` environment variable used by Nix shells (their effective temporary `/build` directory).
To fix this, we replaced the `build-top-$HASH` directory name with simply `build-top`, reducing these paths by at least 30 characters.
We also added a test to ensure that Nix shells do not introduce more than 50 extra characters relative to their base directory (e.g., `/tmp` when `$TMPDIR` is not set).
+15
View File
@@ -0,0 +1,15 @@
---
synopsis: "Add `builtins.warn` for emitting warnings from Nix code"
cls: [2248]
category: "Features"
credits: [milibopp, Qyriad]
---
Lix now has a builtin function for emitting warnings.
Like `builtins.trace`, it takes two arguments: the message to emit, and the expression to return.
_Unlike_ `builtins.trace`, `builtins.warn` requires the first argument — the message — to be a string.
In the future we may extend `builtins.warn` to accept a more structured API.
To go along with this, we also have two new config settings:
- [`debugger-on-warn`](@docroot@/command-ref/conf-file.md#conf-debugger-on-warn), which, when used with `--debugger`, makes `builtins.warn` also function like [`builtins.break`](@docroot@/language/builtins.md#builtins-break).
- [`abort-on-warn`](@docroot@/command-ref/conf-file.md#conf-abort-on-warn), which aborts evaluation entirely after the warning is emitted.
@@ -0,0 +1,13 @@
---
synopsis: "Deprecate shadowing internal files through the Nix search path"
issues: [998]
cls: [4632]
category: "Breaking Changes"
credits: [thubrecht]
---
As Lix uses the path `<nix/fetchurl.nix>` for bootstrapping purposes, the ability to shadow it by adding `nix=/some/path` (or `/other/path` that contains a `nix` directory) to the search path is not desirable.
To alleviate potential issues, Lix now emits a warning when the Nix search path contains potential shadows for internal files, which will be changed to an error in a future release.
The warning can be disabled by enabling the deprecated feature `nix-path-shadow`.
@@ -0,0 +1,10 @@
---
synopsis: "Warn instead of erroring when the final destination of a transfer changes in-flight"
cls: [4641]
issues: [fj#1004]
category: "Miscellany"
credits: [thubrecht]
---
Lix will now emit a warning during downloads where the final destination changes suddently mid-transfer instead of throwing an error.
This transfer behavior has been known to happen very rarely while fetching from some CDNs.
@@ -0,0 +1,10 @@
---
synopsis: 'functional lang migration'
issues: [lix#856]
cls: [3213, 3214, 3215, 3224, 4092, 4093, 4094, 4095, 4096, 4097, 4098, 4099, 4100, 4101, 4102, 4103, 4104, 4105, 4106, 4107, 4108, 4109, 4110, 4111, 4112, 4113, 4114, 4115, 4116, 4117, 4122, 4123, 4269, 4270, 4271, 4272, 4273, 4274, 4347, 4348, 4349, 4350, 4351, 4352, 4569, 4570, 4571, 4572, 4573, 4574, 4596, 4597, 4598, 4599, 4600, 4601, 4602, 4603, 4604, 4605]
category: Development
credits: [piegames, commentator2.0]
---
We have done it! The functional/lang framework has now been fully migrated to functional2/lang.
This means: no more `just clean` and `just install` mess and whatever because one removed a test.
The lang test suite is also getting a face lift, with an improved folder structure and restructuring of many tests.
+10
View File
@@ -0,0 +1,10 @@
---
synopsis: "Allow remote builders to be configured using TOML"
cls: [4533]
category: "Features"
credits: [commentator2.0, Qyriad]
---
Lix now supports configuring remote builders using a TOML file instead of the old, very cursed and incomprehensible format.
This comes with not only a human-understandable file, but also with better messages and error reports on misconfiguration.
A more detailed Documentation can be found on the [distributed-builds](@docroot@/advanced-topics/distributed-builds.md) Wiki-page
+8
View File
@@ -0,0 +1,8 @@
---
synopsis: "Default to showing build logs in the new-style (nix3) CLI"
cls: [4674]
category: "Miscellany"
credits: [k900]
---
Lix will now show logs by default, in addition to the progress bar, when invoked through the new-style "nix3" CLI (`nix build`, etc)
@@ -0,0 +1,31 @@
---
synopsis: "Move /root/.cache/nix to /var/cache/nix by default"
cls: [4671]
issues: [fj#634]
category: "Breaking Changes"
credits: [raito]
---
By default, Lix attempts to locate a cache directory for its operations (such
as the narinfo cache) by checking the value of `$XDG_CACHE_DIR`.
However, since the Nix daemon is a system service, using `$XDG_CACHE_DIR` is
not typical in this context.
To address this, systemd provides a better solution. Specifically, when
`CacheDirectory=` is set in the `[Service]` section of a systemd unit, it
automatically sets the `$CACHE_DIRECTORY` environment variable and systemd will
manage that cache directory for us.
Now, our systemd unit includes `CacheDirectory=nix`, which sets the
`$CACHE_DIRECTORY` and takes precedence over `$XDG_CACHE_DIR`.
If the daemon is run under user units, systemd will automatically set
`$XDG_CACHE_DIR`.
If neither of these variables is set, Lix falls back to its default behavior.
By default, Lix will try to find a cache directory for its various operations
(e.g. narinfo cache) by looking into `$XDG_CACHE_DIR`.
In summary, what was stored in `/root/.cache/nix` is now moved to
`/var/cache/nix/nix`.
+9
View File
@@ -0,0 +1,9 @@
---
synopsis: "Add an indication of nix-shell nesting depth"
cls: [4657]
issues: [fj#826]
category: "Improvements"
credits: [thubrecht]
---
When in a nix shell (either via a `nix-shell` or a `nix develop` invocation), a variable `NIX_SHELL_LEVEL` is exported to indicate the nesting depth of nix shells.
+20
View File
@@ -0,0 +1,20 @@
---
synopsis: Derivations can now be printed in detail in `nix repl`
cls: [3842]
category: Improvements
credits: [Lunaphied]
---
Traditionally derivations printed in the REPL would only print a formatted object
representing the path of the derivation file it refers to. This makes inspecting
the enhanced derivation attribute sets encountered from `mkDerivation` or similar
wrappers more difficult. Even the `:p`/`:print` command would not elaborate attribute sets
tagged as a derivation.
With this change you can now use `:p`/`:print` to directly inspect a derivation
by providing one as the top-level object. Derivation attribute sets will only be
printed two levels deep and internal derivation attrsets will remain in unexpanded
path form as before. `drvAttrs` will also be elided as these attributes are already
present in the top-level attribute set of the derivation. These heuristics provide
a balance between readability and functionality. When the `:p`/`:print` is omitted,
a bare derivation is printed in the path format as before.
@@ -0,0 +1,37 @@
---
synopsis: Remove `fetch-closure` experimental feature
issues: [fj#1010]
cls: [4595]
category: "Breaking Changes"
credits: [just1602]
---
The `fetch-closure` experimental feature has been removed.
Outside of allowing the user to import closure from binary cache,
`fetchClosure` also allow you to do the following:
* rewrite non-CA path to CA
* reject non-CA paths at fetching time
* reject CA paths at fetching time
Some people are using those mechanism to prevent users from having to build any
package and force going via the declared cache or as a way to use ancient/old
software without paying the evaluation cost of a second nixpkgs.
Both use cases are somewhat of an antipattern in Nix semantics. If the user
cannot fetch a program directly via the substituter mechanism and fall back to
local build, this is a feature AND a misconfiguration. If the user cannot build
certain derivations because they are too expensive, the build directives should
pass `-j0` or similar.
As for the second usecase, there's a different way to do it that also allows to
have a way to reproduce the paths that are hardcoded in that file, perform
`import (fetchurl "https://my-cache/${hashparts storepath}.drv")` rather, i.e.
an IFD to a possibly well known name. The backend can generate them on the fly
or once, and possess stable names.
Finally, as for the non-CA → CA features, Lix removed ca-derivations.
fetchClosure offers ca-derivations-like features which suffers from similar
shortcomings albeit lessened. It only follows that we should rather deprecate
and remove these capabilities.
@@ -0,0 +1,14 @@
---
synopsis: "Shells supports $NIX_LOG_FD now"
cls: [4694, 4695]
issues: [fj#336]
category: "Improvements"
credits: [raito]
---
Lix's "debugging" shells (`nix3-develop` and `nix-shell`) now supports
`$NIX_LOG_FD` environment variable.
This means that [hook logging in
stdenv](https://github.com/NixOS/nixpkgs/pull/310387) appears while debugging
derivations via `nix3-develop` or `nix-shell`.
@@ -41,106 +41,17 @@ contains Nix.
> If you are building via the Lix daemon (default on Linux and macOS), it is the Lix daemon user account (that is, `root`) that should have SSH access to a user (not necessarily `root`) on the remote machine.
>
> Furthermore, `root` needs to have the public host keys for the remote system in its `.ssh/known_hosts`.
> To add them to `known_hosts` for root, do `ssh-keyscan USER@HOST | sudo tee -a ~root/.ssh/known_hosts`.
> To add them to `known_hosts` for root, do `ssh-keyscan HOST | sudo tee -a ~root/.ssh/known_hosts`.
>
> If you cant or dont want to configure `root` to be able to access the remote machine, you can use a private Nix store instead by passing e.g. `--store ~/my-nix` when running a Nix command from the local machine.
## Configuration
The list of remote machines can be specified on the command line or in
the Lix configuration file. The former is convenient for testing. For
example, the following command allows you to build a derivation for
`x86_64-darwin` on a Linux machine:
```console
$ uname
Linux
$ nix build --impure \
--expr '(with import <nixpkgs> { system = "x86_64-darwin"; }; runCommand "foo" {} "uname > $out")' \
--builders 'ssh://mac x86_64-darwin'
[1/0/1 built, 0.0 MiB DL] building foo on ssh://mac
$ cat ./result
Darwin
```
It is possible to specify multiple builders separated by a semicolon or
a newline, e.g.
```console
--builders 'ssh://mac x86_64-darwin ; ssh://beastie x86_64-freebsd'
```
Each machine specification consists of the following elements, separated
by spaces. Only the first element is required. To leave a field at its
default, set it to `-`.
1. The URI of the remote store in the format
`ssh://[username@]hostname[?port=<port>]`, e.g. `ssh://nix@mac` or `ssh://mac`.
If the ssh server is not listening on port 22 (e.g. port 1337 in this case)
the URI would be `ssh://nix@mac?port=1337`
For backward compatibility, `ssh://` may be omitted. The hostname
may be an alias defined in your `~/.ssh/config`.
2. A comma-separated list of Nix platform type identifiers, such as
`x86_64-darwin`. It is possible for a machine to support multiple
platform types, e.g., `i686-linux,x86_64-linux`. If omitted, this
defaults to the local platform type.
3. The SSH identity file to be used to log in to the remote machine. If
omitted, SSH will use its regular identities.
4. The maximum number of builds that Lix will execute in parallel on
the machine. Typically this should be equal to the number of CPU
cores. For instance, the machine `itchy` in the example will execute
up to 8 builds in parallel.
5. The “speed factor”, indicating the relative speed of the machine. If
there are multiple machines of the right type, Lix will prefer the
fastest, taking load into account.
6. A comma-separated list of *supported features*. If a derivation has
the `requiredSystemFeatures` attribute, then Lix will only perform
the derivation on a machine that has the specified features. For
instance, the attribute
```nix
requiredSystemFeatures = [ "kvm" ];
```
will cause the build to be performed on a machine that has the `kvm`
feature.
7. A comma-separated list of *mandatory features*. A machine will only
be used to build a derivation if all of the machines mandatory
features appear in the derivations `requiredSystemFeatures`
attribute.
8. The (base64-encoded) public host key of the remote machine. If omitted, SSH
will use its regular known-hosts file. Specifically, the field is calculated
via `base64 -w0 /etc/ssh/ssh_host_ed25519_key.pub`.
For example, the machine specification
nix@scratchy.labs.cs.uu.nl i686-linux /home/nix/.ssh/id_scratchy_auto 8 1 kvm
nix@itchy.labs.cs.uu.nl i686-linux /home/nix/.ssh/id_scratchy_auto 8 2
nix@poochie.labs.cs.uu.nl i686-linux /home/nix/.ssh/id_scratchy_auto 1 2 kvm benchmark
specifies several machines that can perform `i686-linux` builds.
However, `poochie` will only do builds that have the attribute
```nix
requiredSystemFeatures = [ "benchmark" ];
```
or
```nix
requiredSystemFeatures = [ "benchmark" "kvm" ];
```
`itchy` cannot do builds that require `kvm`, but `scratchy` does support
such builds. For regular builds, `itchy` will be preferred over
`scratchy` because it has a higher speed factor.
the Lix configuration file. The former is convenient for testing.
Additionally, there are two supported formats to configure remote builders:
The legacy, "space"-separated format and starting with Lix 2.95.0, a TOML.
Remote builders can also be configured in `nix.conf`, e.g.
@@ -159,3 +70,180 @@ option `builders-use-substitutes` in your local `nix.conf`.
To build only on remote builders and disable building on the local
machine, you can use the option `--max-jobs 0`.
---
Each machine specification consists of the following attributes.
How those are combined within the configuration file differs for the formats, and will be explained further down.
1. `uri` (**required**)
The URI of the remote store in the format
`ssh[-ng]://[username@]hostname[?port=<port>]`, e.g. `ssh://nix@mac` or `ssh://mac`.
If the ssh server is not listening on port 22 (e.g. port 1337 in this case)
the URI would be `ssh[-ng]://nix@mac?port=1337`. The hostname
may be an alias defined in your `~/.ssh/config`.
2. `system-types` (**optional**)
A list of Nix platform type identifiers, such as
`x86_64-darwin`. It is possible for a machine to support multiple
platform types, e.g., `i686-linux` and `x86_64-linux`.
Defaults to the local platform type
3. `ssh-key` (**optional**)
The SSH identity file to be used to log in to the remote machine.
Defaults to SSHs regular identities.
4. `jobs` (**optional**)
The maximum number of builds that Lix will execute in parallel on
the machine. Typically, this should be equal to the number of CPU
cores divided by the cores within the target machines configuration, i.e. `jobs * cores ~= cpu cores`
Defaults to 1; must be a positive integer.
5. `speed-factor`
The “speed factor”, indicating the relative speed of the machine. If
there are multiple machines of the right type, Lix will prefer the
fastest, taking load into account.
Defaults to 1; must be a positive float.
6. `supported-features` (**optional**)
A list of *supported features*. If a derivation has
the `requiredSystemFeatures` attribute, then Lix will only schedule
the derivation on a machine that has the specified features. For
example, the attribute
```nix
requiredSystemFeatures = [ "kvm" ];
```
will cause the build to be performed on a machine that has the `kvm`
feature.
Defaults to an empty list.
7. `mandatory-features` (**optional**)
A list of *mandatory features*. A machine will only
be used to build a derivation if all the machines mandatory
features appear in the derivations `requiredSystemFeatures`
attribute.
Defaults to an empty list.
8. `ssh-public-host-key` (**optional**)
The public host key of the remote machine.
Defaults to basic ssh behavior (checking contests of the known-hosts file)
### Using a TOML configuration
Each machine is configured as an attribute within the map called `machines`.
The attributes name is the machines name.
Attributes can be in any order.
For example:
```toml
version = 1
[machines.andesite]
uri = "ssh://lix@andesite.lix.systems" # toml also allows for comments
system-types = ["i686-linux"]
jobs = 8
speed-factor = 1.0
supported-features = ["kvm"]
ssh-key = "/home/deepslate/.ssh/id_ed25519"
[machines.diorite]
uri = "ssh://lix@diorite.lix.systems"
system-types = ["i686-linux"]
jobs = 8
speed-factor = 2.0
ssh-key = "/home/deepslate/.ssh/id_ed25519"
[machines.granite]
uri = "ssh://lix@granite.lix.systems"
system-types = ["i686-linux"]
jobs = 1
speed-factor = 2.0
supported-features = ["kvm", "benchmark"]
ssh-key = "/home/deepslate/.ssh/id_ed25519"
[machines.legacy]
uri = "ssh://nix@nix-15-11.nixos.org"
enable = false
```
> **Note**
>
> If the version tag is omitted (e.g. in the CLI), it defaults to the latest version.
> It is strongly recommended to always provide a version tag for configuration within files to avoid breakage.
For testing purposes, one can also define a builder ad hoc on the CLI as follows:
`--builders 'machines.andesite = {uri = "ssh://lix@andesite.lix.systems", jobs = 8}'`
#### Special handling of fields
- `enable` (**optional**)
If set to false, the declared machine will not be loaded.
This allows one to statically disable machines.
Defaults to true
### Using the legacy format
> **Warning**
>
> This format is frozen and new features / configuration options will not be backported to this format.
It is possible to specify multiple builders separated by a semicolon or
a newline, e.g.
```console
--builders 'ssh://mac x86_64-darwin ; ssh://beastie x86_64-freebsd'
```
Every machine specification consists of the elements listed in the section above, seperated by any amount of spaces or tabs.
The Attributes need to be provided **in order** and without names.
To leave a field at its default, set it to `-`.
Lists are colon seperated, without additional spaces.
```
lix@andesite.lix.systems i686-linux /home/deepslate/.ssh/id_ed25519 8 1 kvm
lix@diorite.lix.systems i686-linux /home/deepslate/.ssh/id_ed25519 8 2
lix@granite.lix.systems i686-linux /home/deepslate/.ssh/id_ed25519 1 2 kvm benchmark
```
#### Special handling of fields
- `uri`: Due to backward compatibility, the `ssh://` may be omitted for the store-uri.
- `ssh-public-host-key`: The key must be provided encoded in base64. Specifically calculated via `base64 -w0 /etc/ssh/ssh_host_ed25519_key.pub`
### Format detection
At first, the given configuration is being parsed syntactically as a toml.
If parsing fails and the given configuration contains a `"` the error is presented to the user, as those characters are necessary for TOML, but disallowed for the legacy format.
Otherwise, parsing is retried using the legacy format.
If non-syntactic errors are detected within the toml, the exception will always be shown to the user directly.
## Builder selection
The configuration(s) above specify several machines that can perform `i686-linux` builds.
However, `granite` will only do builds that have the attribute
```nix
requiredSystemFeatures = [ "benchmark" ];
```
or
```nix
requiredSystemFeatures = [ "benchmark" "kvm" ];
```
`diorite` cannot do builds that require `kvm`, but `andesite` does support
such builds. For regular builds, `diorite` will be preferred over
`andesite` because it has a higher speed factor.
+15
View File
@@ -141,6 +141,21 @@ To inspect the canonical source of truth on what the state of the buildsystem co
$ meson introspect
```
#### LLD
The development shell on Linux uses LLD by default for faster link times.
This is set using `mesonFlags`, so to override it, you can simplify re-specify the linker to Meson:
```bash
$ just setup-custom -Dc_link_args=-fuse-ld=ld -Dcpp_link_args=-fuse-ld=ld
```
While using LLD, you may find it helpful to use ThinLTO for even further improvements to link times for incremental builds:
```bash
$ just setup-custom -Db_lto=true -Db_lto_mode=thin -Db_thinlto_cache=true
```
## Sending changes to Gerrit for review {#sending-to-gerrit}
We use Gerrit for all our code review in Lix.
-1
View File
@@ -401,7 +401,6 @@ I grepped `lix/` for `get[eE]nv\("` to find the mentions in Lix code.
**Expected value**: the path to an executable shell
- `PRINT_PATH` - Undocumented. Used by `nix-prefetch-url` as an alternative form of `--print-path`. Why???
- `_NIX_IN_TEST` - If present with any value, makes `fetchClosure` accept file URLs in addition to HTTP ones. Why is this not `_NIX_FORCE_HTTP`??
Not used anywhere else.
- `NIX_ALLOW_EVAL` - Used by eval-cache tests to block evaluation if set to `0`.
@@ -5,7 +5,7 @@
FIXME(Lix): This section does not document the most common modern practices in terms of avoiding channels, pinning, declarative software installation (see flakey-profile or home-manager or NixOS), or using flakes, etc.
It is, however, likely correct at a technical level.
For more information on modern practices, see the [resources](https://lix.systems/resources) page on the Lix site.
For more information on modern practices, see the [resources](https://wiki.lix.systems/books/lix-users/page/nix-resources) page on the Lix site.
</div>
+7 -2
View File
@@ -70,10 +70,15 @@ def do_include(content: str, relative_md_path: Path, source_root: Path, search_p
def recursive_replace(data, book_root, search_path):
match data:
# XXX FUTURE: drop sections once mdBook is at 0.5.0 or above in nixpkgs
case {'sections': sections}:
return data | dict(
sections = [recursive_replace(section, book_root, search_path) for section in sections],
)
case {'items': items}:
return data | dict(
items = [recursive_replace(item, book_root, search_path) for item in items],
)
case {'Chapter': chapter}:
path_to_chapter = Path(chapter['path'])
chapter_content = chapter['content']
@@ -119,10 +124,10 @@ def main():
context, book = json.load(sys.stdin)
# book_root is the directory where book contents leave (ie, src/)
book_root = Path(context['root']) / context['config']['book']['src']
book_root = Path(context['root']) / context['config']['book'].get('src', 'src')
# includes pointing into @generated@ will look here
search_path = Path(os.environ['MDBOOK_SUBSTITUTE_SEARCH'])
search_path = Path(os.environ['MANUAL_SUBSTITUTE_SEARCH'])
# Find @var@ in all parts of our recursive book structure.
replaced_content = recursive_replace(book, book_root, search_path)
+17 -6
View File
@@ -222,9 +222,13 @@
busybox-sandbox-shell = final.busybox-sandbox-shell or final.default-busybox-sandbox-shell;
};
lix-clang-tidy = final.callPackage ./subprojects/lix-clang-tidy { };
lix-clang-tidy = final.callPackage ./subprojects/lix-clang-tidy {
# FIXME: To be removed when switching to nixos-25.11-small
llvmPackages = final.llvmPackages_20;
};
nix-eval-jobs = final.callPackage ./subprojects/nix-eval-jobs {
stdenv = currentStdenv;
srcDir = ./subprojects/nix-eval-jobs;
};
@@ -258,7 +262,9 @@
sha512 = "2a4d0rqh8gkw4ca3gkzddp0hjpmmw74cbks8k0inhh0vizmgbn188zdv6m1kgmr019b99g7insli8js3ci1ji7y4n5nk704bswf3z3i";
};
nativeBuildInputs = prevAttrs.nativeBuildInputs ++ [ final.buildPackages.bmake ];
postInstall = lib.replaceStrings [ "lowdown.so.1" ] [ "lowdown.so.2" ] prevAttrs.postInstall;
postInstall = lib.replaceStrings [ "lowdown.so.1" ] [ "lowdown.so.2" ] (
prevAttrs.postInstall or ""
);
});
capnproto = prev.capnproto.overrideAttrs (old: {
@@ -427,17 +433,20 @@
'';
# clang-tidy run against the Lix codebase using the Lix clang-tidy plugin
clang-tidy =
clang-tidy = forAllSystems (
system:
let
nixpkgs = nixpkgsFor.x86_64-linux.native;
inherit (nixpkgs) pkgs;
pkgs = nixpkgsFor.${system}.native;
in
pkgs.callPackage ./package.nix {
# Required since we don't support gcc stdenv
stdenv = pkgs.clangStdenv;
# FIXME: To be removed when switching to nixos-25.11-small
llvmPackages = pkgs.llvmPackages_20;
versionSuffix = "";
lintInsteadOfBuild = true;
};
}
);
# Make sure that nix-env still produces the exact same result
# on a particular version of Nixpkgs.
@@ -582,6 +591,8 @@
inherit stdenv versionSuffix;
busybox-sandbox-shell = pkgs.busybox-sandbox-shell or pkgs.default-busybox-sandbox;
internalApiDocs = false;
# Use LLD in the dev shell by default for faster link times.
useLld = stdenv.hostPlatform.isLinux;
};
pre-commit = self.hydraJobs.pre-commit.${pkgs.system} or { };
in
+1 -1
View File
@@ -527,7 +527,7 @@ kj::Promise<void> AcceptedBuild::runImpl(RunContext context)
AIO().timeoutAfter(15 * kj::MINUTES, lockFileAsync(uploadLock.get(), ltWrite))
);
if (!result) {
printError("somebody is hogging the upload lock for '%s', continuing...");
printError("somebody is hogging the upload lock for '%s', continuing...", storeUri);
}
}
+18 -1
View File
@@ -25,6 +25,7 @@
#include "lix/libutil/shlex.hh"
#include "nix-build.hh"
#include "lix/libstore/temporary-dir.hh"
#include "lix/libutil/strings.hh"
extern char * * environ __attribute__((weak)); // Man what even is this
@@ -191,7 +192,11 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
throw UsageError("'-p' and '-E' are mutually exclusive");
AutoDelete tmpDir(createTempDir(myName));
AutoDelete buildTopTmpDir(createTempSubdir(tmpDir, "build-top"));
// NOTE: we assume there's no `build-top` directory created inside of `tmpDir` and we have
// ownership of this.
auto buildTopTmpDir = tmpDir + "/build-top";
createDirs(buildTopTmpDir);
if (outLink.empty())
outLink = (Path) tmpDir + "/result";
@@ -425,6 +430,18 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
env["__ETC_PROFILE_SOURCED"] = "1";
}
// Set NIX_SHELL_LEVEL
env["NIX_SHELL_LEVEL"] = std::to_string(
getEnvNonEmpty("NIX_SHELL_LEVEL")
.and_then([](std::string lvl) { return string2Int<size_t>(lvl); })
.value_or(0)
+ 1
);
// We re-export similarly to what occurs inside of a derivation goal `NIX_LOG_FD` to stderr.
// So that stdenv hooks that logs information can be observed inside this debugging tool.
env["NIX_LOG_FD"] = "2";
// 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);
+7 -18
View File
@@ -64,7 +64,7 @@ static int main_nix_collect_garbage(AsyncIoRoot & aio, std::string programName,
{
bool removeOld = false;
GCOptions options;
GCOptions options = {.action = GCOptions::gcDeleteDead};
LegacyArgs(aio, programName, [&](Strings::iterator & arg, const Strings::iterator & end) {
if (*arg == "--help")
@@ -75,12 +75,13 @@ static int main_nix_collect_garbage(AsyncIoRoot & aio, std::string programName,
else if (*arg == "--delete-older-than") {
removeOld = true;
deleteOlderThan = getArg(*arg, arg, end);
}
else if (*arg == "--dry-run") dryRun = true;
else if (*arg == "--max-freed")
} else if (*arg == "--dry-run") {
options.action = GCOptions::gcReturnDead;
} else if (*arg == "--max-freed") {
options.maxFreed = std::max(getIntArg<int64_t>(*arg, arg, end, true), (int64_t) 0);
else
} else {
return false;
}
return true;
}).parseCmdline(argv);
@@ -92,24 +93,12 @@ static int main_nix_collect_garbage(AsyncIoRoot & aio, std::string programName,
}
// Run the actual garbage collector.
if (!dryRun) {
options.action = GCOptions::gcDeleteDead;
} else {
options.action = GCOptions::gcReturnDead;
}
auto store = aio.blockOn(openStore());
auto & gcStore = require<GcStore>(*store);
GCResults results;
PrintFreed freed(true, results);
PrintFreed freed(options.action, results);
aio.blockOn(gcStore.collectGarbage(options, results));
if (dryRun) {
// Only print results for dry run; when !dryRun, paths will be printed as they're deleted.
for (auto & i : results.paths) {
printInfo("%s", Uncolored(i));
}
}
return 0;
}
}
+2 -6
View File
@@ -711,12 +711,8 @@ static void opGC(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlag
}
else {
PrintFreed freed(options.action == GCOptions::gcDeleteDead, results);
PrintFreed freed(options.action, results);
aio.blockOn(gcStore.collectGarbage(options, results));
if (options.action != GCOptions::gcDeleteDead)
for (auto & i : results.paths)
cout << i << std::endl;
}
}
@@ -749,7 +745,7 @@ opDelete(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strin
auto & gcStore = require<GcStore>(*store);
GCResults results;
PrintFreed freed(true, results);
PrintFreed freed(options.action, results);
aio.blockOn(gcStore.collectGarbage(options, results));
}
+9 -3
View File
@@ -75,6 +75,15 @@ CopyCommand::CopyCommand()
});
}
void CopyCommand::run()
{
if (requireStore && srcUri.empty() && dstUri.empty()) {
throw UsageError("you must pass '--from' and/or '--to'");
}
StoreCommand::run();
}
ref<Store> CopyCommand::createStore(AsyncIoRoot & in)
{
return srcUri.empty() ? StoreCommand::createStore(in) : in.blockOn(openStore(srcUri));
@@ -82,9 +91,6 @@ ref<Store> CopyCommand::createStore(AsyncIoRoot & in)
ref<Store> CopyCommand::getDstStore()
{
if (srcUri.empty() && dstUri.empty())
throw UsageError("you must pass '--from' and/or '--to'");
return aio().blockOn(dstUri.empty() ? openStore() : openStore(dstUri));
}
+2 -2
View File
@@ -15,8 +15,6 @@ namespace nix {
extern std::string programPath;
extern char * * savedArgv;
class EvalState;
struct Pos;
class Store;
@@ -56,9 +54,11 @@ private:
struct CopyCommand : virtual StoreCommand
{
std::string srcUri, dstUri;
bool requireStore = true;
CopyCommand();
void run() override;
ref<Store> createStore(AsyncIoRoot & in) override;
ref<Store> getDstStore();
+17 -3
View File
@@ -5,18 +5,32 @@
#include <functional>
#include <list>
#include <map>
#include <span>
#include <string>
namespace nix {
typedef std::function<int(AsyncIoRoot &, std::string, std::list<std::string>)> MainFunction;
struct LegacyCommandRegistry
{
using LegacyCommandMap = std::map<std::string, MainFunction>;
typedef std::function<int(AsyncIoRoot &, std::string, std::list<std::string>)> MainFunction;
typedef std::function<
int(AsyncIoRoot &, std::string, std::list<std::string>, std::span<char *>)>
RawMainFunction;
using LegacyCommandMap = std::map<std::string, RawMainFunction>;
static LegacyCommandMap * commands;
static void add(const std::string & name, MainFunction fun)
{
addWithRaw(
name,
[fun](AsyncIoRoot & aio, std::string name, std::list<std::string> args, std::span<char *>) {
return fun(aio, name, args);
}
);
}
static void addWithRaw(const std::string & name, RawMainFunction fun)
{
if (!commands) commands = new LegacyCommandMap;
(*commands)[name] = fun;
+1 -1
View File
@@ -48,7 +48,7 @@ char ** copyCompletions(const StringSet& possible)
if (vp) {
while (--ac >= 0)
free(vp[ac]);
free(vp);
free(static_cast<void *>(vp));
}
throw Error("allocation failure");
}
+1 -1
View File
@@ -46,7 +46,7 @@ public:
*
* This function logs but ignores errors from readline's write_history().
*/
virtual void writeHistory();
void writeHistory();
virtual ~ReadlineLikeInteracter() override;
};
+6 -3
View File
@@ -209,12 +209,13 @@ struct NixRepl
void printValue(std::ostream & str,
Value & v,
unsigned int maxDepth = std::numeric_limits<unsigned int>::max())
unsigned int maxDepth = std::numeric_limits<unsigned int>::max(),
unsigned int derivationPathDepth = 0)
{
::nix::printValue(state, str, v, PrintOptions {
.ansiColors = true,
.force = true,
.derivationPaths = true,
.derivationPathDepth = derivationPathDepth,
.maxDepth = maxDepth,
.prettyIndent = 2,
.errors = ErrorPrintBehavior::ThrowTopLevel,
@@ -793,8 +794,10 @@ ProcessLineResult NixRepl::processLine(std::string line)
evalString(arg, v);
if (v.type() == nString) {
std::cout << v.str();
} else if (v.type() == nAttrs && state.isDerivation(v)) {
printValue(std::cout, v, 2, 1);
} else {
printValue(std::cout, v);
printValue(std::cout, v, std::numeric_limits<unsigned int>::max(), 0);
}
std::cout << std::endl;
}
+3 -3
View File
@@ -97,6 +97,8 @@ public:
res.reserve(size_);
for (Size n = 0; n < size_; n++)
res.emplace_back(&attrs[n]);
// NOTE: this sort uses a total order, so the iteration over pointers is not an issue
// NOLINTNEXTLINE(bugprone-nondeterministic-pointer-iteration-order)
std::sort(res.begin(), res.end(), [&](const Attr * a, const Attr * b) {
std::string_view sa = symbols[a->name], sb = symbols[b->name];
return sa < sb;
@@ -119,7 +121,6 @@ public:
private:
Bindings * bindings;
EvalMemory & mem;
SymbolTable & symbols;
Size capacity;
@@ -127,9 +128,8 @@ public:
// needed by std::back_inserter
using value_type = Attr;
BindingsBuilder(EvalMemory & mem, SymbolTable & symbols, Bindings * bindings, Size capacity)
BindingsBuilder(SymbolTable & symbols, Bindings * bindings, Size capacity)
: bindings(bindings)
, mem(mem)
, symbols(symbols)
, capacity(capacity)
{
+96
View File
@@ -0,0 +1,96 @@
---
name: addErrorContext
args: [message, expr]
---
This adds a `message` to be shown in the stacktrace in the event of
a failure during the evaluation of `expr`.
For example, if a file `err.nix` contains the following:
```nix
let
countDown =
n:
if n == 0 then
throw "kaboom"
else
builtins.addErrorContext "while counting down; n = ${toString n}" ("x" + countDown (n - 1));
in
countDown 2
```
Then, evaluating the file will give the following stack trace:
```console
$ nix-instantiate --show-trace err.nix
error:
… from call site
at /home/plop/git.lix.systems/lix-project/lix/err.nix:9:1:
8| in
9| countDown 2
| ^
10|
… while calling 'countDown'
at /home/plop/git.lix.systems/lix-project/lix/err.nix:3:5:
2| countDown =
3| n:
| ^
4| if n == 0 then
… while calling the 'addErrorContext' builtin
at /home/plop/git.lix.systems/lix-project/lix/err.nix:7:7:
6| else
7| builtins.addErrorContext "while counting down; n = ${toString n}" ("x" + countDown (n - 1));
| ^
8| in
… while counting down; n = 2
… from call site
at /home/plop/git.lix.systems/lix-project/lix/err.nix:7:80:
6| else
7| builtins.addErrorContext "while counting down; n = ${toString n}" ("x" + countDown (n - 1));
| ^
8| in
… while calling 'countDown'
at /home/plop/git.lix.systems/lix-project/lix/err.nix:3:5:
2| countDown =
3| n:
| ^
4| if n == 0 then
… while calling the 'addErrorContext' builtin
at /home/plop/git.lix.systems/lix-project/lix/err.nix:7:7:
6| else
7| builtins.addErrorContext "while counting down; n = ${toString n}" ("x" + countDown (n - 1));
| ^
8| in
… while counting down; n = 1
… from call site
at /home/plop/git.lix.systems/lix-project/lix/err.nix:7:80:
6| else
7| builtins.addErrorContext "while counting down; n = ${toString n}" ("x" + countDown (n - 1));
| ^
8| in
… while calling 'countDown'
at /home/plop/git.lix.systems/lix-project/lix/err.nix:3:5:
2| countDown =
3| n:
| ^
4| if n == 0 then
… caused by explicit throw
at /home/plop/git.lix.systems/lix-project/lix/err.nix:5:7:
4| if n == 0 then
5| throw "kaboom"
| ^
6| else
error: kaboom
```
+6
View File
@@ -0,0 +1,6 @@
---
name: appendContext
args: [s, ctx]
---
Appends the attribute set `ctx` as a context to the string `s`, see [`getContext`](#builtins-getContext) for details on the format of the context.
+22
View File
@@ -0,0 +1,22 @@
---
name: derivationStrict
args: [args]
renameInGlobalScope: false
---
Constructs a [store derivation](../glossary.md#gloss-store-derivation) from the attribute set `args`
(c.f. [derivation](#builtins-derivation). Unlike `derivation` the produced store derivation is placed
in the store *immediately* when this builtin is called, while `derivation` may defer placing store
derivations in the store until it is proven that they are used.
It then returns a new attrset with *only* the following attributes:
- `drvPath` containing the path of the store derivation;
- For each output of the derivation (`out`, `dev`, etc): an attribute named after that output containing the output path
> **Note**
>
> In contrast to [`builtins.derivation`](#builtins-derivation), this computes
> the derivation set in a fully *strict* manner, i.e. the values of the attributes
> directly computed, whereas using `builtins.derivation` will produce an attrset
> whose values will be evaluated when they are used at a later point.
-72
View File
@@ -1,72 +0,0 @@
---
name: fetchClosure
args: [args]
experimentalFeature: fetch-closure
---
Fetch a store path [closure](@docroot@/glossary.md#gloss-closure) from a binary cache, and return the store path as a string with context.
This function can be invoked in three ways, that we will discuss in order of preference.
**Fetch a content-addressed store path**
Example:
```nix
builtins.fetchClosure {
fromStore = "https://cache.nixos.org";
fromPath = /nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1;
}
```
This is the simplest invocation, and it does not require the user of the expression to configure [`trusted-public-keys`](@docroot@/command-ref/conf-file.md#conf-trusted-public-keys) to ensure their authenticity.
If your store path is [input addressed](@docroot@/glossary.md#gloss-input-addressed-store-object) instead of content addressed, consider the other two invocations.
**Fetch any store path and rewrite it to a fully content-addressed store path**
Example:
```nix
builtins.fetchClosure {
fromStore = "https://cache.nixos.org";
fromPath = /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1;
toPath = /nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1;
}
```
This example fetches `/nix/store/r2jd...` from the specified binary cache,
and rewrites it into the content-addressed store path
`/nix/store/ldbh...`.
Like the previous example, no extra configuration or privileges are required.
To find out the correct value for `toPath` given a `fromPath`,
use [`nix store make-content-addressed`](@docroot@/command-ref/new-cli/nix3-store-make-content-addressed.md):
```console
# nix store make-content-addressed --from https://cache.nixos.org /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1
rewrote '/nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1' to '/nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1'
```
Alternatively, set `toPath = ""` and find the correct `toPath` in the error message.
**Fetch an input-addressed store path as is**
Example:
```nix
builtins.fetchClosure {
fromStore = "https://cache.nixos.org";
fromPath = /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1;
inputAddressed = true;
}
```
It is possible to fetch an [input-addressed store path](@docroot@/glossary.md#gloss-input-addressed-store-object) and return it as is.
However, this is the least preferred way of invoking `fetchClosure`, because it requires that the input-addressed paths are trusted by the Lix configuration.
**`builtins.storePath`**
`fetchClosure` is similar to [`builtins.storePath`](#builtins-storePath) in that it allows you to use a previously built store path in a Nix expression.
However, `fetchClosure` is more reproducible because it specifies a binary cache from which the path can be fetched.
Also, using content-addressed store paths does not require users to configure [`trusted-public-keys`](@docroot@/command-ref/conf-file.md#conf-trusted-public-keys) to ensure their authenticity.
+48
View File
@@ -0,0 +1,48 @@
---
name: fetchMercurial
args: [args]
renameInGlobalScope: false
---
Fetch a Mercurial repository. *args* can be a URL, in which case the default
branch of the repo at that URL is fetched. Otherwise, it can be an
attribute with the following attributes (all except `url` optional):
- `url`
The URL of the repo.
- `name` (default: `"source"`)
The name of the directory the repo should be exported to in the store.
- `rev`
The revision to fetch.
This is typically a commit hash.
> **Note**
>
> Currently, `rev` can either contain a revision or a branch/tag name.
The return value is an attrset containing the following keys:
- `outPath` (`string`)
Resulting store path of the fetch process.
- `branch` (`string`)
The branch of the fetch repository.
- `rev` (`string`)
The revision that was fetched.
- `revCount` (`int`)
The number of revsets for this branch.
- `shortRev` (`string`)
The first *12* characters of `rev`.
+59
View File
@@ -0,0 +1,59 @@
---
name: scopedImport
implementation: "[](EvalState & state, Value ** args, Value & v) { import(state, *args[1], args[0], v); }"
args: [scope, path]
renameInGlobalScope: false
---
> **Warning**
>
> This builtin's use is heavily discouraged, it has many drawbacks and may be removed
> in a future version of Lix.
Functions like [`import`](#builtins-import) with the exception that
it takes a `scope`, which is a set of attributes to be added to the
lexical scope of the expression.
This essentially allows overriding the ambient builtin variables.
For example, if `foo.nix` is a file containing the following content:
```nix
x
```
then the following expression
```nix
scopedImport { x = 1; } ./foo.nix
```
will evaluate to `1`.
Another application is overriding builtin functions or constants, e.g. to
trace all calls to `map`, one can do:
```nix
let
overrides = {
map = f: xs: builtins.trace "map called!" (map f xs);
# Ensure that our override gets propagated by calls to
# import/scopedImport.
import = fn: scopedImport overrides fn;
scopedImport = attrs: fn: scopedImport (overrides // attrs) fn;
# Also update builtins.
builtins = builtins // overrides;
};
in scopedImport overrides ./bla.nix
```
Similarly, it can be used to extend the set of builtin functions.
> **Warning**
>
> One of the downsides of `scopedImport` is that it bypasses the evaluation cache.
> This means that importing a file multiple times will lead to multiple expensive
> parsings and evaluations.
-2
View File
@@ -13,5 +13,3 @@ in a new path (e.g. `/nix/store/ld01dnzc…-source-source`).
Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).
Lix may change this, tracking issue: <https://git.lix.systems/lix-project/lix/issues/402>
See also [`builtins.fetchClosure`](#builtins-fetchClosure).
@@ -0,0 +1,33 @@
---
name: unsafeDiscardStringContext
args: [s]
---
Returns a copy of the string `s` with all string context associated with `s` removed.
Since string context is used for dependency tracking the returned string will also have
*no dependencies* on store objects, even when the original string `s` had such dependencies.
This function is mainly useful when discarding dependencies is explicitly required, e.g.
to produce a string listing all inputs of a derivation without propagating these inputs
as dependencies into all *users* of the listing. For example, the derivation `dep` in the
following example will pull `hello` into its closure despite never using it while `nodep`
will not:
```nix
dep = runCommand "dep" {
inherit hello;
} "echo hello is at $hello >$out";
nodep = runCommand "nodep" {
hello = builtins.unsafeDiscardStringContext hello;
} "echo hello is at $hello >$out";
```
This behavior also makes this function unsafe: if `s` contains the path of a
store object that is not present in the store then any use of `s` in a
derivation tree will attempt to realize that path in the store, but no use
of `unsafeDiscardStringContext s` will. This can lead to derivation outputs that
refer to paths that were never created.
Lix cannot determine whether such reference are safe or not and must pass
this obligation to the user.
+7
View File
@@ -0,0 +1,7 @@
---
name: warn
args: [msg, e2]
---
Evaluate string *msg* and print it on standard error. Then return *e2*.
This function is useful for warning about unexpected conditions without aborting evaluation.
If the [`debugger-on-warn`](@docroot@/command-ref/conf-file.md#conf-debugger-on-trace) option is set to `true` and the `--debugger` flag is given, the interactive debugger will be started when `warn` is called (like [`break`](@docroot@/language/builtins.md#builtins-break)).
+70 -4
View File
@@ -4,12 +4,14 @@
#include "lix/libutil/archive.hh"
#include "lix/libutil/ansicolor.hh"
#include "lix/libutil/async.hh"
#include "lix/libutil/deprecated-features.hh"
#include "lix/libutil/error.hh"
#include "lix/libutil/english.hh"
#include "lix/libutil/fmt.hh"
#include "lix/libexpr/primops.hh"
#include "lix/libexpr/print-options.hh"
#include "lix/libmain/shared.hh"
#include "lix/libutil/logging.hh"
#include "lix/libutil/suggestions.hh"
#include "lix/libutil/types.hh"
#include "lix/libstore/store-api.hh"
@@ -208,9 +210,9 @@ void initLibExpr()
(resident) memory to be allocated. This might be a problem on
systems that don't overcommit. */
if (!getEnv("GC_INITIAL_HEAP_SIZE")) {
int64_t size = 32 * 1024 * 1024;
int64_t size = 32l * 1024 * 1024;
#if HAVE_SYSCONF && defined(_SC_PAGESIZE) && defined(_SC_PHYS_PAGES)
int64_t maxSize = 384 * 1024 * 1024;
int64_t maxSize = 384l * 1024 * 1024;
int64_t pageSize = sysconf(_SC_PAGESIZE);
int64_t pages = sysconf(_SC_PHYS_PAGES);
if (pageSize != -1) {
@@ -289,14 +291,14 @@ EvalMemory::EvalMemory()
{
assert(libexprInitialised);
#if HAVE_BOEHMGC
GC_add_roots(gcCache, gcCache + CACHES);
GC_add_roots(static_cast<void *>(gcCache), static_cast<void *>(gcCache + CACHES));
#endif
}
EvalMemory::~EvalMemory()
{
#if HAVE_BOEHMGC
GC_remove_roots(gcCache, gcCache + CACHES);
GC_remove_roots(static_cast<void *>(gcCache), static_cast<void *>(gcCache + CACHES));
#endif
}
@@ -347,6 +349,61 @@ EvalPaths::EvalPaths(
allowPath(path);
}
}
#if LIX_MAJOR >= 3 || (LIX_MAJOR == 2 && LIX_MINOR >= 96)
#warning \
"The feature nix-path-shadow was deprecated in 2.95 with a warning, which needs to be turned into an error in 2.96"
#endif
if (!featureSettings.isEnabled(DeprecatedFeature::NixPathShadow)) {
for (auto & [prefix, path] : searchPath_.elements) {
// Match on the 'nix' prefix
if (prefix.s == "nix") {
logWarning(
{.msg = HintFmt(
"The prefix '%s' is reserved for internal use by Lix in the Nix search "
"path, its usage is deprecated and will be forbidden in the future.\n"
"Use %s to silence this warning.\n"
"This is due to adding '%s=%s' in the Nix search path, either through the "
"environment variable '%s' or by passing the flag %s to the nix "
"invocation.",
"nix",
"--extra-deprecated-features nix-path-shadow",
prefix.s,
path.s,
"NIX_PATH",
"-I"
)}
);
} else
// Match prefixless paths that contain a `nix` directory
if (auto res =
prefix.suffixIfPotentialMatch("nix").and_then([&](std::string_view s) {
return aio.blockOn(resolveSearchPathPath(path))
.and_then([&](std::string r) {
Path res = s.length() ? concatStrings(r, "/", s): r;
return pathExists(res) ? std::optional(res) : std::nullopt;
});
}))
{
logWarning(
{.msg = HintFmt(
"Shadowing '%s' by configuring the nix-path is deprecated and "
"will be forbidden in the future.\n"
"Use %s to silence this warning.\n"
"This is due to adding '%s' to the nix-path without a prefix, "
"either by passing the flag '-I %s' to the nix invocation or by "
"adding this path to the environment variable '%s'.",
"<nix/...>",
"--extra-deprecated-features nix-path-shadow",
path.s,
path.s,
"NIX_PATH"
)}
);
}
}
}
}
Evaluator::Evaluator(
@@ -2924,6 +2981,15 @@ Expr & Evaluator::parseStdin()
);
}
std::optional<DebugTrace const *> Evaluator::nextDebugTrace() const
{
if (!debug) {
return std::nullopt;
}
return debug->traces().next();
}
kj::Promise<Result<EvalPaths::PathResult<SourcePath, ThrownError>>>
EvalPaths::findFile(const std::string_view path)
+4 -1
View File
@@ -216,7 +216,7 @@ public:
BindingsBuilder buildBindings(SymbolTable & symbols, size_t capacity)
{
return BindingsBuilder(*this, symbols, allocBindings(capacity), capacity);
return BindingsBuilder(symbols, allocBindings(capacity), capacity);
}
const Statistics getStats() const { return stats; }
@@ -568,6 +568,9 @@ public:
*/
void evalLazily(Expr & e, Value & v);
/** If debugging is enabled, returns the next trace. Otherwise, std::nullopt. */
std::optional<DebugTrace const *> nextDebugTrace() const;
private:
Expr * parse(
char * text,
+3 -1
View File
@@ -9,15 +9,17 @@ class EvalState;
struct Value;
void prim_addDrvOutputDependencies(EvalState & state, Value * * args, Value & v);
void prim_fetchClosure(EvalState & state, Value * * args, Value & v);
void prim_fetchTree(EvalState & state, Value * * args, Value & v);
void prim_fetchGit(EvalState & state, Value * * args, Value & v);
void prim_fetchMercurial(EvalState & state, Value ** args, Value & v);
void prim_fetchTarball(EvalState & state, Value * * args, Value & v);
void prim_fetchurl(EvalState & state, Value * * args, Value & v);
void prim_fromTOML(EvalState & state, Value * * args, Value & v);
void prim_appendContext(EvalState & state, Value ** args, Value & v);
void prim_getContext(EvalState & state, Value * * args, Value & v);
void prim_hasContext(EvalState & state, Value * * args, Value & v);
void prim_unsafeDiscardOutputDependency(EvalState & state, Value * * args, Value & v);
void prim_unsafeDiscardStringContext(EvalState & state, Value ** args, Value & v);
namespace flake {
+9 -2
View File
@@ -15,10 +15,12 @@ subdir('flake')
libexpr_setting_definitions = files(
# keep-sorted start
'settings/abort-on-warn.md',
'settings/allow-import-from-derivation.md',
'settings/allow-unsafe-native-code-during-evaluation.md',
'settings/allowed-uris.md',
'settings/debugger-on-trace.md',
'settings/debugger-on-warn.md',
'settings/eval-cache.md',
'settings/eval-system.md',
'settings/ignore-try.md',
@@ -51,8 +53,10 @@ builtin_definitions = files(
'builtins/abort.md',
'builtins/add.md',
'builtins/addDrvOutputDependencies.md',
'builtins/addErrorContext.md',
'builtins/all.md',
'builtins/any.md',
'builtins/appendContext.md',
'builtins/attrNames.md',
'builtins/attrValues.md',
'builtins/baseNameOf.md',
@@ -67,12 +71,13 @@ builtin_definitions = files(
'builtins/concatMap.md',
'builtins/concatStringsSep.md',
'builtins/deepSeq.md',
'builtins/derivationStrict.md',
'builtins/dirOf.md',
'builtins/div.md',
'builtins/elem.md',
'builtins/elemAt.md',
'builtins/fetchClosure.md',
'builtins/fetchGit.md',
'builtins/fetchMercurial.md',
'builtins/fetchTarball.md',
'builtins/fetchTree.md',
'builtins/fetchurl.md',
@@ -126,6 +131,7 @@ builtin_definitions = files(
'builtins/readFileType.md',
'builtins/removeAttrs.md',
'builtins/replaceStrings.md',
'builtins/scopedImport.md',
'builtins/seq.md',
'builtins/sort.md',
'builtins/split.md',
@@ -146,7 +152,9 @@ builtin_definitions = files(
'builtins/tryEval.md',
'builtins/typeOf.md',
'builtins/unsafeDiscardOutputDependency.md',
'builtins/unsafeDiscardStringContext.md',
'builtins/unsafeGetAttrPos.md',
'builtins/warn.md',
'builtins/zipAttrsWith.md',
# keep-sorted end
)
@@ -219,7 +227,6 @@ libexpr_sources = files(
'parser/parser.cc',
'primops.cc',
'primops/context.cc',
'primops/fetchClosure.cc',
'primops/fetchMercurial.cc',
'primops/fetchTree.cc',
'primops/fromTOML.cc',
+39 -11
View File
@@ -35,6 +35,24 @@ AttrName::AttrName(PosIdx pos, std::unique_ptr<Expr> e) : pos(pos), expr(std::mo
{
}
static JSON stringToJSON(std::string_view s)
{
try {
JSON value = s;
// dump it now to catch invalid utf8 strings early. this code path is not
// hot, so the extra memory allocation and encoding is not worth avoiding
(void) value.dump();
return value;
} catch (nlohmann::json::type_error & e) { // NOLINT(lix-foreign-exceptions)
if (e.id == 316) {
// invalid utf8 in string! serialize as byte array instead
return s | std::ranges::to<std::vector<unsigned char>>();
} else {
throw; // NOLINT(lix-foreign-exceptions)
}
}
}
JSON Expr::toJSON(const SymbolTable & symbols) const
{
abort();
@@ -57,7 +75,7 @@ JSON ExprLiteral::toJSON(const SymbolTable & symbols) const
break;
case nString:
valueType = "String";
value = v.str();
value = stringToJSON(v.str());
break;
case nPath:
valueType = "Path";
@@ -77,10 +95,7 @@ JSON ExprLiteral::toJSON(const SymbolTable & symbols) const
JSON ExprVar::toJSON(const SymbolTable & symbols) const
{
return {
{"_type", "ExprVar"},
{"value", symbols[name]}
};
return {{"_type", "ExprVar"}, {"value", stringToJSON(symbols[name])}};
}
JSON ExprInheritFrom::toJSON(SymbolTable const & symbols) const
@@ -116,6 +131,8 @@ void ExprAttrs::addBindingsToJSON(JSON & out, const SymbolTable & symbols) const
typedef const decltype(attrs)::value_type * Attr;
std::vector<Attr> sorted;
for (auto & i : attrs) sorted.push_back(&i);
// NOTE: this sort uses a total order, so the iteration over pointers is not an issue
// NOLINTNEXTLINE(bugprone-nondeterministic-pointer-iteration-order)
std::sort(sorted.begin(), sorted.end(), [&](Attr a, Attr b) {
std::string_view sa = symbols[a->first], sb = symbols[b->first];
return sa < sb;
@@ -124,11 +141,19 @@ void ExprAttrs::addBindingsToJSON(JSON & out, const SymbolTable & symbols) const
for (auto & i : sorted) {
switch (i->second.kind) {
case AttrDef::Kind::Plain:
out["attrs"][std::string(symbols[i->first])] = i->second.e->toJSON(symbols);
break;
case AttrDef::Kind::Inherited:
out["inherit"][std::string(symbols[i->first])] = i->second.e->toJSON(symbols);
case AttrDef::Kind::Inherited: {
const auto key = i->second.kind == AttrDef::Kind::Plain ? "attrs" : "inherit";
auto name = stringToJSON(symbols[i->first]);
if (name.is_string()) {
out[key][name] = i->second.e->toJSON(symbols);
} else {
out[fmt("binary_%s", key)][key].push_back({
{"name", name},
{"value", i->second.e->toJSON(symbols)},
});
}
break;
}
case AttrDef::Kind::InheritedFrom: {
auto & select = i->second.e->cast<ExprSelect>();
auto & from = select.e->cast<ExprInheritFrom>();
@@ -148,7 +173,7 @@ void ExprAttrs::addBindingsToJSON(JSON & out, const SymbolTable & symbols) const
for (const auto & [from, syms] : inheritsFrom) {
JSON attrs = JSON::array();
for (auto sym : syms)
attrs.push_back(symbols[sym]);
attrs.push_back(stringToJSON(symbols[sym]));
out["inheritFrom"].push_back({
{"from", inheritFromExprs[from]->toJSON(symbols)},
{"attrs", attrs}
@@ -186,11 +211,13 @@ JSON ExprList::toJSON(const SymbolTable & symbols) const
void SimplePattern::addBindingsToJSON(JSON & out, const SymbolTable & symbols) const
{
// name must be alphanumeric
out["arg"] = symbols[name];
}
void AttrsPattern::addBindingsToJSON(JSON & out, const SymbolTable & symbols) const
{
// name must be alphanumeric
if (name)
out["arg"] = symbols[name];
@@ -198,6 +225,7 @@ void AttrsPattern::addBindingsToJSON(JSON & out, const SymbolTable & symbols) co
// same expression being printed in two different ways depending on its
// context. always use lexicographic ordering to avoid this.
for (const Formal & i : lexicographicOrder(symbols)) {
// names must be alphanumeric
if (i.def)
out["formals"][std::string(symbols[i.name])] = i.def->toJSON(symbols);
else
@@ -311,7 +339,7 @@ JSON printAttrPathToJson(const SymbolTable & symbols, const AttrPath & attrPath)
JSON out = JSON::array();
for (auto & i : attrPath) {
if (i.symbol)
out.push_back(symbols[i.symbol]);
out.push_back(stringToJSON(symbols[i.symbol]));
else
out.push_back(i.expr->toJSON(symbols));
}
+5
View File
@@ -32,6 +32,11 @@ struct AttrName
std::unique_ptr<Expr> expr;
AttrName(PosIdx pos, Symbol s);
AttrName(PosIdx pos, std::unique_ptr<Expr> e);
inline bool isDynamic()
{
return !symbol;
}
};
typedef std::vector<AttrName> AttrPath;
+16 -6
View File
@@ -203,6 +203,16 @@ struct sep : sor<
using seps = star<t::sep>;
// Delimiters.
namespace d {
// Terminators.
namespace t {
struct inherit : one<';'> { };
struct binding : one<';'> { };
struct attrs : one<'}'> { };
}
}
// marker for semantic rules. not handling one of these in an action that cares about
// semantics is probably an error.
@@ -392,7 +402,7 @@ struct _attr {
struct expr : semantic, seq<
TAO_PEGTL_STRING("${"), seps,
must<grammar::v1::expr>, seps,
must<one<'}'>>
must<d::t::attrs>
> {};
};
struct attr : _attr, sor<
@@ -411,7 +421,7 @@ struct inherit : _inherit, seq<
t::kw_inherit, seps,
opt<one<'('>, seps, _inherit::from, seps, must<one<')'>>, seps>,
opt<_inherit::attrs, seps>,
must<one<';'>>
must<d::t::inherit>
> {};
struct _binding {
@@ -430,7 +440,7 @@ struct bindings : opt<
list<
sor<
inherit,
seq<binding, seps, must<one<';'>>>
seq<binding, seps, must<d::t::binding>>
>,
seps
>
@@ -439,9 +449,9 @@ struct bindings : opt<
struct repl_binding : binding {};
struct repl_bindings : seq<
list<repl_binding, one<';'>, t::sep>,
list<repl_binding, d::t::binding, t::sep>,
/* Optional semicolon at the end for convenience */
opt<seps, one<';'>>
opt<seps, d::t::binding>
> {};
struct op {
@@ -496,7 +506,7 @@ struct _expr {
Init...,
OpenMod<one<'{'>>, seps,
bindings, seps,
must<one<'}'>>
must<d::t::attrs>
> {};
struct select;
+3
View File
@@ -56,6 +56,9 @@ error_message_for(grammar::v1::expr::select) = "expecting selection expression";
error_message_for(grammar::v1::t::kw_then) = "expecting 'then'";
error_message_for(grammar::v1::t::kw_else) = "expecting 'else'";
error_message_for(grammar::v1::t::kw_in) = "expecting 'in'";
error_message_for(grammar::v1::d::t::binding) = "expecting ';' to end binding";
error_message_for(grammar::v1::d::t::inherit) = "expecting ';' to end 'inherit' bindings";
error_message_for(grammar::v1::d::t::attrs) = "expecting '}' to end attribute set";
struct SyntaxErrors
{
+97 -65
View File
@@ -6,7 +6,6 @@
#include "lix/libutil/logging.hh"
namespace nix::parser {
struct IndStringLine {
// String containing only the leading whitespace of the line. May be empty.
std::string_view indentation;
@@ -40,6 +39,7 @@ struct State
void badLineEndingFound(const PosIdx pos, bool warnOnly);
void nulFound(const PosIdx pos);
void addAttr(ExprAttrs * attrs, AttrPath && attrPath, std::unique_ptr<Expr> e, const PosIdx pos);
void mergeAttrs(AttrPath & attrPath, ExprSet * source, ExprSet * target);
void validateLambdaAttrs(AttrsPattern & pattern, PosIdx pos = noPos);
std::unique_ptr<Expr> stripIndentation(const PosIdx pos, std::vector<IndStringLine> && line);
@@ -132,81 +132,113 @@ inline void State::addAttr(ExprAttrs * attrs, AttrPath && attrPath, std::unique_
AttrPath::iterator i;
// All attrpaths have at least one attr
assert(!attrPath.empty());
// Checking attrPath validity.
// ===========================
// Walk the attrpath up to the parent of the attribute we want to insert, moving `attrs` along
// and creating new empty intermediate attrsets as necessary.
for (i = attrPath.begin(); i + 1 < attrPath.end(); i++) {
if (i->symbol) {
ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol);
if (j != attrs->attrs.end()) {
if (j->second.kind != ExprAttrs::AttrDef::Kind::Inherited) {
ExprSet * attrs2 = dynamic_cast<ExprSet *>(j->second.e.get());
if (!attrs2) {
attrPath.erase(i + 1, attrPath.end());
dupAttr(attrPath, pos, j->second.pos);
}
attrs = attrs2;
} else {
attrPath.erase(i + 1, attrPath.end());
dupAttr(attrPath, pos, j->second.pos);
}
} else {
auto next = attrs->attrs.emplace(std::piecewise_construct,
std::tuple(i->symbol),
std::tuple(std::make_unique<ExprSet>(), pos));
attrs = static_cast<ExprSet *>(next.first->second.e.get());
}
} else {
AttrName & attr = *i;
if (attr.isDynamic()) {
// Simply insert an empty attrset (but dynamic)
auto & next = attrs->dynamicAttrs.emplace_back(std::move(i->expr), std::make_unique<ExprSet>(), pos);
attrs = static_cast<ExprSet *>(next.valueExpr.get());
} else if (ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol);
j != attrs->attrs.end())
{
// Try to walk down the next attribute, throw duplicate error if not possible
auto & [foundName, foundDef] = *j;
if (foundDef.kind == ExprAttrs::AttrDef::Kind::Inherited) {
attrPath.erase(i + 1, attrPath.end());
return dupAttr(attrPath, pos, foundDef.pos);
}
ExprSet * foundAttrs = dynamic_cast<ExprSet *>(foundDef.e.get());
if (!foundAttrs) {
attrPath.erase(i + 1, attrPath.end());
return dupAttr(attrPath, pos, foundDef.pos);
}
attrs = foundAttrs;
} else {
// Simply insert an empty attrset
auto next = attrs->attrs.emplace(
std::piecewise_construct,
std::tuple(attr.symbol),
std::tuple(std::make_unique<ExprSet>(), pos)
);
attrs = static_cast<ExprSet *>(next.first->second.e.get());
}
}
// Expr insertion.
// ==========================
if (i->symbol) {
ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol);
if (j != attrs->attrs.end()) {
// This attr path is already defined. However, if both
// e and the expr pointed by the attr path are two attribute sets,
// we want to merge them.
// Otherwise, throw an error.
auto * ae = dynamic_cast<ExprSet *>(e.get());
auto * jAttrs = dynamic_cast<ExprSet *>(j->second.e.get());
if (jAttrs && ae) {
if (ae->inheritFromExprs && !jAttrs->inheritFromExprs)
jAttrs->inheritFromExprs = std::make_unique<std::list<std::unique_ptr<Expr>>>();
for (auto & ad : ae->attrs) {
auto j2 = jAttrs->attrs.find(ad.first);
if (j2 != jAttrs->attrs.end()) // Attr already defined in iAttrs, error.
return dupAttr(ad.first, j2->second.pos, ad.second.pos);
if (ad.second.kind == ExprAttrs::AttrDef::Kind::InheritedFrom) {
auto & sel = dynamic_cast<ExprSelect &>(*ad.second.e);
auto & from = dynamic_cast<ExprInheritFrom &>(*sel.e);
from.displ += jAttrs->inheritFromExprs->size();
}
jAttrs->attrs.emplace(ad.first, std::move(ad.second));
}
std::ranges::move(ae->dynamicAttrs, std::back_inserter(jAttrs->dynamicAttrs));
if (ae->inheritFromExprs)
std::ranges::move(*ae->inheritFromExprs, std::back_inserter(*jAttrs->inheritFromExprs));
} else {
dupAttr(attrPath, pos, j->second.pos);
AttrName & attr = *i;
if (attr.isDynamic()) {
attrs->dynamicAttrs.emplace_back(std::move(attr.expr), std::move(e), pos);
} else if (ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(attr.symbol);
j != attrs->attrs.end())
{
// This attr path is already defined. However, if both
// e and the expr pointed by the attr path are two attribute sets,
// we want to merge them.
// Otherwise, throw an error.
auto & [foundName, foundDef] = *j;
auto * insertAttrs = dynamic_cast<ExprSet *>(e.get());
auto * foundAttrs = dynamic_cast<ExprSet *>(foundDef.e.get());
if (!foundAttrs || !insertAttrs) {
return dupAttr(attrPath, pos, foundDef.pos);
}
mergeAttrs(attrPath, insertAttrs, foundAttrs);
} else {
// This attr path is not defined. Let's create it.
// Before inserting new attrs, check for __override and throw an error
// (the error will initially be a warning to ease migration)
if (!featureSettings.isEnabled(Dep::RecSetOverrides) && attr.symbol == s.overrides) {
if (auto set = dynamic_cast<ExprSet *>(attrs); set && set->recursive) {
overridesFound(pos);
}
} else {
// Before inserting new attrs, check for __override and throw an error
// (the error will initially be a warning to ease migration)
if (!featureSettings.isEnabled(Dep::RecSetOverrides) && i->symbol == s.overrides) {
if (auto set = dynamic_cast<ExprSet *>(attrs); set && set->recursive)
overridesFound(pos);
}
e->setName(attr.symbol);
attrs->attrs.emplace(
std::piecewise_construct, std::tuple(attr.symbol), std::tuple(std::move(e), pos)
);
}
}
/* mutably merge source into target. attrPath is only for error messages */
inline void State::mergeAttrs(AttrPath & attrPath, ExprSet * source, ExprSet * target)
{
if (source->inheritFromExprs && !target->inheritFromExprs) {
target->inheritFromExprs = std::make_unique<std::list<std::unique_ptr<Expr>>>();
}
for (auto & [insertKey, insertDef] : source->attrs) {
if (auto collision = target->attrs.find(insertKey); collision != target->attrs.end()) {
// Attr already defined in target, recurse merge if possible otherwise error.
auto * collisionInsert = dynamic_cast<ExprSet *>(insertDef.e.get());
auto * collisionTarget = dynamic_cast<ExprSet *>(collision->second.e.get());
if (!collisionInsert || !collisionTarget) {
attrPath.push_back(AttrName(insertDef.pos, insertKey));
return dupAttr(attrPath, insertDef.pos, collision->second.pos);
}
// This attr path is not defined. Let's create it.
e->setName(i->symbol);
attrs->attrs.emplace(std::piecewise_construct,
std::tuple(i->symbol),
std::tuple(std::move(e), pos));
// Push insertKey to the attrPath for error propagation (pop afterwards), then recurse
// merge
attrPath.push_back(AttrName(insertDef.pos, insertKey));
mergeAttrs(attrPath, collisionInsert, collisionTarget);
attrPath.pop_back();
}
} else {
attrs->dynamicAttrs.emplace_back(std::move(i->expr), std::move(e), pos);
if (insertDef.kind == ExprAttrs::AttrDef::Kind::InheritedFrom) {
auto & sel = dynamic_cast<ExprSelect &>(*insertDef.e);
auto & from = dynamic_cast<ExprInheritFrom &>(*sel.e);
from.displ += target->inheritFromExprs->size();
}
target->attrs.emplace(insertKey, std::move(insertDef));
}
std::ranges::move(source->dynamicAttrs, std::back_inserter(target->dynamicAttrs));
if (source->inheritFromExprs) {
std::ranges::move(*source->inheritFromExprs, std::back_inserter(*target->inheritFromExprs));
}
}
+41 -25
View File
@@ -251,13 +251,6 @@ static void import(EvalState & state, Value & vPath, Value * vScope, Value & v)
}
}
static RegisterPrimOp primop_scopedImport(PrimOp{
{.name = "scopedImport",
.arity = 2,
.fun = [](EvalState & state, Value ** args, Value & v) { import(state, *args[1], args[0], v); }
}
});
static void prim_import(EvalState & state, Value * * args, Value & v)
{
import(state, *args[0], nullptr, v);
@@ -270,6 +263,11 @@ extern "C" typedef void (*ValueInitializer)(EvalState & state, Value & v);
/* Load a ValueInitializer from a DSO and return whatever it initializes */
void prim_importNative(EvalState & state, Value * * args, Value & v)
{
# if LIX_MAJOR > 2 || (LIX_MAJOR == 2 && LIX_MINOR >= 97)
#pragma message ("Folks, we need to rip this out since we've reached 2.97 See fj#796 for more details")
# endif
printTaggedWarning("builtins.importNative is deprecated and will be removed in Lix 2.97, please migrate away from it. You can browse issue #795 for more details.");
auto path = realisePath(state, *args[0]);
std::string sym(state.forceStringNoCtx(*args[1], noPos, "while evaluating the second argument passed to builtins.importNative"));
@@ -636,7 +634,7 @@ static void prim_genericClosure(EvalState & state, Value * * args, Value & v)
static void prim_break(EvalState & state, Value * * args, Value & v)
{
if (auto trace = state.ctx.debug ? state.ctx.debug->traces().next() : std::nullopt) {
if (auto const trace = state.ctx.nextDebugTrace()) {
auto error = EvalError(ErrorInfo {
.level = lvlInfo,
.msg = HintFmt("breakpoint reached"),
@@ -680,12 +678,6 @@ static void prim_addErrorContext(EvalState & state, Value * * args, Value & v)
}
}
static RegisterPrimOp primop_addErrorContext(PrimOp{{
.name = "__addErrorContext",
.arity = 2,
.fun = prim_addErrorContext,
}});
static void prim_ceil(EvalState & state, Value * * args, Value & v)
{
auto value = state.forceFloat(*args[0], noPos,
@@ -771,16 +763,46 @@ static void prim_trace(EvalState & state, Value * * args, Value & v)
printError("trace: %1%", Uncolored(args[0]->str()));
else
printError("trace: %1%", Uncolored(ValuePrinter(state, *args[0])));
if (auto last = evalSettings.builtinsTraceDebugger && state.ctx.debug
? state.ctx.debug->traces().next()
: std::nullopt)
{
state.ctx.debug->onEvalError(nullptr, (*last)->env, (*last)->expr);
if (evalSettings.debuggerOnTrace) {
if (auto const trace = state.ctx.nextDebugTrace()) {
state.ctx.debug->onEvalError(nullptr, (*trace)->env, (*trace)->expr);
}
}
state.forceValue(*args[1], noPos);
v = *args[1];
}
static void prim_warn(EvalState & state, Value ** args, Value & v)
{
// We only accept a string argument for now. The use case for pretty printing a value is covered
// by `trace`. By rejecting non-strings we allow future versions to add more features without
// breaking existing code.
auto const msg =
state.forceString(*args[0], noPos, "while evaluating message for builtins.warn");
printTaggedWarning("%s", Uncolored(msg));
if (evalSettings.abortOnWarn) {
state.ctx.errors.make<Abort>("evaluation aborted (abort-on-warn)")
.debugThrow();
}
if (evalSettings.debuggerOnWarn) {
if (auto const trace = state.ctx.nextDebugTrace()) {
auto const error = EvalError(ErrorInfo{
.level = lvlWarn,
.msg = HintFmt("builtins.warn reached"),
});
state.ctx.debug->onEvalError(&error, (*trace)->env, (*trace)->expr);
}
}
state.forceValue(*args[1], noPos);
v = *args[1];
}
/* Takes two arguments and evaluates to the second one. Used as the
* builtins.traceVerbose implementation when --trace-verbose is not enabled
@@ -1226,12 +1248,6 @@ drvName, Bindings * attrs, Value & v)
v.mkAttrs(result);
}
static RegisterPrimOp primop_derivationStrict(PrimOp{{
.name = "derivationStrict",
.arity = 1,
.fun = prim_derivationStrict,
}});
/* Return a placeholder string for the specified output that will be
substituted by the corresponding output path at build time. For
example, 'placeholder "out"' returns the string
+2 -16
View File
@@ -7,20 +7,13 @@
namespace nix {
static void prim_unsafeDiscardStringContext(EvalState & state, Value * * args, Value & v)
void prim_unsafeDiscardStringContext(EvalState & state, Value ** args, Value & v)
{
NixStringContext context;
auto s = state.coerceToString(noPos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardStringContext");
v.mkString(*s);
}
static RegisterPrimOp primop_unsafeDiscardStringContext({
.name = "__unsafeDiscardStringContext",
.arity = 1,
.fun = prim_unsafeDiscardStringContext
});
void prim_hasContext(EvalState & state, Value * * args, Value & v)
{
NixStringContext context;
@@ -165,7 +158,7 @@ void prim_getContext(EvalState & state, Value * * args, Value & v)
See the commentary above unsafeGetContext for details of the
context representation.
*/
static void prim_appendContext(EvalState & state, Value * * args, Value & v)
void prim_appendContext(EvalState & state, Value ** args, Value & v)
{
NixStringContext context;
auto orig = state.forceString(*args[0], context, noPos, "while evaluating the first argument passed to builtins.appendContext");
@@ -241,11 +234,4 @@ static void prim_appendContext(EvalState & state, Value * * args, Value & v)
v.mkString(orig, context);
}
static RegisterPrimOp primop_appendContext({
.name = "__appendContext",
.arity = 2,
.fun = prim_appendContext
});
}
-201
View File
@@ -1,201 +0,0 @@
#include "lix/libexpr/eval.hh"
#include "lix/libexpr/extra-primops.hh"
#include "lix/libstore/store-api.hh"
#include "lix/libstore/make-content-addressed.hh"
#include "lix/libutil/async.hh"
#include "lix/libutil/url.hh"
namespace nix {
/**
* Handler for the content addressed case.
*
* @param state Evaluator state and store to write to.
* @param fromStore Store containing the path to rewrite.
* @param fromPath Source path to be rewritten.
* @param toPathMaybe Path to write the rewritten path to. If empty, the error shows the actual path.
* @param v Return `Value`
*/
static void runFetchClosureWithRewrite(EvalState & state, Store & fromStore, const StorePath & fromPath, const std::optional<StorePath> & toPathMaybe, Value &v) {
// establish toPath or throw
if (!toPathMaybe || !state.aio.blockOn(state.ctx.store->isValidPath(*toPathMaybe))) {
auto rewrittenPath =
state.aio.blockOn(makeContentAddressed(fromStore, *state.ctx.store, fromPath));
if (toPathMaybe && *toPathMaybe != rewrittenPath)
throw Error({
.msg = HintFmt("rewriting '%s' to content-addressed form yielded '%s', while '%s' was expected",
state.ctx.store->printStorePath(fromPath),
state.ctx.store->printStorePath(rewrittenPath),
state.ctx.store->printStorePath(*toPathMaybe)),
});
if (!toPathMaybe)
throw Error({
.msg = HintFmt(
"rewriting '%s' to content-addressed form yielded '%s'\n"
"Use this value for the 'toPath' attribute passed to 'fetchClosure'",
state.ctx.store->printStorePath(fromPath),
state.ctx.store->printStorePath(rewrittenPath)),
});
}
auto toPath = *toPathMaybe;
// check and return
auto resultInfo = state.aio.blockOn(state.ctx.store->queryPathInfo(toPath));
if (!resultInfo->isContentAddressed(*state.ctx.store)) {
// We don't perform the rewriting when outPath already exists, as an optimisation.
// However, we can quickly detect a mistake if the toPath is input addressed.
throw Error({
.msg = HintFmt(
"The 'toPath' value '%s' is input-addressed, so it can't possibly be the result of rewriting to a content-addressed path.\n\n"
"Set 'toPath' to an empty string to make Lix report the correct content-addressed path.",
state.ctx.store->printStorePath(toPath)),
});
}
state.ctx.paths.mkStorePathString(toPath, v);
}
/**
* Fetch the closure and make sure it's content addressed.
*/
static void runFetchClosureWithContentAddressedPath(EvalState & state, Store & fromStore, const StorePath & fromPath, Value & v) {
if (!state.aio.blockOn(state.ctx.store->isValidPath(fromPath)))
state.aio.blockOn(copyClosure(fromStore, *state.ctx.store, RealisedPath::Set { fromPath }));
auto info = state.aio.blockOn(state.ctx.store->queryPathInfo(fromPath));
if (!info->isContentAddressed(*state.ctx.store)) {
throw Error({
.msg = HintFmt(
"The 'fromPath' value '%s' is input-addressed, but 'inputAddressed' is set to 'false' (default).\n\n"
"If you do intend to fetch an input-addressed store path, add\n\n"
" inputAddressed = true;\n\n"
"to the 'fetchClosure' arguments.\n\n"
"Note that to ensure authenticity input-addressed store paths, users must configure a trusted binary cache public key on their systems. This is not needed for content-addressed paths.",
state.ctx.store->printStorePath(fromPath)),
});
}
state.ctx.paths.mkStorePathString(fromPath, v);
}
/**
* Fetch the closure and make sure it's input addressed.
*/
static void runFetchClosureWithInputAddressedPath(EvalState & state, Store & fromStore, const StorePath & fromPath, Value & v) {
if (!state.aio.blockOn(state.ctx.store->isValidPath(fromPath)))
state.aio.blockOn(copyClosure(fromStore, *state.ctx.store, RealisedPath::Set { fromPath }));
auto info = state.aio.blockOn(state.ctx.store->queryPathInfo(fromPath));
if (info->isContentAddressed(*state.ctx.store)) {
throw Error({
.msg = HintFmt(
"The store object referred to by 'fromPath' at '%s' is not input-addressed, but 'inputAddressed' is set to 'true'.\n\n"
"Remove the 'inputAddressed' attribute (it defaults to 'false') to expect 'fromPath' to be content-addressed",
state.ctx.store->printStorePath(fromPath)),
});
}
state.ctx.paths.mkStorePathString(fromPath, v);
}
typedef std::optional<StorePath> StorePathOrGap;
void prim_fetchClosure(EvalState & state, Value * * args, Value & v)
{
state.forceAttrs(*args[0], noPos, "while evaluating the argument passed to builtins.fetchClosure");
std::optional<std::string> fromStoreUrl;
std::optional<StorePath> fromPath;
std::optional<StorePathOrGap> toPath;
std::optional<bool> inputAddressedMaybe;
for (auto & attr : *args[0]->attrs()) {
const auto & attrName = state.ctx.symbols[attr.name];
auto attrHint = [&]() -> std::string {
return "while evaluating the '" + attrName + "' attribute passed to builtins.fetchClosure";
};
if (attrName == "fromPath") {
NixStringContext context;
fromPath = state.coerceToStorePath(attr.pos, attr.value, context, attrHint());
}
else if (attrName == "toPath") {
state.forceValue(attr.value, attr.pos);
bool isEmptyString = attr.value.type() == nString && attr.value.str().empty();
if (isEmptyString) {
toPath = StorePathOrGap {};
}
else {
NixStringContext context;
toPath = state.coerceToStorePath(attr.pos, attr.value, context, attrHint());
}
}
else if (attrName == "fromStore")
fromStoreUrl = state.forceStringNoCtx(attr.value, attr.pos, attrHint());
else if (attrName == "inputAddressed")
inputAddressedMaybe = state.forceBool(attr.value, attr.pos, attrHint());
else
throw Error({
.msg = HintFmt("attribute '%s' isn't supported in call to 'fetchClosure'", attrName),
});
}
if (!fromPath)
throw Error({
.msg = HintFmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"),
});
bool inputAddressed = inputAddressedMaybe.value_or(false);
if (inputAddressed) {
if (toPath)
throw Error({
.msg = HintFmt("attribute '%s' is set to true, but '%s' is also set. Please remove one of them",
"inputAddressed",
"toPath"),
});
}
if (!fromStoreUrl)
throw Error({
.msg = HintFmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"),
});
auto parsedURL = parseURL(*fromStoreUrl);
if (parsedURL.scheme != "http" &&
parsedURL.scheme != "https" &&
!(getEnv("_NIX_IN_TEST").has_value() && parsedURL.scheme == "file"))
throw Error({
.msg = HintFmt("'fetchClosure' only supports http:// and https:// stores"),
});
if (!parsedURL.query.empty())
throw Error({
.msg = HintFmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl),
});
auto fromStore = state.aio.blockOn(openStore(parsedURL.to_string()));
if (toPath)
runFetchClosureWithRewrite(state, *fromStore, *fromPath, *toPath, v);
else if (inputAddressed)
runFetchClosureWithInputAddressedPath(state, *fromStore, *fromPath, v);
else
runFetchClosureWithContentAddressedPath(state, *fromStore, *fromPath, v);
}
}
+1 -8
View File
@@ -5,7 +5,7 @@
namespace nix {
static void prim_fetchMercurial(EvalState & state, Value * * args, Value & v)
void prim_fetchMercurial(EvalState & state, Value ** args, Value & v)
{
std::string url;
std::optional<Hash> rev;
@@ -102,11 +102,4 @@ static void prim_fetchMercurial(EvalState & state, Value * * args, Value & v)
state.ctx.paths.allowPath(tree.storePath);
}
static RegisterPrimOp r_fetchMercurial({
.name = "fetchMercurial",
.arity = 1,
.fun = prim_fetchMercurial
});
}
+5 -3
View File
@@ -48,10 +48,12 @@ struct PrintOptions
bool force = false;
/**
* If true and `force` is set, print derivations as
* `«derivation /nix/store/...»` instead of as attribute sets.
* Determines at what depth derivations start getting printed as paths.
* When `force` is true and our print depth is greater than or equal to
* this value, print derivations as `«derivation /nix/store/...»`
* instead of as attribute sets.
*/
bool derivationPaths = false;
size_t derivationPathDepth = std::numeric_limits<size_t>::max();
/**
* If true, track which values have been printed and skip them on
+19 -3
View File
@@ -281,7 +281,9 @@ private:
void printAttrs(Value & v, size_t depth)
{
if (options.force && options.derivationPaths && state.isDerivation(v)) {
bool shouldSimplifyDerivations = options.force && depth >= options.derivationPathDepth;
bool isDerivation = state.isDerivation(v);
if (shouldSimplifyDerivations && isDerivation) {
printDerivation(v);
} else if (seen && !v.attrs()->empty() && !seen->insert(v.attrs()).second) {
printRepeated();
@@ -304,7 +306,7 @@ private:
for (auto & i : sorted) {
printSpace(prettyPrint);
if (attrsPrinted >= options.maxAttrs) {
if (attrsPrinted >= options.maxAttrs ) {
printElided(sorted.size() - printedHere, "attribute", "attributes");
break;
}
@@ -324,6 +326,21 @@ private:
}
output << " = ";
// Elide repeated drvAttrs attribute.
if (!shouldSimplifyDerivations && isDerivation && i.first == "drvAttrs") {
state.forceValue(i.second->value, noPos);
if (i.second->value.type() == ValueType::nAttrs) {
printElided(i.second->value.attrs()->size(), "attribute", "attributes");
} else {
print(i.second->value, depth + 1);
}
output << ";";
attrsPrinted++;
printedHere++;
continue;
}
print(i.second->value, depth + 1);
output << ";";
attrsPrinted++;
@@ -582,7 +599,6 @@ public:
seen.reset();
}
ValuesSeen seen;
print(v, 0);
}
};
+10
View File
@@ -0,0 +1,10 @@
---
name: abort-on-warn
internalName: abortOnWarn
type: bool
default: false
---
If set to true, [`builtins.warn`](@docroot@/language/builtins.md#builtins-warn) will throw an error when logging a warning.
This will give you a stack trace that leads to the location of the warning.
This is useful for finding information about warnings in third-party Nix code when you can not start the interactive debugger, such as when Nix is called from a non-interactive script. See [`debugger-on-warn`](#conf-debugger-on-warn).
Currently, a stack trace can only be produced when the debugger is enabled, or when evaluation is aborted.
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: debugger-on-trace
internalName: builtinsTraceDebugger
internalName: debuggerOnTrace
type: bool
default: false
---
+9
View File
@@ -0,0 +1,9 @@
---
name: debugger-on-warn
internalName: debuggerOnWarn
type: bool
default: false
---
If set to true and the `--debugger` flag is given, [`builtins.warn`](@docroot@/language/builtins.md#builtins-warn) will enter the debugger like [`builtins.break`](@docroot@/language/builtins.md#builtins-break).
This is useful for debugging warnings in third-party Nix code.
Use [`debugger-on-trace`](#conf-debugger-on-trace) to also enter the debugger on legacy warnings that are logged with [`builtins.trace`](@docroot@/language/builtins.md#builtins-trace).
+30 -8
View File
@@ -4,6 +4,7 @@
#include "lix/libstore/store-api.hh"
#include "lix/libstore/gc-store.hh"
#include "lix/libutil/c-calls.hh"
#include "lix/libutil/logging.hh"
#include "lix/libutil/result.hh"
#include "lix/libutil/signals.hh"
#include "lix/libmain/loggers.hh"
@@ -32,8 +33,6 @@
namespace nix {
char * * savedArgv;
static bool gcWarning = true;
void printGCWarning()
@@ -94,6 +93,8 @@ try {
std::vector<const StorePath *> willSubstituteSorted = {};
std::for_each(willSubstitute.begin(), willSubstitute.end(),
[&](const StorePath &p) { willSubstituteSorted.push_back(&p); });
// NOTE: this sort uses a total order, so the iteration over pointers is not an issue
// NOLINTNEXTLINE(bugprone-nondeterministic-pointer-iteration-order)
std::sort(willSubstituteSorted.begin(), willSubstituteSorted.end(),
[](const StorePath *lhs, const StorePath *rhs) {
if (lhs->name() == rhs->name())
@@ -403,13 +404,34 @@ RunPager::~RunPager()
}
}
PrintFreed::~PrintFreed()
{
if (show)
std::cout << fmt("%d store paths deleted, %s freed\n",
results.paths.size(),
showBytes(results.bytesFreed));
}
// When in dry-run mode, print the paths on stdout
if (action == GCOptions::gcReturnLive || action == GCOptions::gcReturnDead) {
for (auto & i : results.paths) {
logger->cout("%s", i);
};
}
switch (action) {
case GCOptions::gcReturnLive: {
notice("%1% store paths would be kept\n", results.paths.size());
break;
}
case GCOptions::gcReturnDead: {
notice("%1% store paths would be deleted\n", results.paths.size());
break;
}
case GCOptions::gcDeleteDead:
case GCOptions::gcDeleteSpecific:
case GCOptions::gcTryDeleteSpecific: {
notice(
"%1% store paths deleted, %2% freed\n",
results.paths.size(),
showBytes(results.bytesFreed)
);
break;
}
}
}
}
+8 -3
View File
@@ -1,6 +1,7 @@
#pragma once
///@file
#include "lix/libstore/gc-store.hh"
#include "lix/libutil/args.hh"
#include "lix/libutil/args/root.hh"
#include "lix/libmain/common-args.hh"
@@ -99,10 +100,14 @@ struct GCResults;
struct PrintFreed
{
bool show;
GCOptions::GCAction action;
const GCResults & results;
PrintFreed(bool show, const GCResults & results)
: show(show), results(results) { }
PrintFreed(GCOptions::GCAction action, const GCResults & results)
: action(action)
, results(results)
{
}
~PrintFreed();
};
+1 -1
View File
@@ -48,7 +48,7 @@ void detectStackOverflow()
requires an alternative stack, otherwise the signal cannot be
delivered when we're out of stack space. */
stack_t stack;
stack.ss_size = 4096 * 4 + MINSIGSTKSZ;
stack.ss_size = 4096ul * 4 + MINSIGSTKSZ;
static auto stackBuf = std::make_unique<std::vector<char>>(stack.ss_size);
stack.ss_sp = stackBuf->data();
if (!stack.ss_sp) throw Error("cannot allocate alternative stack");
+24 -14
View File
@@ -227,13 +227,20 @@ retry:
}
auto & localStore = getLocalStore();
if (localStore.config().storeDir != localStore.config().realStoreDir.get()) {
#if __linux__
const bool sandboxFallbackAllowed = [&]() {
if (localStore.config().storeDir != localStore.config().realStoreDir.get()) {
#if __linux__
if (!useChroot) {
printTaggedWarning("auto-enabling the sandbox due to using a diverted store");
}
useChroot = true;
#else
return false;
#else
throw Error("building using a diverted store is not supported on this platform");
#endif
}
#endif
}
return true;
}();
if (useBuildUsers()) {
if (!buildUser)
@@ -263,7 +270,9 @@ retry:
if (!mountAndPidNamespacesSupported()) {
if (!settings.sandboxFallback)
throw Error("this system does not support the kernel namespaces that are required for sandboxing; use '--no-sandbox' to disable sandboxing. Pass --debug for diagnostics on what is broken.");
debug("auto-disabling sandboxing because the prerequisite namespaces are not available");
if (!sandboxFallbackAllowed)
throw Error("Sandboxing is enabled due to using a diverted store, but this system does not support the kernel namespaces that are required. Pass --debug for diagnostics on what is broken.");
printTaggedWarning("auto-disabling sandboxing because the prerequisite namespaces are not available");
useChroot = false;
}
@@ -1825,15 +1834,18 @@ try {
);
}
outputGraph[scratchOutputs.at(name)] = StorePathSet{};
std::visit(
overloaded{/* Since we'll use the already installed versions of these, we
can treat them as leaves and ignore any references they
have. */
[&](const AlreadyRegistered &) {
outputGraph[scratchOutputs.at(name)] = StorePathSet{};
},
[&](const AlreadyRegistered &) {},
[&](const PerhapsNeedToRegister & refs) {
outputGraph[scratchOutputs.at(name)] = refs.refs;
for (auto & ref : refs.refs) {
if (inverseOutputMap.find(ref) != inverseOutputMap.end()) {
outputGraph[scratchOutputs.at(name)].insert(ref);
}
}
}
},
*orifu
@@ -1844,10 +1856,8 @@ try {
topoSort(outputsToSort, {[&](const std::string & name) {
StringSet dependencies;
for (auto & path : outputGraph.at(scratchOutputs.at(name))) {
auto outputName = inverseOutputMap.find(path);
if (outputName != inverseOutputMap.end()) {
dependencies.insert(outputName->second);
}
auto outputName = inverseOutputMap.at(path);
dependencies.insert(outputName);
}
return dependencies;
}});
+14 -4
View File
@@ -142,6 +142,13 @@ static void expect(StringViewStream & str, std::string_view s)
str.remaining.remove_prefix(s.size());
}
static void expectWithErrorMsg(StringViewStream & str, std::string_view s, const char * errorMsg)
{
if (!str.remaining.starts_with(s)) {
throw FormatError(errorMsg);
}
str.remaining.remove_prefix(s.size());
}
/* Read a C-style string from stream `str'. */
static BackedStringView parseString(StringViewStream & str)
@@ -276,13 +283,16 @@ Derivation parseDerivation(
drv.name = name;
StringViewStream str{s};
expect(str, "D");
const char * genericErrorMsg = "derivation does not start with 'Derive' or 'DrvWithVersion'";
expectWithErrorMsg(str, "D", genericErrorMsg);
switch (str.peek()) {
case 'e':
expect(str, "erive(");
expectWithErrorMsg(str, "erive(", genericErrorMsg);
break;
case 'r': {
expect(str, "rvWithVersion(");
expectWithErrorMsg(str, "rvWithVersion(", genericErrorMsg);
auto versionS = parseString(str);
throw FormatError("Unknown derivation ATerm format version '%s'", *versionS);
}
@@ -342,7 +352,7 @@ Derivation parseDerivation(
*/
static void printString(std::string & res, std::string_view s)
{
boost::container::small_vector<char, 64 * 1024> buffer;
boost::container::small_vector<char, 64ul * 1024> buffer;
buffer.reserve(s.size() * 2 + 2);
char * buf = buffer.data();
char * p = buf;
+31 -16
View File
@@ -333,7 +333,7 @@ struct TransferItem
// when the buffer is full (as determined by a historical magic value) we
// pause the transfer and wait for the receiver to unpause it when ready.
if (successfulStatuses.count(getHTTPStatus()) && state->data.size() > 1024 * 1024) {
if (successfulStatuses.count(getHTTPStatus()) && state->data.size() > 1024ul * 1024) {
return CURL_WRITEFUNC_PAUSE;
}
@@ -1093,15 +1093,6 @@ struct curlFileTransfer : public FileTransfer
co_return result::current_exception();
}
void throwChangedTarget(std::string_view what, std::string_view from, std::string_view to)
{
if (!from.empty() && from != to) {
throw FileTransferError(
Misc, {}, "uri %s changed %s from %s to %s during transfer", uri, what, from, to
);
}
}
kj::Promise<Result<void>> prepareRetry(
const std::string & context,
const std::chrono::milliseconds & waitTime,
@@ -1113,7 +1104,7 @@ struct curlFileTransfer : public FileTransfer
"%s; retrying from offset %d in %d ms (attempt %d/%d)",
Uncolored(context),
totalReceived,
waitTime,
waitTime.count(),
Uncolored(attempt),
Uncolored(tries)
);
@@ -1121,7 +1112,7 @@ struct curlFileTransfer : public FileTransfer
printTaggedWarning(
"%s; retrying in %d ms (attempt %d/%d)",
Uncolored(context),
waitTime,
waitTime.count(),
Uncolored(attempt),
Uncolored(tries)
);
@@ -1135,17 +1126,41 @@ struct curlFileTransfer : public FileTransfer
kj::Promise<Result<void>> restartTransfer(const std::chrono::milliseconds & timeout)
try {
auto onChange =
[&](std::string_view what, std::string_view from, std::string_view to, bool throw_
) -> void {
if (!from.empty() && from != to) {
FileTransferError e = FileTransferError(
Misc,
{},
"uri %s changed %s from %s to %s during transfer",
uri,
what,
from,
to
);
if (throw_) {
throw e;
}
logWarning(e.info());
}
};
// use the effective URI of the previous transfer for retries. this avoids
// some silent corruption if a redirect changes between starting and retry
const auto & uri = metadata.effectiveUri.empty() ? this->uri : metadata.effectiveUri;
auto newMeta = TRY_AWAIT(startTransfer(uri, timeout, totalReceived));
throwChangedTarget("final destination", metadata.effectiveUri, newMeta.effectiveUri);
throwChangedTarget("ETag", metadata.etag, newMeta.etag);
throwChangedTarget(
onChange("final destination", metadata.effectiveUri, newMeta.effectiveUri, false);
onChange("ETag", metadata.etag, newMeta.etag, true);
onChange(
"immutable url",
metadata.immutableUrl.value_or(""),
newMeta.immutableUrl.value_or("")
newMeta.immutableUrl.value_or(""),
true
);
co_return result::success();
} catch (...) {
+1
View File
@@ -18,6 +18,7 @@
#include <kj/async.h>
#include <queue>
#include <regex>
#include <thread>
#include <errno.h>
#include <fcntl.h>
+327 -43
View File
@@ -1,47 +1,16 @@
#include "lix/libstore/machines.hh"
#include "lix/libstore/globals.hh"
#include "lix/libstore/store-api.hh"
#include "lix/libutil/async.hh"
#include "lix/libutil/strings.hh"
#include <numeric>
#include <algorithm>
namespace nix {
#include <toml.hpp>
Machine::Machine(decltype(storeUri) storeUri,
decltype(systemTypes) systemTypes,
decltype(sshKey) sshKey,
decltype(maxJobs) maxJobs,
decltype(speedFactor) speedFactor,
decltype(supportedFeatures) supportedFeatures,
decltype(mandatoryFeatures) mandatoryFeatures,
decltype(sshPublicHostKey) sshPublicHostKey) :
storeUri(
// Backwards compatibility: if the URI is schemeless, is not a path,
// and is not one of the special store connection words, prepend
// ssh://.
storeUri.find("://") != std::string::npos
|| storeUri.find("/") != std::string::npos
|| storeUri == "auto"
|| storeUri == "daemon"
|| storeUri == "local"
|| storeUri.starts_with("auto?")
|| storeUri.starts_with("daemon?")
|| storeUri.starts_with("local?")
|| storeUri.starts_with("?")
? storeUri
: "ssh://" + storeUri),
systemTypes(systemTypes),
sshKey(sshKey),
maxJobs(maxJobs),
speedFactor(speedFactor == 0.0f ? 1.0f : speedFactor),
supportedFeatures(supportedFeatures),
mandatoryFeatures(mandatoryFeatures),
sshPublicHostKey(sshPublicHostKey)
{
if (speedFactor < 0.0)
throw UsageError("speed factor must be >= 0");
}
namespace nix {
bool Machine::systemSupported(const std::string & system) const
{
@@ -71,6 +40,7 @@ try {
StoreConfig::Params storeParams;
if (storeUri.starts_with("ssh://")) {
// Remote builds become flakey, when having more than one ssh connection
storeParams["max-connections"] = "1";
}
@@ -100,6 +70,8 @@ try {
co_return result::current_exception();
}
namespace machines_legacy_parsing {
static std::vector<std::string> expandBuilderLines(const std::string & builders)
{
std::vector<std::string> result;
@@ -166,15 +138,43 @@ static Machine parseBuilderLine(const std::string & line)
if (!isSet(0))
throw FormatError("bad machine specification: store URL was not found at the first column of a row: '%s'", line);
auto storeUri = tokens[0];
// Backwards compatibility: if the URI is schemeless, is not a path,
// and is not one of the special store connection words, prepend
// ssh://.
storeUri = storeUri.find("://") != std::string::npos || storeUri.find("/") != std::string::npos
|| storeUri == "auto" || storeUri == "daemon" || storeUri == "local"
|| storeUri.starts_with("auto?") || storeUri.starts_with("daemon?")
|| storeUri.starts_with("local?") || storeUri.starts_with("?")
? storeUri
: "ssh://" + storeUri;
auto systemTypes = isSet(1) ? tokenizeString<std::set<std::string>>(tokens[1], ",")
: std::set<std::string>{settings.thisSystem};
auto sshKey = isSet(2) ? tokens[2] : "";
auto maxJobs = isSet(3) ? parseUnsignedIntField(3) : 1U;
auto speedFactor = isSet(4) ? parseFloatField(4) : 1.0f;
auto supportedFeatures =
isSet(5) ? tokenizeString<std::set<std::string>>(tokens[5], ",") : std::set<std::string>{};
auto mandatoryFeatures =
isSet(6) ? tokenizeString<std::set<std::string>>(tokens[6], ",") : std::set<std::string>{};
auto sshPublicHostKey = isSet(7) ? ensureBase64(7) : "";
speedFactor = speedFactor == 0.0f ? 1.0f : speedFactor;
if (speedFactor < 0.0) {
throw UsageError("speed factor must be >= 0");
}
return {
tokens[0],
isSet(1) ? tokenizeString<std::set<std::string>>(tokens[1], ",") : std::set<std::string>{settings.thisSystem},
isSet(2) ? tokens[2] : "",
isSet(3) ? parseUnsignedIntField(3) : 1U,
isSet(4) ? parseFloatField(4) : 1.0f,
isSet(5) ? tokenizeString<std::set<std::string>>(tokens[5], ",") : std::set<std::string>{},
isSet(6) ? tokenizeString<std::set<std::string>>(tokens[6], ",") : std::set<std::string>{},
isSet(7) ? ensureBase64(7) : ""
storeUri,
systemTypes,
sshKey,
maxJobs,
speedFactor,
supportedFeatures,
mandatoryFeatures,
sshPublicHostKey
};
}
@@ -192,3 +192,287 @@ Machines getMachines()
}
}
namespace machines_toml_parsing {
static constexpr int MIN_VERSION = 1;
static constexpr int LATEST_VERSION = 1;
// Toml format:
// [[machines.andesite]]
// uri = "..."
//
// [[machines.diorite]]
// ...
template<typename T>
static toml::result<T, std::string> parse(const toml::value & data, const std::string & key)
{
try {
return toml::success(toml::get<T>(data.at(key)));
} catch (toml::type_error & e) { // NOLINT(lix-foreign-exceptions)
return toml::failure<std::string>({e.what()});
} catch (std::out_of_range & _) { // NOLINT(lix-foreign-exceptions)
const auto ei =
toml::make_error_info(fmt("%s must be present", key), data, "but was not set");
return toml::failure(toml::format_error(ei));
}
}
template<typename T>
static toml::result<T, std::string>
parse(const toml::value & data, const std::string & key, T defaultValue)
{
if (!data.contains(key)) {
return toml::success(defaultValue);
}
try {
return toml::success(toml::get<T>(data.at(key)));
} catch (toml::type_error & e) { // NOLINT(lix-foreign-exceptions)
// invalid value
return toml::failure<std::string>({e.what()});
}
}
static const std::set<std::string> EXPECTED_KEYS = {
"uri",
"system-types",
"ssh-key",
"jobs",
"speed-factor",
"supported-features",
"mandatory-features",
"ssh-public-host-key",
"enable",
};
static toml::result<float, std::string> getSpeedFactor(const toml::value & data)
{
if (data.contains("speed-factor")) {
auto sf = data.at("speed-factor");
if (sf.is_integer()) {
return toml::success(static_cast<float>(sf.as_integer()));
}
if (sf.is_floating()) {
return toml::success(static_cast<float>(sf.as_floating()));
}
return toml::failure(toml::format_error(toml::make_error_info(
"bad_cast to floating for `speed-factor`", sf, "Was neither an integer nor a float"
)));
}
return toml::success(1.0f);
}
static toml::result<Machine, std::vector<std::string>> parseMachine(const toml::value & data)
{
std::vector<std::string> errs;
if (!data.is_table()) {
errs.push_back(toml::format_error(toml::make_error_info(
"Each machine must be a table", data, "This should be a table. Did you mean `.uri = `?"
)));
return toml::failure(errs);
}
// parsing
auto storeUri = parse<std::string>(data, "uri");
auto systemTypes = parse<std::vector<std::string>>(
data, "system-types", std::vector<std::string>{settings.thisSystem}
);
auto sshKey = parse<std::string>(data, "ssh-key", "");
auto maxJobs = parse<int>(data, "jobs", 1U);
auto speedFactor = getSpeedFactor(data);
auto supportedFeatures =
parse<std::vector<std::string>>(data, "supported-features", std::vector<std::string>{});
auto mandatoryFeatures =
parse<std::vector<std::string>>(data, "mandatory-features", std::vector<std::string>{});
auto sshPublicHostKey = parse<std::string>(data, "ssh-public-host-key", "");
// parsing validation
if (storeUri.is_err()) {
errs.push_back(storeUri.as_err());
}
if (systemTypes.is_err()) {
errs.push_back(systemTypes.as_err());
}
if (sshKey.is_err()) {
errs.push_back(sshKey.as_err());
}
if (maxJobs.is_err()) {
errs.push_back(maxJobs.as_err());
}
if (speedFactor.is_err()) {
errs.push_back(speedFactor.as_err());
}
if (supportedFeatures.is_err()) {
errs.push_back(supportedFeatures.as_err());
}
if (mandatoryFeatures.is_err()) {
errs.push_back(mandatoryFeatures.as_err());
}
if (sshPublicHostKey.is_err()) {
errs.push_back(sshPublicHostKey.as_err());
}
// value validation
if (maxJobs.is_ok() && maxJobs.as_ok() < 0) {
auto ei =
toml::make_error_info("jobs must be >= 0", data.at("jobs"), "but got negative value");
errs.push_back(toml::format_error(ei));
}
if (speedFactor.is_ok() && speedFactor.as_ok() < 0.0) {
auto ei = toml::make_error_info(
"speed factor must be >= 0", data.at("speed-factor"), "but got negative value"
);
errs.push_back(toml::format_error(ei));
}
for (const auto & [key, _] : data.as_table()) {
if (!EXPECTED_KEYS.contains(key)) {
errs.push_back(toml::format_error(toml::make_error_info(
fmt("unexpected key `%s`", key), data.at(key), "should not be present"
)));
}
}
if (!errs.empty()) {
return toml::failure(errs);
}
return toml::success<Machine>({
storeUri.unwrap(),
std::set(systemTypes.unwrap().begin(), systemTypes.unwrap().end()),
sshKey.unwrap(),
static_cast<unsigned>(maxJobs.unwrap()),
speedFactor.unwrap(),
std::set(supportedFeatures.unwrap().begin(), supportedFeatures.unwrap().end()),
std::set(mandatoryFeatures.unwrap().begin(), mandatoryFeatures.unwrap().end()),
base64Encode(sshPublicHostKey.unwrap()),
});
}
static toml::result<Machines, std::vector<std::string>> parseToml(const toml::value & data)
{
auto const array_name = "machines";
std::vector<std::string> parserErrors;
Machines machines;
// Empty config
if (data.size() == 0) {
return toml::success<Machines>({});
}
if (!data.is_table()) {
parserErrors.push_back(
"Top level must be a table. This should never throw as this is required by the toml "
"SPEC"
);
return toml::failure(parserErrors);
}
if (auto config_version = parse<int>(data, "version", LATEST_VERSION); config_version.is_err())
{
parserErrors.push_back(config_version.as_err());
} else if (config_version.as_ok() < MIN_VERSION || config_version.as_ok() > LATEST_VERSION) {
parserErrors.push_back(
fmt("Unable to parse Machines of version %d, only versions between %d and %d are "
"supported.",
config_version.as_ok(),
MIN_VERSION,
LATEST_VERSION)
);
}
if (!parserErrors.empty()) {
return toml::failure(parserErrors);
}
auto & tbl = data.as_table();
std::string unexpected_keys;
for (auto it = tbl.begin(); it != tbl.end(); ++it) {
if (it->first == array_name || it->first == "version") {
// expected keys
continue;
}
unexpected_keys += ", " + it->first;
}
if (unexpected_keys.size()) {
parserErrors.push_back(fmt("unexpected keys found: %s", unexpected_keys.erase(0, 2)));
}
if (!data.at(array_name).is_table()) {
parserErrors.push_back(
fmt("Expected key `%s` to be a table of name -> machine configurations", array_name)
);
return toml::failure(parserErrors);
}
for (const auto & [name, machine] : data.at(array_name).as_table()) {
auto const res = parseMachine(machine);
if (res.is_err()) {
auto err = res.as_err();
parserErrors.push_back(fmt("for machine %s:", name));
parserErrors.insert(parserErrors.end(), err.begin(), err.end());
continue;
}
auto enable = parse<bool>(machine, "enable", true);
if (enable.is_ok()) {
if (enable.unwrap()) {
// Check if it hasn't been statically disabled
// But still throw parsing errors if it was
machines.push_back(res.unwrap());
}
} else {
parserErrors.push_back(enable.as_err());
}
}
if (!parserErrors.empty()) {
return toml::failure(parserErrors);
}
return toml::success(machines);
}
static std::optional<Machines> getMachines()
{
toml::value data;
auto buildersStr = settings.builders.get();
try {
if (buildersStr.size() > 0 && buildersStr.at(0) == '@') {
data = toml::parse(buildersStr.substr(1));
} else {
data = toml::parse_str(settings.builders);
}
} catch (toml::syntax_error const & e) { // NOLINT(lix-foreign-exceptions)
if (toLower(buildersStr).contains("toml") || buildersStr.contains("\"")) {
// Yes, we are sure this is a TOML and no this shitty legacy format
// so we can safely throw the syntax error here
throw UsageError(fmt("invalid Machines TOML syntax: \n%s", e.what()));
}
return {};
} catch (toml::file_io_error const & _) { // NOLINT(lix-foreign-exceptions)
// sadly we have to do this otherwise we break the old format,
// which requires **silently ignoring** invalid files
return {};
}
auto const fromToml = parseToml(data);
if (fromToml.is_ok()) {
return fromToml.unwrap();
}
auto const & errs = fromToml.as_err();
std::string msg = "invalid Machines TOML:\n";
msg += concatStringsSep("\n", errs);
throw UsageError(msg);
}
}
Machines getMachines()
{
auto const toml_result = machines_toml_parsing::getMachines();
if (toml_result.has_value()) {
return toml_result.value();
}
debug("Trying again with legacy format");
return machines_legacy_parsing::getMachines();
}
}
-9
View File
@@ -41,15 +41,6 @@ struct Machine {
*/
bool mandatoryMet(const std::set<std::string> & features) const;
Machine(decltype(storeUri) storeUri,
decltype(systemTypes) systemTypes,
decltype(sshKey) sshKey,
decltype(maxJobs) maxJobs,
decltype(speedFactor) speedFactor,
decltype(supportedFeatures) supportedFeatures,
decltype(mandatoryFeatures) mandatoryFeatures,
decltype(sshPublicHostKey) sshPublicHostKey);
kj::Promise<Result<std::pair<ref<Store>, Pipe>>> openStore() const;
};
+1 -1
View File
@@ -471,6 +471,6 @@ configure_file(
'libdir' : libdir,
'includedir' : includedir,
'PACKAGE_VERSION' : meson.project_version(),
'AWS_SDK_IF_FOUND' : aws_sdk.found() ? 'aws-cpp-sdk-core aws-cpp-sdk-s3 aws-cpp-std-transfer' : '',
'AWS_SDK_IF_FOUND' : aws_sdk.found() ? 'aws-cpp-sdk-core aws-cpp-sdk-s3 aws-cpp-sdk-transfer' : '',
},
)
+1 -1
View File
@@ -265,7 +265,7 @@ static std::map<StorePath, Node> mkGraph(
for (auto & node : graph_data) {
for (auto & ref : node.second.dependencies) {
graph_data.find(ref)->second.dependents.insert(node.first);
graph_data.at(ref).dependents.insert(node.first);
}
}
+1 -1
View File
@@ -253,7 +253,7 @@ time_t parseOlderThanTimeSpec(std::string_view timeSpec)
time_t curTime = time(0);
auto strDays = timeSpec.substr(0, timeSpec.size() - 1);
auto days = string2Int<int>(strDays);
auto days = string2Int<time_t>(strDays);
if (!days || *days < 1)
throw UsageError("invalid number of days specifier '%1%'", timeSpec);
+7 -4
View File
@@ -195,8 +195,8 @@ ref<Aws::Client::ClientConfiguration> S3Helper::makeConfig(
if (!endpoint.empty()) {
res->endpointOverride = endpoint;
}
res->requestTimeoutMs = 600 * 1000;
res->connectTimeoutMs = 5 * 1000;
res->requestTimeoutMs = 600l * 1000;
res->connectTimeoutMs = 5l * 1000;
res->retryStrategy = std::make_shared<RetryStrategy>();
res->caFile = settings.caFile;
// Use the system proxy env-vars in curl for s3, which is off by default for some reason
@@ -312,8 +312,11 @@ struct S3BinaryCacheStoreConfig final : BinaryCacheStoreConfig
"Whether to use multi-part uploads."};
const Setting<uint64_t> bufferSize{
this, 5 * 1024 * 1024, "buffer-size",
"Size (in bytes) of each part in multi-part uploads."};
this,
5ul * 1024 * 1024,
"buffer-size",
"Size (in bytes) of each part in multi-part uploads."
};
const std::string name() override { return "S3 Binary Cache Store"; }
+17 -2
View File
@@ -1338,7 +1338,19 @@ try {
Derivation::nameFromPath(drvPath)
);
} catch (FormatError & e) {
throw Error("error parsing derivation '%s': %s", store.printStorePath(drvPath), e.msg());
auto drvPathS = store.printStorePath(drvPath);
throw Error(
fmt("error parsing derivation '%1%': %2%\n"
"This can occur when the derivation is corrupted.\n"
"You can check this with `nix-store --verify-path %1%` and possibly repair with "
"`nix-store --repair-path %1%`.\n"
"In case the repair cannot be done, `nix-store --delete %1%` may be able "
"to remove the broken path.\n"
"We would appreciate a bug report at "
"https://git.lix.systems/lix-project/lix/issues if you think this is a bug.",
drvPathS,
e.msg())
);
}
} catch (...) {
co_return result::current_exception();
@@ -1537,7 +1549,10 @@ try {
try {
(*lk)->push_back(TRY_AWAIT(openStore(uri)));
} catch (Error & e) {
logWarning(e.info());
logWarning(
{.msg = HintFmt("Failed to setup the substituter at URI '%s': %s", uri, e.msg())
}
);
}
}
+2 -2
View File
@@ -121,7 +121,7 @@ public:
{
}
AsyncBufferedInputStream(AsyncInputStream & inner, size_t bufSize = 32 * 1024)
AsyncBufferedInputStream(AsyncInputStream & inner, size_t bufSize = 32ul * 1024)
: AsyncBufferedInputStream(inner, make_ref<IoBuffer>(bufSize))
{
}
@@ -173,7 +173,7 @@ public:
{
}
AsyncBufferedOutputStream(AsyncOutputStream & inner, size_t bufSize = 32 * 1024)
AsyncBufferedOutputStream(AsyncOutputStream & inner, size_t bufSize = 32ul * 1024)
: AsyncBufferedOutputStream(inner, make_ref<IoBuffer>(bufSize))
{
}
+8 -7
View File
@@ -16,7 +16,6 @@
#include <archive.h>
#include <archive_entry.h>
#include <cerrno>
#include <chrono>
#include <cstdio>
#include <cstring>
@@ -38,7 +37,7 @@ static const int COMPRESSION_LEVEL_DEFAULT = -1;
// Don't feed brotli too much at once.
struct ChunkedCompressionSink : CompressionSink
{
uint8_t outbuf[32 * 1024];
uint8_t outbuf[32ul * 1024];
void writeUnbuffered(std::string_view data) override
{
@@ -63,13 +62,15 @@ struct ArchiveDecompressionSource : Source
struct archive_entry * ae;
if (!archive) {
archive = std::make_unique<TarArchive>(*src, true);
this->archive->check(archive_read_next_header(this->archive->archive, &ae),
"failed to read header (%s)");
if (archive_filter_count(this->archive->archive) < 2) {
this->archive->check(
archive_read_next_header(this->archive->archive.get(), &ae),
"failed to read header (%s)"
);
if (archive_filter_count(this->archive->archive.get()) < 2) {
throw CompressionError("input compression not recognized");
}
}
ssize_t result = archive_read_data(this->archive->archive, data, len);
ssize_t result = archive_read_data(this->archive->archive.get(), data, len);
if (result > 0) return result;
if (result == 0) {
throw EndOfFile("reached end of compressed file");
@@ -167,7 +168,7 @@ struct NoneSink : CompressionSink
struct BrotliDecompressionSource : Source
{
static constexpr size_t BUF_SIZE = 32 * 1024;
static constexpr size_t BUF_SIZE = 32ul * 1024;
std::unique_ptr<char[]> buf;
size_t avail_in = 0;
const uint8_t * next_in;
@@ -0,0 +1,6 @@
---
name: nix-path-shadow
internalName: NixPathShadow
---
Allows shadowing `<nix/fetchurl.nix>` by configuration of the [*nix path*](@docroot@/language/builtin-constants.html#builtins-nixPath) to a value containing `nix=/some/path`.
@@ -1,5 +0,0 @@
---
name: fetch-closure
internalName: FetchClosure
---
Enable the use of the [`fetchClosure`](@docroot@/language/builtins.md#builtins-fetchClosure) built-in function in the Nix language.
+1 -1
View File
@@ -133,7 +133,7 @@ Generator<Bytes> drainFDSource(int fd, bool block)
}
});
std::array<unsigned char, 64 * 1024> buf;
std::array<unsigned char, 64ul * 1024> buf;
while (1) {
checkInterrupt();
ssize_t rd = read(fd, buf.data(), buf.size());
+3 -3
View File
@@ -443,7 +443,7 @@ void writeFile(const Path & path, Source & source, mode_t mode)
{
AutoCloseFD fd = openForWrite(path, mode);
std::vector<char> buf(64 * 1024);
std::vector<char> buf(64ul * 1024);
try {
while (true) {
@@ -463,7 +463,7 @@ void writeFileExcl(const Path & path, Source & source, mode_t mode)
{
AutoCloseFD fd = openForWriteExcl(path, mode);
std::vector<char> buf(64 * 1024);
std::vector<char> buf(64ul * 1024);
try {
while (true) {
@@ -483,7 +483,7 @@ kj::Promise<Result<void>> writeFile(const Path & path, AsyncInputStream & source
try {
AutoCloseFD fd = openForWrite(path, mode);
std::vector<char> buf(64 * 1024);
std::vector<char> buf(64ul * 1024);
try {
while (true) {
+2 -3
View File
@@ -191,9 +191,8 @@ Hash::Hash(std::string_view rest, HashType type, bool isSRI)
};
for (unsigned int i = 0; i < hashSize; i++) {
hash[i] =
parseHexDigit(rest[i * 2]) << 4
| parseHexDigit(rest[i * 2 + 1]);
const size_t j = i << 1;
hash[i] = parseHexDigit(rest[j]) << 4 | parseHexDigit(rest[j + 1]);
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ class IoBuffer
std::unique_ptr<char[]> buffer;
public:
explicit IoBuffer(size_t bufSize = 32 * 1024) : bufSize(bufSize) {}
explicit IoBuffer(size_t bufSize = 32ul * 1024) : bufSize(bufSize) {}
size_t size() const
{
+2 -2
View File
@@ -107,8 +107,8 @@ struct RpcLogger : Logger
}
buffer->sizeEstimate += sizeof(e) + extraSize;
buffer->items.emplace_back(std::move(e));
return buffer->sizeEstimate >= 1024 * 1024 ? BufferState::NeedsFlush
: BufferState::HasSpace;
return buffer->sizeEstimate >= static_cast<size_t>(1024 * 1024) ? BufferState::NeedsFlush
: BufferState::HasSpace;
}
BufferState log(Verbosity lvl, std::string_view s) override
+1 -1
View File
@@ -158,7 +158,6 @@ experimental_feature_definitions = files(
'experimental-features/cgroups.md',
'experimental-features/coerce-integers.md',
'experimental-features/daemon-trust-override.md',
'experimental-features/fetch-closure.md',
'experimental-features/flake-self-attrs.md',
'experimental-features/flakes.md',
'experimental-features/lix-custom-sub-commands.md',
@@ -173,6 +172,7 @@ deprecated_feature_definitions = files(
# keep-sorted start
'deprecated-features/ancient-let.md',
'deprecated-features/cr-line-endings.md',
'deprecated-features/nix-path-shadow.md',
'deprecated-features/nul-bytes.md',
'deprecated-features/rec-set-overrides.md',
'deprecated-features/shadow-internal-symbols.md',
+1 -1
View File
@@ -214,7 +214,7 @@ Pid startProcess(std::function<void()> fun, const ProcessOptions & options)
// Not supported, since then we don't know when to free the stack.
assert(!(options.cloneFlags & CLONE_VM));
size_t stackSize = 1 * 1024 * 1024;
size_t stackSize = 1ul * 1024 * 1024;
auto stack = static_cast<char *>(mmap(0, stackSize,
PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0));
if (stack == MAP_FAILED) throw SysError("allocating stack");
+2 -2
View File
@@ -53,7 +53,7 @@ struct BufferedSink : virtual Sink
{
ref<IoBuffer> buffer;
BufferedSink(size_t bufSize = 32 * 1024) : buffer(make_ref<IoBuffer>(bufSize)) {}
BufferedSink(size_t bufSize = 32ul * 1024) : buffer(make_ref<IoBuffer>(bufSize)) {}
explicit BufferedSink(ref<IoBuffer> buffer) : buffer(std::move(buffer)) {}
void operator () (std::string_view data) override;
@@ -105,7 +105,7 @@ struct BufferedSource : Source
{
ref<IoBuffer> buffer;
BufferedSource(size_t bufSize = 32 * 1024) : buffer(make_ref<IoBuffer>(bufSize)) {}
BufferedSource(size_t bufSize = 32ul * 1024) : buffer(make_ref<IoBuffer>(bufSize)) {}
explicit BufferedSource(ref<IoBuffer> buffer) : buffer(std::move(buffer)) {}
size_t read(char * data, size_t len) override;
+75 -30
View File
@@ -1,9 +1,11 @@
#include <archive.h>
#include <archive_entry.h>
#include <kj/async.h>
#include <map>
#include "async-io.hh"
#include "file-descriptor.hh"
#include "lix/libstore/temporary-dir.hh"
#include "lix/libutil/c-calls.hh"
#include "lix/libutil/charptr-cast.hh"
#include "lix/libutil/file-system.hh"
@@ -45,67 +47,110 @@ void TarArchive::check(int err, const std::string & reason)
if (err == ARCHIVE_EOF)
throw EndOfFile("reached end of archive");
else if (err != ARCHIVE_OK)
throw Error(reason, archive_error_string(this->archive));
throw Error(reason, archive_error_string(this->archive.get()));
}
TarArchive::TarArchive(Source & source, bool raw) : buffer(65536)
TarArchive::TarArchive(Source & source, bool raw)
: archive{archive_read_new(), archive_read_free}
, source(&source)
, buffer(65536)
{
this->archive = archive_read_new();
this->source = &source;
if (!raw) {
archive_read_support_filter_all(archive);
archive_read_support_format_all(archive);
archive_read_support_filter_all(archive.get());
archive_read_support_format_all(archive.get());
} else {
archive_read_support_filter_all(archive);
archive_read_support_format_raw(archive);
archive_read_support_format_empty(archive);
archive_read_support_filter_all(archive.get());
archive_read_support_format_raw(archive.get());
archive_read_support_format_empty(archive.get());
}
archive_read_set_option(archive, nullptr, "mac-ext", nullptr);
check(archive_read_open(archive, (void *)this, callback_open, callback_read, callback_close), "Failed to open archive (%s)");
archive_read_set_option(archive.get(), nullptr, "mac-ext", nullptr);
check(
archive_read_open(
archive.get(), (void *) this, callback_open, callback_read, callback_close
),
"Failed to open archive (%s)"
);
}
TarArchive::TarArchive(const Path & path)
TarArchive::TarArchive(const Path & path) : archive{archive_read_new(), archive_read_free}
{
this->archive = archive_read_new();
archive_read_support_filter_all(archive);
archive_read_support_format_all(archive);
archive_read_set_option(archive, nullptr, "mac-ext", nullptr);
archive_read_support_filter_all(archive.get());
archive_read_support_format_all(archive.get());
archive_read_set_option(archive.get(), nullptr, "mac-ext", nullptr);
check(
archive_read_open_filename(archive, requireCString(path), 16384),
archive_read_open_filename(archive.get(), requireCString(path), 16384),
"failed to open archive: %s"
);
}
void TarArchive::close()
{
check(archive_read_close(this->archive), "Failed to close archive (%s)");
check(archive_read_close(this->archive.get()), "Failed to close archive (%s)");
}
TarArchive::~TarArchive()
namespace {
struct IndexEntry
{
if (this->archive) archive_read_free(this->archive);
struct IndexDirectory
{
std::map<std::string, IndexEntry> entries;
};
struct IndexFile
{
uint64_t start;
uint64_t size;
};
std::variant<IndexDirectory, IndexFile> innerData;
int mode;
};
/** Extract a TAR archive into an in-memory index with data stored on disk,
* because fundamentally all we have to do when importing archives to the store
* is to sort the entries and then send it into a NAR serializer.
*
* This deals with macOS performance problems by not materializing the files to
* disk in the first place.
*
* https://git.lix.systems/lix-project/lix/issues/1072
*/
struct InMemoryIndex
{
Path tempDir;
AutoDelete tempDirDeleter;
AutoCloseFD backingFile;
std::ofstream writer;
IndexEntry topLevel;
InMemoryIndex() : tempDir(createTempDir()), tempDirDeleter(tempDir, true)
{
backingFile = sys::open(tempDir + "/extracted", O_RDWR | O_TRUNC | O_CLOEXEC);
}
};
}
static void extract_archive(TarArchive & archive, const Path & destDir)
{
requireCString(destDir);
int flags = ARCHIVE_EXTRACT_TIME
| ARCHIVE_EXTRACT_SECURE_SYMLINKS
| ARCHIVE_EXTRACT_SECURE_NODOTDOT;
int flags = ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_SECURE_SYMLINKS
| ARCHIVE_EXTRACT_SECURE_NODOTDOT | ARCHIVE_EXTRACT_NO_HFS_COMPRESSION;
for (;;) {
struct archive_entry * entry;
int r = archive_read_next_header(archive.archive, &entry);
int r = archive_read_next_header(archive.archive.get(), &entry);
if (r == ARCHIVE_EOF) break;
auto name = archive_entry_pathname(entry);
if (!name)
throw Error("cannot get archive member name: %s", archive_error_string(archive.archive));
throw Error(
"cannot get archive member name: %s", archive_error_string(archive.archive.get())
);
if (r == ARCHIVE_WARN)
printTaggedWarning("%1%", Uncolored(archive_error_string(archive.archive)));
printTaggedWarning("%1%", Uncolored(archive_error_string(archive.archive.get())));
else
archive.check(r);
@@ -125,7 +170,7 @@ static void extract_archive(TarArchive & archive, const Path & destDir)
(destDir + "/" + original_hardlink).c_str());
}
archive.check(archive_read_extract(archive.archive, entry, flags));
archive.check(archive_read_extract(archive.archive.get(), entry, flags));
}
archive.close();
+1 -3
View File
@@ -8,7 +8,7 @@
namespace nix {
struct TarArchive {
struct archive * archive;
std::unique_ptr<struct archive, decltype(&archive_read_free)> archive;
Source * source;
std::vector<unsigned char> buffer;
@@ -22,8 +22,6 @@ struct TarArchive {
TarArchive(const TarArchive &) = delete;
void close();
~TarArchive();
};
kj::Promise<Result<void>> unpackTarfile(AsyncInputStream & source, const Path & destDir);
+8
View File
@@ -11,6 +11,14 @@
#include <span>
#include <stdint.h> // IWYU pragma: keep (this is used literally everywhere)
// NOTE: This check is load-bearing, c.f.
// https://git.lix.systems/lix-project/lix/commit/96a36833084f66b79ba778e719862b8219557773
// It is put here so that every consumer of our headers are prohibited from
// compiling with NDEBUG as some asserts are used in inline code
#ifdef NDEBUG
#error "Lix may not be built with assertions disabled (i.e. with -DNDEBUG)."
#endif
namespace nix {
typedef std::list<std::string> Strings;
+18 -2
View File
@@ -73,8 +73,24 @@ Path getHome()
Path getCacheDir()
{
auto cacheDir = getEnv("XDG_CACHE_HOME");
return cacheDir ? *cacheDir : getHome() + "/.cache";
// We follow systemd semantics here:
// https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#RuntimeDirectory=
static auto cacheDir = [] {
auto userCacheDir = getEnv("XDG_CACHE_HOME");
auto serviceCacheDir = getEnv("CACHE_DIRECTORY");
if (serviceCacheDir) {
return *serviceCacheDir;
}
if (userCacheDir) {
return *userCacheDir;
}
return getHome() + "/.cache";
}();
return cacheDir;
}
+10
View File
@@ -1,7 +1,17 @@
# Cursed, but I don't think there's another way to get this environment variable.
lix_suffix = run_command('bash', '-c', 'echo -n "$VERSION_SUFFIX"', check : true).stdout().strip()
lix_version_parts = meson.project_version().split('.')
lix_major = lix_version_parts[0]
lix_minor = lix_version_parts[1]
lix_patch = lix_version_parts[2].replace(lix_suffix, '')
config_h = configure_file(
configuration : {
'PACKAGE_NAME': '"' + meson.project_name() + '"',
'PACKAGE_VERSION': '"' + meson.project_version() + '"',
'LIX_MAJOR': lix_major,
'LIX_MINOR': lix_minor,
'LIX_PATCH': lix_patch,
'PACKAGE_TARNAME': '"' + meson.project_name() + '"',
'PACKAGE_STRING': '"' + meson.project_name() + ' ' + meson.project_version() + '"',
'HAVE_STRUCT_DIRENT_D_TYPE': 1, # FIXME: actually check this for solaris
+1 -33
View File
@@ -55,7 +55,6 @@
#endif
static constexpr int SUBDAEMON_CONNECTION_FD = 0;
static constexpr int SUBDAEMON_SETTINGS_FD = 3;
namespace nix {
@@ -346,9 +345,6 @@ try {
peer.pidKnown ? fmt("pid %1%", peer.pid) : "unknown peer"
);
Pipe settings;
settings.create();
// Fork a child to handle the connection. make sure it's called with
// argv0 `nix-daemon` so we don't try to run `nix --for` when called
// from more modern scripts that assume nix-command being available.
@@ -363,11 +359,7 @@ try {
fmt("%1%", int(verbosity)),
},
.dieWithParent = false,
.redirections =
{
{.dup = SUBDAEMON_CONNECTION_FD, .from = remote.get()},
{.dup = SUBDAEMON_SETTINGS_FD, .from = settings.readSide.get()},
}
.redirections = {{.dup = SUBDAEMON_CONNECTION_FD, .from = remote.get()}}
};
if (forceTrustClientOpt) {
options.args.push_back(
@@ -375,15 +367,6 @@ try {
);
}
runProgram2(options).release();
FdSink sink(settings.writeSide.get());
std::map<std::string, Config::SettingInfo> overriddenSettings;
globalConfig.getSettings(overriddenSettings, true);
for (auto & setting : overriddenSettings) {
sink << 1 << setting.first << setting.second.value;
}
sink << 0;
sink.flush();
} catch (Error & error) {
auto ei = error.info();
// FIXME: add to trace?
@@ -417,21 +400,6 @@ static void daemonInstance(AsyncIoRoot & aio, std::optional<TrustedFlag> forceTr
forceTrustClientOpt ? " by override" : ""
);
{
FdSource source(SUBDAEMON_SETTINGS_FD);
/* Read the parent's settings. */
while (readNum<unsigned>(source)) {
auto name = readString(source);
auto value = readString(source);
settings.set(name, value);
}
if (close(SUBDAEMON_SETTINGS_FD) < 0) {
throw SysError("preparing subdaemon connection");
}
}
// Background the daemon.
if (setsid() == -1) {
throw SysError("creating a new session");
+11
View File
@@ -257,6 +257,10 @@ try {
output.second = DerivationOutput::InputAddressed { .path = StorePath::dummy };
drv.env[output.first] = "";
}
// outputs default to [ "out" ]
drv.env.try_emplace("outputs", "out");
auto hashesModulo = TRY_AWAIT(hashDerivationModulo(*evalStore, drv, true));
for (auto & output : drv.outputs) {
@@ -352,10 +356,17 @@ struct Common : InstallableCommand, MixProfile
buildEnvironment.toBash(out, ignoreVars);
// Add NIX_SHELL_LEVEL
out << "printf -v NIX_SHELL_LEVEL \"%d\" \"${NIX_SHELL_LEVEL:-0}\" || NIX_SHELL_LEVEL=0\n";
out << "export NIX_SHELL_LEVEL=$(( NIX_SHELL_LEVEL + 1 ))\n";
for (auto & var : savedVars)
out << fmt("%s=\"$%s${nix_saved_%s:+:$nix_saved_%s}\"\n", var, var, var, var);
out << "export NIX_BUILD_TOP=\"$(mktemp -d -t nix-shell.XXXXXX)\"\n";
// We re-export similarly to what occurs inside of a derivation goal `NIX_LOG_FD` to stderr.
// So that stdenv hooks that logs information can be observed inside this debugging tool.
out << "export NIX_LOG_FD=2\n";
for (auto & i : {"TMP", "TMPDIR", "TEMP", "TEMPDIR"})
out << fmt("export %s=\"$NIX_BUILD_TOP\"\n", i);
+1 -1
View File
@@ -152,7 +152,7 @@ void renderDiffInfo(
const std::string_view indent)
{
for (auto & [name, item] : diff) {
auto showDelta = std::abs(item.sizeDelta) >= 8 * 1024;
auto showDelta = std::abs(item.sizeDelta) >= 8l * 1024;
std::vector<std::string> line;
if (!item.removedVersions.empty() || !item.addedVersions.empty())
+1 -1
View File
@@ -110,7 +110,7 @@ struct CmdEval : MixJSON, InstallableCommand, MixReadOnlyOption
v,
PrintOptions{
.force = true,
.derivationPaths = true,
.derivationPathDepth = 0,
.errors = ErrorPrintBehavior::ThrowTopLevel,
}
)
+10 -9
View File
@@ -455,8 +455,6 @@ void registerNixHelp()
int mainWrapped(AsyncIoRoot & aio, int argc, char ** argv)
{
savedArgv = argv;
/* The chroot helper needs to be run before any threads have been
started. */
if (argc > 0 && argv[0] == chrootHelperName) {
@@ -493,14 +491,19 @@ int mainWrapped(AsyncIoRoot & aio, int argc, char ** argv)
registerLegacyCommands();
auto legacy = (*LegacyCommandRegistry::commands)[programName];
if (legacy) {
return legacy(aio, std::string(baseNameOf(argv[0])), Strings(argv + 1, argv + argc));
return legacy(
aio,
std::string(baseNameOf(argv[0])),
Strings(argv + 1, argv + argc),
{argv + 1, argv + argc}
);
}
}
evalSettings.pureEval.setDefault(true);
setLogFormat(LogFormat::bar);
settings.verboseBuild = false;
setLogFormat(LogFormat::barWithLogs);
// FIXME: stop messing about with log verbosity depending on if it is interactive use
if (isatty(STDERR_FILENO)) {
verbosity = lvlNotice;
@@ -521,8 +524,7 @@ int mainWrapped(AsyncIoRoot & aio, int argc, char ** argv)
if (argc == 2 && std::string(argv[1]) == "__dump-language") {
experimentalFeatureSettings.experimentalFeatures.override(ExperimentalFeatures{}
| Xp::Flakes
| Xp::FetchClosure);
| Xp::Flakes);
evalSettings.pureEval.override(false);
Evaluator state(aio, {}, aio.blockOn(openStore("dummy://")));
auto res = JSON::object();
@@ -646,7 +648,6 @@ int mainWrapped(AsyncIoRoot & aio, int argc, char ** argv)
return 0;
}
}
int main(int argc, char * * argv)
@@ -658,7 +659,7 @@ int main(int argc, char * * argv)
// Increase the default stack size for the evaluator and for
// libstdc++'s std::regex.
nix::setStackSize(64 * 1024 * 1024);
nix::setStackSize(64ul * 1024 * 1024);
return nix::handleExceptions(argv[0], [&]() {
nix::AsyncIoRoot aio;
+2 -1
View File
@@ -12,6 +12,7 @@ struct CmdMakeContentAddressed : virtual CopyCommand, virtual StorePathsCommand,
CmdMakeContentAddressed()
{
realiseMode = Realise::Outputs;
requireStore = false;
}
std::string description() override
@@ -28,7 +29,7 @@ struct CmdMakeContentAddressed : virtual CopyCommand, virtual StorePathsCommand,
void run(ref<Store> srcStore, StorePaths && storePaths) override
{
auto dstStore = aio().blockOn(dstUri.empty() ? openStore() : openStore(dstUri));
auto dstStore = getDstStore();
auto remappings = aio().blockOn(makeContentAddressed(*srcStore, *dstStore,
StorePathSet(storePaths.begin(), storePaths.end())));
+13 -1
View File
@@ -150,6 +150,17 @@ struct CmdShell : InstallablesCommand, MixEnvironment
auto unixPathString = concatStringsSep(":", unixPath);
(void) sys::setenv("PATH", unixPathString, 1);
(void) sys::setenv("IN_NIX_SHELL", ignoreEnvironment ? "pure" : "impure", 1);
// Set NIX_SHELL_LEVEL
(void) sys::setenv(
"NIX_SHELL_LEVEL",
std::to_string(
getEnvNonEmpty("NIX_SHELL_LEVEL")
.and_then([](std::string lvl) { return string2Int<size_t>(lvl); })
.value_or(0)
+ 1
),
1
);
Strings args;
for (auto & arg : command) args.push_back(arg);
@@ -230,6 +241,7 @@ void registerNixRun()
void chrootHelper(int argc, char * * argv)
{
#if __linux__
int p = 1;
std::string storeDir = argv[p++];
std::string realStoreDir = argv[p++];
@@ -239,7 +251,6 @@ void chrootHelper(int argc, char * * argv)
while (p < argc)
args.push_back(argv[p++]);
#if __linux__
uid_t uid = getuid();
uid_t gid = getgid();
@@ -310,6 +321,7 @@ void chrootHelper(int argc, char * * argv)
throw SysError("unable to exec '%s'", cmd);
#else
std::string storeDir = argv[1];
throw Error("mounting the Nix store on '%s' is not supported on this platform", storeDir);
#endif
}
+1 -1
View File
@@ -58,7 +58,7 @@ struct CmdStoreDelete : StorePathsCommand
}
GCResults results;
PrintFreed freed(true, results);
PrintFreed freed(options.action, results);
aio().blockOn(gcStore.collectGarbage(options, results));
}
};

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