Compare commits

...

80 Commits

Author SHA1 Message Date
Eelco Dolstra
5bc21dccc9 Merge pull request #8763 from hercules-ci/backport-7447-to-2.13-maintenance
Backport 7447 to 2.13 maintenance
2023-08-07 19:30:27 +02:00
Eelco Dolstra
01401ffffd Merge remote-tracking branch 'origin/backport-8483-to-2.13-maintenance' into 2.13-maintenance 2023-08-07 16:43:57 +02:00
Eelco Dolstra
d971ce3ed4 Add docs
(cherry picked from commit cab03fb779)
2023-08-07 16:38:50 +02:00
Eelco Dolstra
dc718e28c9 Allow tarball URLs to redirect to a lockable immutable URL
Previously, for tarball flakes, we recorded the original URL of the
tarball flake, rather than the URL to which it ultimately
redirects. Thus, a flake URL like
http://example.org/patchelf-latest.tar that redirects to
http://example.org/patchelf-<revision>.tar was not really usable. We
couldn't record the redirected URL, because sites like GitHub redirect
to CDN URLs that we can't rely on to be stable.

So now we use the redirected URL only if the server returns the
`x-nix-is-immutable` or `x-amz-meta-nix-is-immutable` headers in its
response.

(cherry picked from commit 1ad3328c5e)
2023-08-07 16:38:30 +02:00
Eelco Dolstra
cb425af931 Add a generic check for rev attribute mismatches
(cherry picked from commit 3402b650cd)
2023-08-07 16:33:57 +02:00
Eelco Dolstra
3cc2ff77da GC server: Clear O_NONBLOCK on the right file descriptor
The bug fix in 6d30f9e6fe erroneously
cleared O_NONBLOCK on the server rather than client FD (leaving both
in an incorrect state).

Fixes #8551.

(cherry picked from commit a6a75ecad8)
2023-08-07 16:31:04 +02:00
Eelco Dolstra
0a0a4e2ea3 restoreMountNamespace(): Restore the original root directory
This is necessary when we're in a chroot environment, where the
process root is not the same as the root of the mount namespace
(e.g. in nixos-enter).

Fixes #7602.

(cherry picked from commit e54538c461)
2023-08-07 08:23:32 +00:00
Robert Hensing
995b658e72 Fixup release notes (#8393)
* Fixup release notes
2023-07-31 16:40:57 +02:00
Robert Hensing
1dfcf41645 rl-next.md: Minor improvement
(cherry picked from commit 37c533ed27)
2023-07-31 16:37:44 +02:00
Alex Ameen
1a6446b5eb primop: add readFileType, optimize readDir
Allows checking directory entry type of a single file/directory.

This was added to optimize the use of `builtins.readDir` on some
filesystems and operating systems which cannot detect this information
using POSIX's `readdir`.

Previously `builtins.readDir` would eagerly use system calls to lookup
these filetypes using other interfaces; this change makes these
operations lazy in the attribute values for each file with application
of `builtins.readFileType`.

(cherry picked from commit 153ee460c5)
2023-07-31 16:37:44 +02:00
Eelco Dolstra
25e1eb4dfd Bump version 2023-05-30 11:53:09 +02:00
Théophane Hufschmitt
43aa47cf8a Merge pull request #8410 from tweag/backport-7957-to-2.13-maintenance
[Backport 2.13-maintenance] Switch to cachix/install-nix-action@v20
2023-05-28 17:24:13 +02:00
Théophane Hufschmitt
d17ecc5177 Switch to cachix/install-nix-action@v20
Fixes the installation issue with the latest Nix.

Also revert the pinning to nix-2.13 since it's not needed any more.

(cherry picked from commit c3b5499dff)
2023-05-28 17:23:37 +02:00
Théophane Hufschmitt
e0afeef10f Merge pull request #8409 from NixOS/backport-7766-to-2.13-maintenance
[Backport 2.13-maintenance] Bump cachix/install-nix-action from 18 to 19
2023-05-28 17:18:25 +02:00
dependabot[bot]
478b015577 Bump cachix/install-nix-action from 18 to 19
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 18 to 19.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Commits](https://github.com/cachix/install-nix-action/compare/v18...v19)

---
updated-dependencies:
- dependency-name: cachix/install-nix-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 6fdce7a9df)
2023-05-28 15:18:06 +00:00
Théophane Hufschmitt
691d1170e5 Merge pull request #8408 from NixOS/backport-8399-to-2.13-maintenance
[Backport 2.13-maintenance] Properly report build errors on chrooted stores
2023-05-28 17:11:48 +02:00
Théophane Hufschmitt
00811f98fb Properly report build errors on chrooted stores
When encountering a build error, Nix moves the output paths out of the
chroot into their final location (for “easier debugging of build
failures”). However this was broken for chroot stores as it was moving
it to the _logical_ location, not the _physical_ one.

Fix it by moving to the physical (_real_) location.

Fix https://github.com/NixOS/nix/issues/8395

(cherry picked from commit d16a1994fb)
2023-05-28 15:11:26 +00:00
Théophane Hufschmitt
8aa178f75b Merge pull request #8181 from NixOS/backport-8179-to-2.13-maintenance
[Backport 2.13-maintenance] disable gc on coroutine
2023-04-08 13:44:15 +02:00
Yorick van Pelt
ad96bf9791 Add talkative msg for coro gc debug
(cherry picked from commit 62ddd8633c)
2023-04-07 16:21:30 +00:00
Yorick van Pelt
de7803e15f Always disable GC in a coroutine unless the patch is applied
(cherry picked from commit 58d24a4cb6)
2023-04-07 16:21:30 +00:00
Yorick van Pelt
cc90264a7d DisableGC: replace by CoroutineContext, std::shared_ptr<void>
(cherry picked from commit 00bc34430b)
2023-04-07 16:21:30 +00:00
Yorick van Pelt
325ea715b3 Disable GC inside coroutines on mac OS
(cherry picked from commit 2c53ef1bfe)
2023-04-07 16:21:30 +00:00
John Ericson
11f12253ce Merge pull request #8054 from NixOS/backport-8053-to-2.13-maintenance
[Backport 2.13-maintenance] LocalDerivationGoal: set NIX_ATTRS_*_FILE correctly for sandboxed builds
2023-03-16 18:24:51 -04:00
Linus Heckemann
e697c74269 LocalDerivationGoal: set NIX_ATTRS_*_FILE correctly for sandboxed builds
(cherry picked from commit af4cbdafe7)
2023-03-16 15:04:52 +00:00
Eelco Dolstra
9bf0baa7f4 Bump version 2023-02-27 18:13:04 +01:00
Théophane Hufschmitt
4acc684ef7 Merge pull request #7878 from NixOS/backport-7856-to-2.13-maintenance
[Backport 2.13-maintenance] Wait with making /etc unwritable until after build env setup
2023-02-22 06:52:14 +01:00
Yorick van Pelt
11522a573d Wait with making /etc unwritable until after build env setup
This fixes /etc/nsswitch.conf

(cherry picked from commit bbba49b3e4)
2023-02-21 19:22:35 +00:00
Eelco Dolstra
1083ecbb2b Merge pull request #7833 from hercules-ci/backport-7616-to-2.13-maintenance
Backport 7616 to 2.13 maintenance
2023-02-14 16:53:27 +01:00
Eelco Dolstra
911cd5b45a Merge pull request #7837 from NixOS/backport-7830-to-2.13-maintenance
[Backport 2.13-maintenance] Don't allow writing to /etc
2023-02-14 16:52:50 +01:00
Yorick van Pelt
bca7075edc Make /etc writability conditional on uid-range feature
(cherry picked from commit 49fd72a903)
2023-02-14 15:50:29 +00:00
Yorick van Pelt
459832e5c2 container test: make /etc writable
(cherry picked from commit ad1f61c39b)
2023-02-14 15:50:29 +00:00
Yorick van Pelt
58210e5306 Don't allow writing to /etc
(cherry picked from commit db41f74af3)
2023-02-14 15:50:29 +00:00
Robert Hensing
4ca48e3c7f NarInfoDiskCache: Also test id consistency with updated fields
And clarify test

(cherry picked from commit 19b495a48a)
2023-02-14 14:54:51 +01:00
Robert Hensing
2e31c54ce5 NarInfoDiskCache: Keep BinaryCache.id stable and improve test
Fixes #3898

The entire `BinaryCaches` row used to get replaced after it became
stale according to the `timestamp` column. In a concurrent scenario,
this leads to foreign key conflicts as different instances of the
in-process `state.caches` cache now differ, with the consequence that
the older process still tries to use the `id` number of the old record.

Furthermore, this phenomenon appears to have caused the cache for
actual narinfos to be erased about every week, while the default
ttl for narinfos was supposed to be 30 days.

(cherry picked from commit fb94d5cabd)
2023-02-14 14:54:51 +01:00
Robert Hensing
e404ae5bb6 NarInfoDiskCache: Prepare reproducer for #3898
(cherry picked from commit 2ceece3ef3)
2023-02-14 14:54:51 +01:00
Robert Hensing
80498bd923 NarInfoDiskCacheImpl: Make dbPath a parameter
This allows testing with a clean database.

(cherry picked from commit 79f62d2dda)
2023-02-13 12:02:46 +00:00
Robert Hensing
7a67baf951 NarInfoDiskCache: Rename cacheExists -> upToDateCacheExists
This is slightly more accurate considering that an outdated record
may exist in the persistent cache. Possibly-outdated records are
quite relevant as they may be foreign keys to more recent information
that we want to keep, but we will not return them here.

(cherry picked from commit 29f0b196f4)
2023-02-13 12:02:46 +00:00
Robert Hensing
9f788403cf sqlite.cc: Add SQL tracing
Set environment variable NIX_DEBUG_SQLITE_TRACES=1 to log all sql statements.

(cherry picked from commit 8a0ef5d58e)
2023-02-13 12:02:46 +00:00
Robert Hensing
b6ce4e100a local.mk: Don't log docroot comments
These were accidentally logged and do not need to appear in make's
log output.

(cherry picked from commit 1a86d3e98e)
2023-02-13 12:02:46 +00:00
Robert Hensing
9157f94e77 Merge pull request #7746 from NixOS/backport-7705-to-2.13-maintenance
[Backport 2.13-maintenance] perl: run `initLibStore()` on `openStore()`
2023-02-04 13:39:00 +01:00
Maximilian Bosch
77d8066e83 perl: run initLibStore() on openStore()
Since #7478 it's mandatory that `initLibStore()` is called for store
operations. However that's not the case when running `openStore()` in
Perl using the perl-bindings. That breaks e.g. `hydra-eval-jobset` when
built against Nix 2.13 which uses small portions of the store API.

(cherry picked from commit 51013da921)
2023-02-03 22:33:58 +00:00
Eelco Dolstra
286d212d10 Bump version 2023-01-26 12:42:03 +01:00
Eelco Dolstra
435a16b555 Merge pull request #7694 from NixOS/backport-7685-to-2.13-maintenance
[Backport 2.13-maintenance] Fix the 2.13 changelog
2023-01-25 21:28:02 +01:00
John Ericson
13c00e2969 Update doc/manual/src/release-notes/rl-2.13.md
Co-authored-by: Eelco Dolstra <edolstra@gmail.com>
(cherry picked from commit f465e378c4)
2023-01-25 20:08:59 +00:00
John Ericson
e60a492173 Fix the 2.13 changelog
It is just the new CLI that gets the `^` syntax. The old CLI already has
a (slightly different) `!` syntax.

Fixes #7682

(cherry picked from commit 816031173c)
2023-01-25 20:08:59 +00:00
Robert Hensing
da3faaf8a6 Merge pull request #7687 from hercules-ci/2.13-maintenance-revert-addition-of-property-tests
[2.13-maintenance] revert addition of property tests
2023-01-25 18:54:17 +01:00
Robert Hensing
cdad101bc9 Restore some non-rapidcheck tests 2023-01-25 18:11:36 +01:00
Robert Hensing
3424e1e055 Revert "Try to fix #7669"
We've decided against backporting rapidcheck

This reverts commit e60ee9e90a.
2023-01-25 18:09:10 +01:00
Robert Hensing
9566203d0d Partially revert "Test store paths, with property tests"
This reverts the addition of property tests from commit d111d2ed49.
2023-01-25 11:33:29 +01:00
Robert Hensing
65e690a087 Revert "Add rapidcheck dependency for testing"
This reverts commit c92e51ecec.
2023-01-25 11:27:52 +01:00
Robert Hensing
2e66fe9b1e Merge pull request #7680 from NixOS/backport-7679-to-2.13-maintenance
[Backport 2.13-maintenance] Revert "fixup: remove boehmgc patch"
2023-01-24 17:17:24 +01:00
Robert Hensing
7ddbc12bc8 Update boehmgc-coroutine-sp-fallback.diff
(cherry picked from commit 46054f932b)
2023-01-24 15:27:09 +00:00
Robert Hensing
8f170f98f4 Actually complete the revert
(cherry picked from commit 8270dccf60)
2023-01-24 15:27:09 +00:00
Robert Hensing
46daba3852 Revert "fixup: remove boehmgc patch"
It is still necessary.
Please do your research, or f ask the author, which happens to be me.

An evaluator like this is not an environment where "it compiles, so
it works" will ever hold.

This reverts commit 1c40182b12.

(cherry picked from commit 0664ba0a67)
2023-01-24 15:27:09 +00:00
Robert Hensing
75d33d3e89 Merge pull request #7678 from NixOS/backport-7670-to-2.13-maintenance
[Backport 2.13-maintenance] Try to fix #7669
2023-01-24 13:51:31 +01:00
John Ericson
e60ee9e90a Try to fix #7669
The issue *seems* to be the cross jobs, which are missing the `CXXFLAGS`
needed to get rapidcheck.

PR #6538 would be really nice to resurrect which will prevent the
`configureFlags` from going out of sync between the regular build and
the cross build again.

(cherry picked from commit a91709a604)
2023-01-24 11:44:19 +00:00
Robert Hensing
2c552c66af Merge pull request #7666 from NixOS/backport-7657-to-2.13-maintenance
[Backport 2.13-maintenance] Fix #7655
2023-01-23 16:22:59 +01:00
Robert Hensing
aff2c1c642 Merge pull request #7664 from NixOS/backport-7639-to-2.13-maintenance
[Backport 2.13-maintenance] Test store paths, with property tests, fix bug
2023-01-23 15:58:43 +01:00
John Ericson
af27f2fe00 Fix #7655
We had some local variables left over from the older (more
complicated) implementation of this function. They should all be unused,
but one wasn't by mistake.

Delete them all, and replace the one that was still in use as intended.

(cherry picked from commit 0afdf4084c)
2023-01-23 14:43:23 +00:00
John Ericson
3f58d5a76b Expand tests to reproduce #7655
The original `builtins.getContext` test from
1d757292d0 would have caught this. The
problem is that b30be6b450 adding
`builtins.appendContext` modified that test to make it test too much at
once, rather than adding a separate test.

We now have isolated tests for both functions, and also a property test
showing everything put together (in the form of an eta rule for strings
with context). This is better coverage and properly reproduces the bug.

(cherry picked from commit 88d8f6ac48)
2023-01-23 14:43:23 +00:00
John Ericson
d111d2ed49 Test store paths, with property tests
The property test in fact found a bug: we were excluding numbers!

(cherry picked from commit 018e2571aa)
2023-01-23 14:21:44 +00:00
John Ericson
01b30df520 Better-scope Store forward declarations
(cherry picked from commit 685395332d)
2023-01-23 14:21:44 +00:00
John Ericson
c92e51ecec Add rapidcheck dependency for testing
Property tests are great!

Co-authored-by: Cole Helbling <cole.e.helbling@outlook.com>
(cherry picked from commit 7fe308c2f8)
2023-01-23 14:21:44 +00:00
Eelco Dolstra
291e36b1c0 Merge pull request #7637 from NixOS/backport-7636-to-2.13-maintenance
[Backport 2.13-maintenance] Relase notes: add empty flake registry
2023-01-18 19:37:11 +01:00
Eelco Dolstra
c834fa66b3 Bump version 2023-01-18 19:33:30 +01:00
Lorenzo Manacorda
8cdb15408f Relase notes: add empty flake registry
Introduced in #5420

(cherry picked from commit 913782af4d)
2023-01-18 17:04:47 +00:00
Eelco Dolstra
7304806241 Merge pull request #7635 from NixOS/backport-7631-to-2.13-maintenance
[Backport 2.13-maintenance] OutputSpec: Allow all valid output names
2023-01-18 17:19:37 +01:00
Eelco Dolstra
be0f37ad7b Add test for OutputsSpec::Names
From @Ericson2314.

(cherry picked from commit 75c89c3e5e)
2023-01-18 16:09:36 +00:00
Eelco Dolstra
3de7107312 Fix indentation
(cherry picked from commit 8a3b30822b)
2023-01-18 16:09:36 +00:00
Eelco Dolstra
a7e6bb7fd4 Add some tests for illegal output names
(cherry picked from commit 1ebfa6ba2d)
2023-01-18 16:09:36 +00:00
Eelco Dolstra
9ccde7ec9f OutputSpec: Allow all valid output names
Fixes #7624.

(cherry picked from commit 95cfd50d25)
2023-01-18 16:09:36 +00:00
Eelco Dolstra
7cd4a8f73c Merge pull request #7632 from NixOS/backport-7627-to-2.13-maintenance
[Backport 2.13-maintenance] Restore support for channel: URLs in fetchTarball
2023-01-18 14:28:06 +01:00
Eelco Dolstra
fda0d52e3c Restore support for channel: URLs in fetchTarball
Fixes #7625.

(cherry picked from commit 01f268322a)
2023-01-18 13:26:57 +00:00
Eelco Dolstra
f60d45d661 Merge pull request #7622 from NixOS/backport-7621-to-2.13-maintenance
[Backport 2.13-maintenance] Revert #6204 to fix regression, add nixpkgs/lib/tests as regression test
2023-01-18 11:49:56 +01:00
Robert Hensing
cfee550bec flake.nix: Add nixpkgs/lib/tests as regression test
(cherry picked from commit 620e4fb89b)
2023-01-18 01:27:12 +00:00
Robert Hensing
b050a226b2 Revert "Merge pull request #6204 from layus/coerce-string"
This reverts commit a75b7ba30f, reversing
changes made to 9af16c5f74.

(cherry picked from commit 9b33ef3879)
2023-01-18 01:27:12 +00:00
Eelco Dolstra
973144c986 Bump version 2023-01-17 22:06:55 +01:00
Eelco Dolstra
54ef7e7664 Typo
(cherry picked from commit 3ff9fc0d7d)
2023-01-17 17:44:38 +01:00
John Ericson
b5e1da41f6 Try again to fix aarch64-linux build failure
f419ab48e6 was on the right track, but
there are a few more missing `raw()` calls to fix.

(cherry picked from commit 3965b0f75f)
2023-01-17 16:29:27 +01:00
Eelco Dolstra
193462930c Mark official release 2023-01-17 13:49:52 +01:00
82 changed files with 1692 additions and 1102 deletions

View File

@@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: cachix/install-nix-action@v18
- uses: cachix/install-nix-action@v20
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
- uses: cachix/cachix-action@v12
if: needs.check_secrets.outputs.cachix == 'true'
@@ -58,7 +58,7 @@ jobs:
with:
fetch-depth: 0
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
- uses: cachix/install-nix-action@v18
- uses: cachix/install-nix-action@v20
- uses: cachix/cachix-action@v12
with:
name: '${{ env.CACHIX_NAME }}'
@@ -77,7 +77,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
- uses: cachix/install-nix-action@v18
- uses: cachix/install-nix-action@v20
with:
install_url: '${{needs.installer.outputs.installerURL}}'
install_options: "--tarball-url-prefix https://${{ env.CACHIX_NAME }}.cachix.org/serve"
@@ -102,7 +102,7 @@ jobs:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: cachix/install-nix-action@v18
- uses: cachix/install-nix-action@v20
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
- run: echo NIX_VERSION="$(nix --experimental-features 'nix-command flakes' eval .\#default.version | tr -d \")" >> $GITHUB_ENV
- uses: cachix/cachix-action@v12

View File

@@ -1 +1 @@
2.13.0
2.13.5

View File

@@ -0,0 +1,93 @@
diff --git a/darwin_stop_world.c b/darwin_stop_world.c
index 0468aaec..b348d869 100644
--- a/darwin_stop_world.c
+++ b/darwin_stop_world.c
@@ -356,6 +356,7 @@ GC_INNER void GC_push_all_stacks(void)
int nthreads = 0;
word total_size = 0;
mach_msg_type_number_t listcount = (mach_msg_type_number_t)THREAD_TABLE_SZ;
+ size_t stack_limit;
if (!EXPECT(GC_thr_initialized, TRUE))
GC_thr_init();
@@ -411,6 +412,19 @@ GC_INNER void GC_push_all_stacks(void)
GC_push_all_stack_sections(lo, hi, p->traced_stack_sect);
}
if (altstack_lo) {
+ // When a thread goes into a coroutine, we lose its original sp until
+ // control flow returns to the thread.
+ // While in the coroutine, the sp points outside the thread stack,
+ // so we can detect this and push the entire thread stack instead,
+ // as an approximation.
+ // We assume that the coroutine has similarly added its entire stack.
+ // This could be made accurate by cooperating with the application
+ // via new functions and/or callbacks.
+ stack_limit = pthread_get_stacksize_np(p->id);
+ if (altstack_lo >= altstack_hi || altstack_lo < altstack_hi - stack_limit) { // sp outside stack
+ altstack_lo = altstack_hi - stack_limit;
+ }
+
total_size += altstack_hi - altstack_lo;
GC_push_all_stack(altstack_lo, altstack_hi);
}
diff --git a/include/gc.h b/include/gc.h
index edab6c22..f2c61282 100644
--- a/include/gc.h
+++ b/include/gc.h
@@ -2172,6 +2172,11 @@ GC_API void GC_CALL GC_win32_free_heap(void);
(*GC_amiga_allocwrapper_do)(a,GC_malloc_atomic_ignore_off_page)
#endif /* _AMIGA && !GC_AMIGA_MAKINGLIB */
+#if !__APPLE__
+/* Patch doesn't work on apple */
+#define NIX_BOEHM_PATCH_VERSION 1
+#endif
+
#ifdef __cplusplus
} /* extern "C" */
#endif
diff --git a/pthread_stop_world.c b/pthread_stop_world.c
index b5d71e62..aed7b0bf 100644
--- a/pthread_stop_world.c
+++ b/pthread_stop_world.c
@@ -768,6 +768,8 @@ STATIC void GC_restart_handler(int sig)
/* world is stopped. Should not fail if it isn't. */
GC_INNER void GC_push_all_stacks(void)
{
+ size_t stack_limit;
+ pthread_attr_t pattr;
GC_bool found_me = FALSE;
size_t nthreads = 0;
int i;
@@ -851,6 +853,31 @@ GC_INNER void GC_push_all_stacks(void)
hi = p->altstack + p->altstack_size;
/* FIXME: Need to scan the normal stack too, but how ? */
/* FIXME: Assume stack grows down */
+ } else {
+ if (pthread_getattr_np(p->id, &pattr)) {
+ ABORT("GC_push_all_stacks: pthread_getattr_np failed!");
+ }
+ if (pthread_attr_getstacksize(&pattr, &stack_limit)) {
+ ABORT("GC_push_all_stacks: pthread_attr_getstacksize failed!");
+ }
+ if (pthread_attr_destroy(&pattr)) {
+ ABORT("GC_push_all_stacks: pthread_attr_destroy failed!");
+ }
+ // When a thread goes into a coroutine, we lose its original sp until
+ // control flow returns to the thread.
+ // While in the coroutine, the sp points outside the thread stack,
+ // so we can detect this and push the entire thread stack instead,
+ // as an approximation.
+ // We assume that the coroutine has similarly added its entire stack.
+ // This could be made accurate by cooperating with the application
+ // via new functions and/or callbacks.
+ #ifndef STACK_GROWS_UP
+ if (lo >= hi || lo < hi - stack_limit) { // sp outside stack
+ lo = hi - stack_limit;
+ }
+ #else
+ #error "STACK_GROWS_UP not supported in boost_coroutine2 (as of june 2021), so we don't support it in Nix."
+ #endif
}
GC_push_all_stack_sections(lo, hi, traced_stack_sect);
# ifdef STACK_GROWS_UP

View File

@@ -51,13 +51,13 @@ $(d)/src/SUMMARY.md: $(d)/src/SUMMARY.md.in $(d)/src/command-ref/new-cli
$(d)/src/command-ref/new-cli: $(d)/nix.json $(d)/generate-manpage.nix $(bindir)/nix
@rm -rf $@
$(trace-gen) $(nix-eval) --write-to $@.tmp --expr 'import doc/manual/generate-manpage.nix { toplevel = builtins.readFile $<; }'
# @docroot@: https://nixos.org/manual/nix/unstable/contributing/hacking.html#docroot-variable
@# @docroot@: https://nixos.org/manual/nix/unstable/contributing/hacking.html#docroot-variable
$(trace-gen) sed -i $@.tmp/*.md -e 's^@docroot@^../..^g'
@mv $@.tmp $@
$(d)/src/command-ref/conf-file.md: $(d)/conf-file.json $(d)/generate-options.nix $(d)/src/command-ref/conf-file-prefix.md $(bindir)/nix
@cat doc/manual/src/command-ref/conf-file-prefix.md > $@.tmp
# @docroot@: https://nixos.org/manual/nix/unstable/contributing/hacking.html#docroot-variable
@# @docroot@: https://nixos.org/manual/nix/unstable/contributing/hacking.html#docroot-variable
$(trace-gen) $(nix-eval) --expr 'import doc/manual/generate-options.nix (builtins.fromJSON (builtins.readFile $<))' \
| sed -e 's^@docroot@^..^g'>> $@.tmp
@mv $@.tmp $@
@@ -72,7 +72,7 @@ $(d)/conf-file.json: $(bindir)/nix
$(d)/src/language/builtins.md: $(d)/builtins.json $(d)/generate-builtins.nix $(d)/src/language/builtins-prefix.md $(bindir)/nix
@cat doc/manual/src/language/builtins-prefix.md > $@.tmp
# @docroot@: https://nixos.org/manual/nix/unstable/contributing/hacking.html#docroot-variable
@# @docroot@: https://nixos.org/manual/nix/unstable/contributing/hacking.html#docroot-variable
$(trace-gen) $(nix-eval) --expr 'import doc/manual/generate-builtins.nix (builtins.fromJSON (builtins.readFile $<))' \
| sed -e 's^@docroot@^..^g' >> $@.tmp
@cat doc/manual/src/language/builtins-suffix.md >> $@.tmp

View File

@@ -61,12 +61,13 @@
- [Files](command-ref/files.md)
- [nix.conf](command-ref/conf-file.md)
- [Architecture](architecture/architecture.md)
- [Protocols](protocols/protocols.md)
- [Serving Tarball Flakes](protocols/tarball-fetcher.md)
- [Glossary](glossary.md)
- [Contributing](contributing/contributing.md)
- [Hacking](contributing/hacking.md)
- [CLI guideline](contributing/cli-guideline.md)
- [Release Notes](release-notes/release-notes.md)
- [Release X.Y (202?-??-??)](release-notes/rl-next.md)
- [Release 2.13 (2023-01-17)](release-notes/rl-2.13.md)
- [Release 2.12 (2022-12-06)](release-notes/rl-2.12.md)
- [Release 2.11 (2022-08-25)](release-notes/rl-2.11.md)

View File

@@ -0,0 +1,4 @@
# Protocols
This chapter documents various developer-facing interfaces provided by
Nix.

View File

@@ -0,0 +1,40 @@
# Serving Tarball Flakes
Tarball flakes are served as regular tarballs via HTTP or the file
system (for `file://` URLs).
An HTTP server can return an "immutable" flakeref appropriate for lock
files. This allows users to specify a tarball flake input in
`flake.nix` that requests the latest version of a flake
(e.g. `https://example.org/hello/latest.tar.gz`), while `flake.lock`
will record a URL whose contents will not change
(e.g. `https://example.org/hello/<revision>.tar.gz`). To do so, the
server must return a `Link` header with the `rel` attribute set to
`immutable`, as follows:
```
Link: <flakeref>; rel="immutable"
```
(Note the required `<` and `>` characters around *flakeref*.)
*flakeref* must be a tarball flakeref. It can contain flake attributes
such as `narHash`, `rev` and `revCount`. If `narHash` is included, its
value must be the NAR hash of the unpacked tarball (as computed via
`nix hash path`). Nix checks the contents of the returned tarball
against the `narHash` attribute. The `rev` and `revCount` attributes
are useful when the tarball flake is a mirror of a fetcher type that
has those attributes, such as Git or GitHub. They are not checked by
Nix.
```
Link: <https://example.org/hello/442793d9ec0584f6a6e82fa253850c8085bb150a.tar.gz
?rev=442793d9ec0584f6a6e82fa253850c8085bb150a
&revCount=835
&narHash=sha256-GUm8Uh/U74zFCwkvt9Mri4DSM%2BmHj3tYhXUkYpiv31M%3D>; rel="immutable"
```
(The linebreaks in this example are for clarity and must not be included in the actual response.)
For tarball flakes, the value of the `lastModified` flake attribute is
defined as the timestamp of the newest file inside the tarball.

View File

@@ -18,18 +18,14 @@
* Instead of "antiquotation", the more common term [string interpolation](../language/string-interpolation.md) is now used consistently.
Historical release notes were not changed.
* Error traces have been reworked to provide detailed explanations and more
accurate error locations. A short excerpt of the trace is now shown by
default when an error occurs.
* Allow explicitly selecting outputs in a store derivation installable, just like we can do with other sorts of installables.
For example,
```shell-session
# nix-build /nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^dev`
# nix build /nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^dev
```
now works just as
```shell-session
# nix-build glibc^dev`
# nix build nixpkgs#glibc^dev
```
does already.
@@ -38,3 +34,16 @@
for the development shell in the same way as the actual build of the
derivation. This makes shells for `i686-linux` derivations work
correctly on `x86_64-linux`.
* You can now disable the global flake registry by setting the `flake-registry`
configuration option to an empty string. The same can be achieved at runtime with
`--flake-registry ""`.
* Since 2.13.5, a new function `builtins.readFileType` is available. It is similar to
`builtins.readDir` but acts on a single file or directory.
* Since 2.13.5, the `builtins.readDir` function has been optimized when encountering not-yet-known
file types from POSIX's `readdir`. In such cases the type of each file was
discovered by making multiple syscalls. This change makes these operations
lazy such that these lookups will only be performed if the attribute is used.
This optimization affects a minority of filesystems and operating systems.

View File

@@ -1,2 +0,0 @@
# Release X.Y (202?-??-??)

View File

@@ -9,7 +9,7 @@
let
officialRelease = false;
officialRelease = true;
version = nixpkgs.lib.fileContents ./.version + versionSuffix;
versionSuffix =
@@ -128,9 +128,14 @@
});
propagatedDeps =
[ (boehmgc.override {
[ ((boehmgc.override {
enableLargeConfig = true;
}).overrideAttrs(o: {
patches = (o.patches or []) ++ [
./boehmgc-coroutine-sp-fallback.diff
];
})
)
nlohmann_json
];
};
@@ -532,6 +537,12 @@
mkdir $out
'';
tests.nixpkgsLibTests =
nixpkgs.lib.genAttrs systems (system:
import (nixpkgs + "/lib/tests/release.nix")
{ pkgs = nixpkgsFor.${system}; }
);
metrics.nixpkgs = import "${nixpkgs-regression}/pkgs/top-level/metrics.nix" {
pkgs = nixpkgsFor.x86_64-linux;
nixpkgs = nixpkgs-regression;
@@ -562,6 +573,7 @@
binaryTarball = self.hydraJobs.binaryTarball.${system};
perlBindings = self.hydraJobs.perlBindings.${system};
installTests = self.hydraJobs.installTests.${system};
nixpkgsLibTests = self.hydraJobs.tests.nixpkgsLibTests.${system};
} // (nixpkgs.lib.optionalAttrs (builtins.elem system linux64BitSystems)) {
dockerImage = self.hydraJobs.dockerImage.${system};
});

View File

@@ -26,6 +26,7 @@ static ref<Store> store()
static std::shared_ptr<Store> _store;
if (!_store) {
try {
initLibStore();
loadConfFile();
settings.lockCPU = false;
_store = openStore();

View File

@@ -161,7 +161,7 @@ Path lookupFileArg(EvalState & state, std::string_view s)
{
if (EvalSettings::isPseudoUrl(s)) {
auto storePath = fetchers::downloadTarball(
state.store, EvalSettings::resolvePseudoUrl(s), "source", false).first.storePath;
state.store, EvalSettings::resolvePseudoUrl(s), "source", false).tree.storePath;
return state.store->toRealPath(storePath);
}

View File

@@ -554,7 +554,7 @@ ref<eval_cache::EvalCache> openEvalCache(
auto vFlake = state.allocValue();
flake::callFlake(state, *lockedFlake, *vFlake);
state.forceAttrs(*vFlake, noPos, "while parsing cached flake data");
state.forceAttrs(*vFlake, noPos);
auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs"));
assert(aOutputs);
@@ -618,7 +618,7 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
else if (v.type() == nString) {
PathSet context;
auto s = state->forceString(v, context, noPos, fmt("while evaluating the flake output attribute '%s'", attrPath));
auto s = state->forceString(v, context, noPos);
auto storePath = state->store->maybeParseStorePath(s);
if (storePath && context.count(std::string(s))) {
return {{

View File

@@ -397,7 +397,7 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
Expr * e = parseString(expr);
Value v;
e->eval(*state, *env, v);
state->forceAttrs(v, noPos, "nevermind, it is ignored anyway");
state->forceAttrs(v, noPos);
for (auto & i : *v.attrs) {
std::string_view name = state->symbols[i.name];
@@ -590,7 +590,7 @@ bool NixRepl::processLine(std::string line)
const auto [path, line] = [&] () -> std::pair<Path, uint32_t> {
if (v.type() == nPath || v.type() == nString) {
PathSet context;
auto path = state->coerceToPath(noPos, v, context, "while evaluating the filename to edit");
auto path = state->coerceToPath(noPos, v, context);
return {path, 0};
} else if (v.isLambda()) {
auto pos = state->positions[v.lambda.fun->pos];
@@ -839,7 +839,7 @@ void NixRepl::loadFiles()
void NixRepl::addAttrsToScope(Value & attrs)
{
state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); }, "while evaluating an attribute set to be merged in the global scope");
state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); });
if (displ + attrs.attrs->size() >= envSize)
throw Error("environment full; cannot add more variables");
@@ -944,7 +944,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
Bindings::iterator i = v.attrs->find(state->sDrvPath);
PathSet context;
if (i != v.attrs->end())
str << state->store->printStorePath(state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the drvPath of a derivation"));
str << state->store->printStorePath(state->coerceToStorePath(i->pos, *i->value, context));
else
str << "???";
str << "»";

View File

@@ -118,7 +118,7 @@ std::pair<std::string, uint32_t> findPackageFilename(EvalState & state, Value &
// FIXME: is it possible to extract the Pos object instead of doing this
// toString + parsing?
auto pos = state.forceString(*v2, noPos, "while evaluating the 'meta.position' attribute of a derivation");
auto pos = state.forceString(*v2);
auto colon = pos.rfind(':');
if (colon == std::string::npos)

View File

@@ -385,7 +385,7 @@ Value & AttrCursor::getValue()
if (!_value) {
if (parent) {
auto & vParent = parent->first->getValue();
root->state.forceAttrs(vParent, noPos, "while searching for an attribute");
root->state.forceAttrs(vParent, noPos);
auto attr = vParent.attrs->get(parent->second);
if (!attr)
throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr());
@@ -571,14 +571,14 @@ std::string AttrCursor::getString()
debug("using cached string attribute '%s'", getAttrPathStr());
return s->first;
} else
root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow<TypeError>();
root->state.debugThrowLastTrace(TypeError("'%s' is not a string", getAttrPathStr()));
}
}
auto & v = forceValue();
if (v.type() != nString && v.type() != nPath)
root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
root->state.debugThrowLastTrace(TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type())));
return v.type() == nString ? v.string.s : v.path;
}
@@ -613,7 +613,7 @@ string_t AttrCursor::getStringWithContext()
return *s;
}
} else
root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow<TypeError>();
root->state.debugThrowLastTrace(TypeError("'%s' is not a string", getAttrPathStr()));
}
}
@@ -624,7 +624,7 @@ string_t AttrCursor::getStringWithContext()
else if (v.type() == nPath)
return {v.path, {}};
else
root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
root->state.debugThrowLastTrace(TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type())));
}
bool AttrCursor::getBool()
@@ -637,14 +637,14 @@ bool AttrCursor::getBool()
debug("using cached Boolean attribute '%s'", getAttrPathStr());
return *b;
} else
root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow<TypeError>();
root->state.debugThrowLastTrace(TypeError("'%s' is not a Boolean", getAttrPathStr()));
}
}
auto & v = forceValue();
if (v.type() != nBool)
root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow<TypeError>();
root->state.debugThrowLastTrace(TypeError("'%s' is not a Boolean", getAttrPathStr()));
return v.boolean;
}
@@ -696,7 +696,7 @@ std::vector<std::string> AttrCursor::getListOfStrings()
std::vector<std::string> res;
for (auto & elem : v.listItems())
res.push_back(std::string(root->state.forceStringNoCtx(*elem, noPos, "while evaluating an attribute for caching")));
res.push_back(std::string(root->state.forceStringNoCtx(*elem)));
if (root->db)
cachedValue = {root->db->setListOfStrings(getKey(), res), res};
@@ -714,14 +714,14 @@ std::vector<Symbol> AttrCursor::getAttrs()
debug("using cached attrset attribute '%s'", getAttrPathStr());
return *attrs;
} else
root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow<TypeError>();
root->state.debugThrowLastTrace(TypeError("'%s' is not an attribute set", getAttrPathStr()));
}
}
auto & v = forceValue();
if (v.type() != nAttrs)
root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow<TypeError>();
root->state.debugThrowLastTrace(TypeError("'%s' is not an attribute set", getAttrPathStr()));
std::vector<Symbol> attrs;
for (auto & attr : *getValue().attrs)

View File

@@ -103,36 +103,33 @@ void EvalState::forceValue(Value & v, Callable getPos)
else if (v.isApp())
callFunction(*v.app.left, *v.app.right, v, noPos);
else if (v.isBlackhole())
error("infinite recursion encountered").atPos(getPos()).template debugThrow<EvalError>();
throwEvalError(getPos(), "infinite recursion encountered");
}
[[gnu::always_inline]]
inline void EvalState::forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx)
inline void EvalState::forceAttrs(Value & v, const PosIdx pos)
{
forceAttrs(v, [&]() { return pos; }, errorCtx);
forceAttrs(v, [&]() { return pos; });
}
template <typename Callable>
[[gnu::always_inline]]
inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view errorCtx)
inline void EvalState::forceAttrs(Value & v, Callable getPos)
{
forceValue(v, noPos);
if (v.type() != nAttrs) {
PosIdx pos = getPos();
error("value is %1% while a set was expected", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
}
forceValue(v, getPos);
if (v.type() != nAttrs)
throwTypeError(getPos(), "value is %1% while a set was expected", v);
}
[[gnu::always_inline]]
inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view errorCtx)
inline void EvalState::forceList(Value & v, const PosIdx pos)
{
forceValue(v, noPos);
if (!v.isList()) {
error("value is %1% while a list was expected", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
}
forceValue(v, pos);
if (!v.isList())
throwTypeError(pos, "value is %1% while a list was expected", v);
}

View File

@@ -318,11 +318,27 @@ static Symbol getName(const AttrName & name, EvalState & state, Env & env)
} else {
Value nameValue;
name.expr->eval(state, env, nameValue);
state.forceStringNoCtx(nameValue, noPos, "while evaluating an attribute name");
state.forceStringNoCtx(nameValue);
return state.symbols.create(nameValue.string.s);
}
}
#if HAVE_BOEHMGC
/* Disable GC while this object lives. Used by CoroutineContext.
*
* Boehm keeps a count of GC_disable() and GC_enable() calls,
* and only enables GC when the count matches.
*/
class BoehmDisableGC {
public:
BoehmDisableGC() {
GC_disable();
};
~BoehmDisableGC() {
GC_enable();
};
};
#endif
static bool gcInitialised = false;
@@ -347,6 +363,15 @@ void initGC()
StackAllocator::defaultAllocator = &boehmGCStackAllocator;
#if NIX_BOEHM_PATCH_VERSION != 1
printTalkative("Unpatched BoehmGC, disabling GC inside coroutines");
/* Used to disable GC when entering coroutines on macOS */
create_coro_gc_hook = []() -> std::shared_ptr<void> {
return std::make_shared<BoehmDisableGC>();
};
#endif
/* Set the initial heap size to something fairly big (25% of
physical RAM, up to a maximum of 384 MiB) so that in most cases
we don't need to garbage collect at all. (Collection has a
@@ -414,44 +439,6 @@ static Strings parseNixPath(const std::string & s)
return res;
}
ErrorBuilder & ErrorBuilder::atPos(PosIdx pos)
{
info.errPos = state.positions[pos];
return *this;
}
ErrorBuilder & ErrorBuilder::withTrace(PosIdx pos, const std::string_view text)
{
info.traces.push_front(Trace{ .pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = false });
return *this;
}
ErrorBuilder & ErrorBuilder::withFrameTrace(PosIdx pos, const std::string_view text)
{
info.traces.push_front(Trace{ .pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = true });
return *this;
}
ErrorBuilder & ErrorBuilder::withSuggestions(Suggestions & s)
{
info.suggestions = s;
return *this;
}
ErrorBuilder & ErrorBuilder::withFrame(const Env & env, const Expr & expr)
{
// NOTE: This is abusing side-effects.
// TODO: check compatibility with nested debugger calls.
state.debugTraces.push_front(DebugTrace {
.pos = nullptr,
.expr = expr,
.env = env,
.hint = hintformat("Fake frame for debugging purposes"),
.isError = true
});
return *this;
}
EvalState::EvalState(
const Strings & _searchPath,
@@ -684,7 +671,25 @@ void EvalState::addConstant(const std::string & name, Value * v)
Value * EvalState::addPrimOp(const std::string & name,
size_t arity, PrimOpFun primOp)
{
return addPrimOp(PrimOp { .fun = primOp, .arity = arity, .name = name });
auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name;
auto sym = symbols.create(name2);
/* Hack to make constants lazy: turn them into a application of
the primop to a dummy value. */
if (arity == 0) {
auto vPrimOp = allocValue();
vPrimOp->mkPrimOp(new PrimOp { .fun = primOp, .arity = 1, .name = name2 });
Value v;
v.mkApp(vPrimOp, vPrimOp);
return addConstant(name, v);
}
Value * v = allocValue();
v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = name2 });
staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
baseEnv.values[0]->attrs->push_back(Attr(sym, v));
return v;
}
@@ -862,14 +867,176 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr &
}
}
/* Every "format" object (even temporary) takes up a few hundred bytes
of stack space, which is a real killer in the recursive
evaluator. So here are some helper functions for throwing
exceptions. */
void EvalState::throwEvalError(const PosIdx pos, const char * s, Env & env, Expr & expr)
{
debugThrow(EvalError({
.msg = hintfmt(s),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwEvalError(const PosIdx pos, const char * s)
{
debugThrowLastTrace(EvalError({
.msg = hintfmt(s),
.errPos = positions[pos]
}));
}
void EvalState::throwEvalError(const char * s, const std::string & s2)
{
debugThrowLastTrace(EvalError(s, s2));
}
void EvalState::throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s,
const std::string & s2, Env & env, Expr & expr)
{
debugThrow(EvalError(ErrorInfo{
.msg = hintfmt(s, s2),
.errPos = positions[pos],
.suggestions = suggestions,
}), env, expr);
}
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2)
{
debugThrowLastTrace(EvalError({
.msg = hintfmt(s, s2),
.errPos = positions[pos]
}));
}
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2, Env & env, Expr & expr)
{
debugThrow(EvalError({
.msg = hintfmt(s, s2),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwEvalError(const char * s, const std::string & s2,
const std::string & s3)
{
debugThrowLastTrace(EvalError({
.msg = hintfmt(s, s2, s3),
.errPos = positions[noPos]
}));
}
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2,
const std::string & s3)
{
debugThrowLastTrace(EvalError({
.msg = hintfmt(s, s2, s3),
.errPos = positions[pos]
}));
}
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2,
const std::string & s3, Env & env, Expr & expr)
{
debugThrow(EvalError({
.msg = hintfmt(s, s2, s3),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2, Env & env, Expr & expr)
{
// p1 is where the error occurred; p2 is a position mentioned in the message.
debugThrow(EvalError({
.msg = hintfmt(s, symbols[sym], positions[p2]),
.errPos = positions[p1]
}), env, expr);
}
void EvalState::throwTypeError(const PosIdx pos, const char * s, const Value & v)
{
debugThrowLastTrace(TypeError({
.msg = hintfmt(s, showType(v)),
.errPos = positions[pos]
}));
}
void EvalState::throwTypeError(const PosIdx pos, const char * s, const Value & v, Env & env, Expr & expr)
{
debugThrow(TypeError({
.msg = hintfmt(s, showType(v)),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwTypeError(const PosIdx pos, const char * s)
{
debugThrowLastTrace(TypeError({
.msg = hintfmt(s),
.errPos = positions[pos]
}));
}
void EvalState::throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun,
const Symbol s2, Env & env, Expr &expr)
{
debugThrow(TypeError({
.msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s,
const ExprLambda & fun, const Symbol s2, Env & env, Expr &expr)
{
debugThrow(TypeError(ErrorInfo {
.msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]),
.errPos = positions[pos],
.suggestions = suggestions,
}), env, expr);
}
void EvalState::throwTypeError(const char * s, const Value & v, Env & env, Expr &expr)
{
debugThrow(TypeError({
.msg = hintfmt(s, showType(v)),
.errPos = positions[expr.getPos()],
}), env, expr);
}
void EvalState::throwAssertionError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr)
{
debugThrow(AssertionError({
.msg = hintfmt(s, s1),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr)
{
debugThrow(UndefinedVarError({
.msg = hintfmt(s, s1),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr)
{
debugThrow(MissingArgumentError({
.msg = hintfmt(s, s1),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) const
{
e.addTrace(nullptr, s, s2);
}
void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2, bool frame) const
void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const
{
e.addTrace(positions[pos], hintfmt(s, s2), frame);
e.addTrace(positions[pos], s, s2);
}
static std::unique_ptr<DebugTraceStacker> makeDebugTraceStacker(
@@ -946,7 +1113,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
if (env->type == Env::HasWithExpr) {
if (noEval) return 0;
Value * v = allocValue();
evalAttrs(*env->up, (Expr *) env->values[0], *v, noPos, "<borked>");
evalAttrs(*env->up, (Expr *) env->values[0], *v);
env->values[0] = v;
env->type = Env::HasWithAttrs;
}
@@ -956,7 +1123,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
return j->value;
}
if (!env->prevWith)
error("undefined variable '%1%'", symbols[var.name]).atPos(var.pos).withFrame(*env, var).debugThrow<UndefinedVarError>();
throwUndefinedVarError(var.pos, "undefined variable '%1%'", symbols[var.name], *env, const_cast<ExprVar&>(var));
for (size_t l = env->prevWith; l; --l, env = env->up) ;
}
}
@@ -1106,7 +1273,7 @@ void EvalState::cacheFile(
// computation.
if (mustBeTrivial &&
!(dynamic_cast<ExprAttrs *>(e)))
error("file '%s' must be an attribute set", path).debugThrow<EvalError>();
throw EvalError("file '%s' must be an attribute set", path);
eval(e, v);
} catch (Error & e) {
addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath);
@@ -1124,31 +1291,31 @@ void EvalState::eval(Expr * e, Value & v)
}
inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos, std::string_view errorCtx)
inline bool EvalState::evalBool(Env & env, Expr * e)
{
try {
Value v;
e->eval(*this, env, v);
if (v.type() != nBool)
error("value is %1% while a Boolean was expected", showType(v)).withFrame(env, *e).debugThrow<TypeError>();
return v.boolean;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
Value v;
e->eval(*this, env, v);
if (v.type() != nBool)
throwTypeError(noPos, "value is %1% while a Boolean was expected", v, env, *e);
return v.boolean;
}
inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const PosIdx pos, std::string_view errorCtx)
inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos)
{
try {
e->eval(*this, env, v);
if (v.type() != nAttrs)
error("value is %1% while a set was expected", showType(v)).withFrame(env, *e).debugThrow<TypeError>();
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
Value v;
e->eval(*this, env, v);
if (v.type() != nBool)
throwTypeError(pos, "value is %1% while a Boolean was expected", v, env, *e);
return v.boolean;
}
inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v)
{
e->eval(*this, env, v);
if (v.type() != nAttrs)
throwTypeError(noPos, "value is %1% while a set was expected", v, env, *e);
}
@@ -1221,7 +1388,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
Hence we need __overrides.) */
if (hasOverrides) {
Value * vOverrides = (*v.attrs)[overrides->second.displ].value;
state.forceAttrs(*vOverrides, [&]() { return vOverrides->determinePos(noPos); }, "while evaluating the `__overrides` attribute");
state.forceAttrs(*vOverrides, [&]() { return vOverrides->determinePos(noPos); });
Bindings * newBnds = state.allocBindings(v.attrs->capacity() + vOverrides->attrs->size());
for (auto & i : *v.attrs)
newBnds->push_back(i);
@@ -1249,11 +1416,11 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
state.forceValue(nameVal, i.pos);
if (nameVal.type() == nNull)
continue;
state.forceStringNoCtx(nameVal, i.pos, "while evaluating the name of a dynamic attribute");
state.forceStringNoCtx(nameVal);
auto nameSym = state.symbols.create(nameVal.string.s);
Bindings::iterator j = v.attrs->find(nameSym);
if (j != v.attrs->end())
state.error("dynamic attribute '%1%' already defined at %2%", state.symbols[nameSym], state.positions[j->pos]).atPos(i.pos).withFrame(env, *this).debugThrow<EvalError>();
state.throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, j->pos, env, *this);
i.valueExpr->setName(nameSym);
/* Keep sorted order so find can catch duplicates */
@@ -1350,14 +1517,15 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
return;
}
} else {
state.forceAttrs(*vAttrs, pos, "while selecting an attribute");
state.forceAttrs(*vAttrs, pos);
if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) {
std::set<std::string> allAttrNames;
for (auto & attr : *vAttrs->attrs)
allAttrNames.insert(state.symbols[attr.name]);
auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]);
state.error("attribute '%1%' missing", state.symbols[name])
.atPos(pos).withSuggestions(suggestions).withFrame(env, *this).debugThrow<EvalError>();
state.throwEvalError(
pos,
Suggestions::bestMatches(allAttrNames, state.symbols[name]),
"attribute '%1%' missing", state.symbols[name], env, *this);
}
}
vAttrs = j->value;
@@ -1452,12 +1620,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
if (!lambda.hasFormals())
env2.values[displ++] = args[0];
else {
try {
forceAttrs(*args[0], lambda.pos, "while evaluating the value passed for the lambda argument");
} catch (Error & e) {
if (pos) e.addTrace(positions[pos], "from call site");
throw;
}
forceAttrs(*args[0], pos);
if (lambda.arg)
env2.values[displ++] = args[0];
@@ -1469,15 +1632,8 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
for (auto & i : lambda.formals->formals) {
auto j = args[0]->attrs->get(i.name);
if (!j) {
if (!i.def) {
error("function '%1%' called without required argument '%2%'",
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
symbols[i.name])
.atPos(lambda.pos)
.withTrace(pos, "from call site")
.withFrame(*fun.lambda.env, lambda)
.debugThrow<TypeError>();
}
if (!i.def) throwTypeError(pos, "%1% called without required argument '%2%'",
lambda, i.name, *fun.lambda.env, lambda);
env2.values[displ++] = i.def->maybeThunk(*this, env2);
} else {
attrsUsed++;
@@ -1495,15 +1651,11 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
std::set<std::string> formalNames;
for (auto & formal : lambda.formals->formals)
formalNames.insert(symbols[formal.name]);
auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]);
error("function '%1%' called with unexpected argument '%2%'",
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
symbols[i.name])
.atPos(lambda.pos)
.withTrace(pos, "from call site")
.withSuggestions(suggestions)
.withFrame(*fun.lambda.env, lambda)
.debugThrow<TypeError>();
throwTypeError(
pos,
Suggestions::bestMatches(formalNames, symbols[i.name]),
"%1% called with unexpected argument '%2%'",
lambda, i.name, *fun.lambda.env, lambda);
}
abort(); // can't happen
}
@@ -1526,15 +1678,11 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
lambda.body->eval(*this, env2, vCur);
} catch (Error & e) {
if (loggerSettings.showTrace.get()) {
addErrorTrace(
e,
lambda.pos,
"while calling %s",
lambda.name
? concatStrings("'", symbols[lambda.name], "'")
: "anonymous lambda",
true);
if (pos) addErrorTrace(e, pos, "from call site%s", "", true);
addErrorTrace(e, lambda.pos, "while calling %s",
(lambda.name
? concatStrings("'", symbols[lambda.name], "'")
: "anonymous lambda"));
addErrorTrace(e, pos, "while evaluating call site%s", "");
}
throw;
}
@@ -1553,17 +1701,9 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
return;
} else {
/* We have all the arguments, so call the primop. */
auto name = vCur.primOp->name;
nrPrimOpCalls++;
if (countCalls) primOpCalls[name]++;
try {
vCur.primOp->fun(*this, noPos, args, vCur);
} catch (Error & e) {
addErrorTrace(e, pos, "while calling the '%1%' builtin", name);
throw;
}
if (countCalls) primOpCalls[vCur.primOp->name]++;
vCur.primOp->fun(*this, pos, args, vCur);
nrArgs -= argsLeft;
args += argsLeft;
@@ -1598,20 +1738,9 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
for (size_t i = 0; i < argsLeft; ++i)
vArgs[argsDone + i] = args[i];
auto name = primOp->primOp->name;
nrPrimOpCalls++;
if (countCalls) primOpCalls[name]++;
try {
// TODO:
// 1. Unify this and above code. Heavily redundant.
// 2. Create a fake env (arg1, arg2, etc.) and a fake expr (arg1: arg2: etc: builtins.name arg1 arg2 etc)
// so the debugger allows to inspect the wrong parameters passed to the builtin.
primOp->primOp->fun(*this, noPos, vArgs, vCur);
} catch (Error & e) {
addErrorTrace(e, pos, "while calling the '%1%' builtin", name);
throw;
}
if (countCalls) primOpCalls[primOp->primOp->name]++;
primOp->primOp->fun(*this, pos, vArgs, vCur);
nrArgs -= argsLeft;
args += argsLeft;
@@ -1624,18 +1753,14 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
heap-allocate a copy and use that instead. */
Value * args2[] = {allocValue(), args[0]};
*args2[0] = vCur;
try {
callFunction(*functor->value, 2, args2, vCur, functor->pos);
} catch (Error & e) {
e.addTrace(positions[pos], "while calling a functor (an attribute set with a '__functor' attribute)");
throw;
}
/* !!! Should we use the attr pos here? */
callFunction(*functor->value, 2, args2, vCur, pos);
nrArgs--;
args++;
}
else
error("attempt to call something which is not a function but %1%", showType(vCur)).atPos(pos).debugThrow<TypeError>();
throwTypeError(pos, "attempt to call something which is not a function but %1%", vCur);
}
vRes = vCur;
@@ -1699,12 +1824,13 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
if (j != args.end()) {
attrs.insert(*j);
} else if (!i.def) {
error(R"(cannot evaluate a function that has an argument without a value ('%1%')
throwMissingArgumentError(i.pos, R"(cannot evaluate a function that has an argument without a value ('%1%')
Nix attempted to evaluate a function as a top level expression; in
this case it must have its arguments supplied either by default
values, or passed explicitly with '--arg' or '--argstr'. See
https://nixos.org/manual/nix/stable/language/constructs.html#functions.)", symbols[i.name])
.atPos(i.pos).withFrame(*fun.lambda.env, *fun.lambda.fun).debugThrow<MissingArgumentError>();
https://nixos.org/manual/nix/stable/language/constructs.html#functions.)", symbols[i.name],
*fun.lambda.env, *fun.lambda.fun);
}
}
}
@@ -1727,17 +1853,16 @@ void ExprWith::eval(EvalState & state, Env & env, Value & v)
void ExprIf::eval(EvalState & state, Env & env, Value & v)
{
// We cheat in the parser, and pass the position of the condition as the position of the if itself.
(state.evalBool(env, cond, pos, "while evaluating a branch condition") ? then : else_)->eval(state, env, v);
(state.evalBool(env, cond, pos) ? then : else_)->eval(state, env, v);
}
void ExprAssert::eval(EvalState & state, Env & env, Value & v)
{
if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) {
if (!state.evalBool(env, cond, pos)) {
std::ostringstream out;
cond->show(state.symbols, out);
state.error("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow<AssertionError>();
state.throwAssertionError(pos, "assertion '%1%' failed", out.str(), env, *this);
}
body->eval(state, env, v);
}
@@ -1745,7 +1870,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v)
void ExprOpNot::eval(EvalState & state, Env & env, Value & v)
{
v.mkBool(!state.evalBool(env, e, noPos, "in the argument of the not operator")); // XXX: FIXME: !
v.mkBool(!state.evalBool(env, e));
}
@@ -1753,7 +1878,7 @@ void ExprOpEq::eval(EvalState & state, Env & env, Value & v)
{
Value v1; e1->eval(state, env, v1);
Value v2; e2->eval(state, env, v2);
v.mkBool(state.eqValues(v1, v2, pos, "while testing two values for equality"));
v.mkBool(state.eqValues(v1, v2));
}
@@ -1761,33 +1886,33 @@ void ExprOpNEq::eval(EvalState & state, Env & env, Value & v)
{
Value v1; e1->eval(state, env, v1);
Value v2; e2->eval(state, env, v2);
v.mkBool(!state.eqValues(v1, v2, pos, "while testing two values for inequality"));
v.mkBool(!state.eqValues(v1, v2));
}
void ExprOpAnd::eval(EvalState & state, Env & env, Value & v)
{
v.mkBool(state.evalBool(env, e1, pos, "in the left operand of the AND (&&) operator") && state.evalBool(env, e2, pos, "in the right operand of the AND (&&) operator"));
v.mkBool(state.evalBool(env, e1, pos) && state.evalBool(env, e2, pos));
}
void ExprOpOr::eval(EvalState & state, Env & env, Value & v)
{
v.mkBool(state.evalBool(env, e1, pos, "in the left operand of the OR (||) operator") || state.evalBool(env, e2, pos, "in the right operand of the OR (||) operator"));
v.mkBool(state.evalBool(env, e1, pos) || state.evalBool(env, e2, pos));
}
void ExprOpImpl::eval(EvalState & state, Env & env, Value & v)
{
v.mkBool(!state.evalBool(env, e1, pos, "in the left operand of the IMPL (->) operator") || state.evalBool(env, e2, pos, "in the right operand of the IMPL (->) operator"));
v.mkBool(!state.evalBool(env, e1, pos) || state.evalBool(env, e2, pos));
}
void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v)
{
Value v1, v2;
state.evalAttrs(env, e1, v1, pos, "in the left operand of the update (//) operator");
state.evalAttrs(env, e2, v2, pos, "in the right operand of the update (//) operator");
state.evalAttrs(env, e1, v1);
state.evalAttrs(env, e2, v2);
state.nrOpUpdates++;
@@ -1826,18 +1951,18 @@ void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v)
Value v1; e1->eval(state, env, v1);
Value v2; e2->eval(state, env, v2);
Value * lists[2] = { &v1, &v2 };
state.concatLists(v, 2, lists, pos, "while evaluating one of the elements to concatenate");
state.concatLists(v, 2, lists, pos);
}
void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx)
void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos)
{
nrListConcats++;
Value * nonEmpty = 0;
size_t len = 0;
for (size_t n = 0; n < nrLists; ++n) {
forceList(*lists[n], pos, errorCtx);
forceList(*lists[n], pos);
auto l = lists[n]->listSize();
len += l;
if (l) nonEmpty = lists[n];
@@ -1914,20 +2039,20 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
nf = n;
nf += vTmp.fpoint;
} else
state.error("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>();
state.throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp), env, *this);
} else if (firstType == nFloat) {
if (vTmp.type() == nInt) {
nf += vTmp.integer;
} else if (vTmp.type() == nFloat) {
nf += vTmp.fpoint;
} else
state.error("cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>();
state.throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp), env, *this);
} else {
if (s.empty()) s.reserve(es->size());
/* skip canonization of first path, which would only be not
canonized in the first place if it's coming from a ./${foo} type
path */
auto part = state.coerceToString(i_pos, vTmp, context, false, firstType == nString, !first, "while evaluating a path segment");
auto part = state.coerceToString(i_pos, vTmp, context, false, firstType == nString, !first);
sSize += part->size();
s.emplace_back(std::move(part));
}
@@ -1941,7 +2066,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
v.mkFloat(nf);
else if (firstType == nPath) {
if (!context.empty())
state.error("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow<EvalError>();
state.throwEvalError(pos, "a string that refers to a store path cannot be appended to a path", env, *this);
v.mkPath(canonPath(str()));
} else
v.mkStringMove(c_str(), context);
@@ -1991,47 +2116,33 @@ void EvalState::forceValueDeep(Value & v)
}
NixInt EvalState::forceInt(Value & v, const PosIdx pos, std::string_view errorCtx)
NixInt EvalState::forceInt(Value & v, const PosIdx pos)
{
try {
forceValue(v, pos);
if (v.type() != nInt)
error("value is %1% while an integer was expected", showType(v)).debugThrow<TypeError>();
forceValue(v, pos);
if (v.type() != nInt)
throwTypeError(pos, "value is %1% while an integer was expected", v);
return v.integer;
}
NixFloat EvalState::forceFloat(Value & v, const PosIdx pos)
{
forceValue(v, pos);
if (v.type() == nInt)
return v.integer;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
else if (v.type() != nFloat)
throwTypeError(pos, "value is %1% while a float was expected", v);
return v.fpoint;
}
NixFloat EvalState::forceFloat(Value & v, const PosIdx pos, std::string_view errorCtx)
bool EvalState::forceBool(Value & v, const PosIdx pos)
{
try {
forceValue(v, pos);
if (v.type() == nInt)
return v.integer;
else if (v.type() != nFloat)
error("value is %1% while a float was expected", showType(v)).debugThrow<TypeError>();
return v.fpoint;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
}
bool EvalState::forceBool(Value & v, const PosIdx pos, std::string_view errorCtx)
{
try {
forceValue(v, pos);
if (v.type() != nBool)
error("value is %1% while a Boolean was expected", showType(v)).debugThrow<TypeError>();
return v.boolean;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
forceValue(v, pos);
if (v.type() != nBool)
throwTypeError(pos, "value is %1% while a Boolean was expected", v);
return v.boolean;
}
@@ -2041,30 +2152,21 @@ bool EvalState::isFunctor(Value & fun)
}
void EvalState::forceFunction(Value & v, const PosIdx pos, std::string_view errorCtx)
void EvalState::forceFunction(Value & v, const PosIdx pos)
{
try {
forceValue(v, pos);
if (v.type() != nFunction && !isFunctor(v))
error("value is %1% while a function was expected", showType(v)).debugThrow<TypeError>();
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
forceValue(v, pos);
if (v.type() != nFunction && !isFunctor(v))
throwTypeError(pos, "value is %1% while a function was expected", v);
}
std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string_view errorCtx)
std::string_view EvalState::forceString(Value & v, const PosIdx pos)
{
try {
forceValue(v, pos);
if (v.type() != nString)
error("value is %1% while a string was expected", showType(v)).debugThrow<TypeError>();
return v.string.s;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
forceValue(v, pos);
if (v.type() != nString) {
throwTypeError(pos, "value is %1% while a string was expected", v);
}
return v.string.s;
}
@@ -2087,19 +2189,24 @@ NixStringContext Value::getContext(const Store & store)
}
std::string_view EvalState::forceString(Value & v, PathSet & context, const PosIdx pos, std::string_view errorCtx)
std::string_view EvalState::forceString(Value & v, PathSet & context, const PosIdx pos)
{
auto s = forceString(v, pos, errorCtx);
auto s = forceString(v, pos);
copyContext(v, context);
return s;
}
std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx)
std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos)
{
auto s = forceString(v, pos, errorCtx);
auto s = forceString(v, pos);
if (v.string.context) {
error("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0]).withTrace(pos, errorCtx).debugThrow<EvalError>();
if (pos)
throwEvalError(pos, "the string '%1%' is not allowed to refer to a store path (such as '%2%')",
v.string.s, v.string.context[0]);
else
throwEvalError("the string '%1%' is not allowed to refer to a store path (such as '%2%')",
v.string.s, v.string.context[0]);
}
return s;
}
@@ -2123,15 +2230,14 @@ std::optional<std::string> EvalState::tryAttrsToString(const PosIdx pos, Value &
if (i != v.attrs->end()) {
Value v1;
callFunction(*i->value, v, v1, pos);
return coerceToString(pos, v1, context, coerceMore, copyToStore,
"while evaluating the result of the `toString` attribute").toOwned();
return coerceToString(pos, v1, context, coerceMore, copyToStore).toOwned();
}
return {};
}
BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet & context,
bool coerceMore, bool copyToStore, bool canonicalizePath, std::string_view errorCtx)
bool coerceMore, bool copyToStore, bool canonicalizePath)
{
forceValue(v, pos);
@@ -2155,12 +2261,12 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
return std::move(*maybeString);
auto i = v.attrs->find(sOutPath);
if (i == v.attrs->end())
error("cannot coerce a set to a string", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
return coerceToString(pos, *i->value, context, coerceMore, copyToStore, canonicalizePath, errorCtx);
throwTypeError(pos, "cannot coerce a set to a string");
return coerceToString(pos, *i->value, context, coerceMore, copyToStore);
}
if (v.type() == nExternal)
return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore, errorCtx);
return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore);
if (coerceMore) {
/* Note that `false' is represented as an empty string for
@@ -2174,13 +2280,7 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
if (v.isList()) {
std::string result;
for (auto [n, v2] : enumerate(v.listItems())) {
try {
result += *coerceToString(noPos, *v2, context, coerceMore, copyToStore, canonicalizePath,
"while evaluating one element of the list");
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
result += *coerceToString(pos, *v2, context, coerceMore, copyToStore);
if (n < v.listSize() - 1
/* !!! not quite correct */
&& (!v2->isList() || v2->listSize() != 0))
@@ -2190,14 +2290,14 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
}
}
error("cannot coerce %1% to a string", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
throwTypeError(pos, "cannot coerce %1% to a string", v);
}
StorePath EvalState::copyPathToStore(PathSet & context, const Path & path)
{
if (nix::isDerivation(path))
error("file names are not allowed to end in '%1%'", drvExtension).debugThrow<EvalError>();
throwEvalError("file names are not allowed to end in '%1%'", drvExtension);
auto dstPath = [&]() -> StorePath
{
@@ -2218,25 +2318,28 @@ StorePath EvalState::copyPathToStore(PathSet & context, const Path & path)
}
Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context)
{
auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned();
auto path = coerceToString(pos, v, context, false, false).toOwned();
if (path == "" || path[0] != '/')
error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
throwEvalError(pos, "string '%1%' doesn't represent an absolute path", path);
return path;
}
StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & context)
{
auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned();
auto path = coerceToString(pos, v, context, false, false).toOwned();
if (auto storePath = store->maybeParseStorePath(path))
return *storePath;
error("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
throw EvalError({
.msg = hintfmt("path '%1%' is not in the Nix store", path),
.errPos = positions[pos]
});
}
bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_view errorCtx)
bool EvalState::eqValues(Value & v1, Value & v2)
{
forceValue(v1, noPos);
forceValue(v2, noPos);
@@ -2256,6 +2359,7 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
if (v1.type() != v2.type()) return false;
switch (v1.type()) {
case nInt:
return v1.integer == v2.integer;
@@ -2274,7 +2378,7 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
case nList:
if (v1.listSize() != v2.listSize()) return false;
for (size_t n = 0; n < v1.listSize(); ++n)
if (!eqValues(*v1.listElems()[n], *v2.listElems()[n], pos, errorCtx)) return false;
if (!eqValues(*v1.listElems()[n], *v2.listElems()[n])) return false;
return true;
case nAttrs: {
@@ -2284,7 +2388,7 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
Bindings::iterator i = v1.attrs->find(sOutPath);
Bindings::iterator j = v2.attrs->find(sOutPath);
if (i != v1.attrs->end() && j != v2.attrs->end())
return eqValues(*i->value, *j->value, pos, errorCtx);
return eqValues(*i->value, *j->value);
}
if (v1.attrs->size() != v2.attrs->size()) return false;
@@ -2292,7 +2396,7 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
/* Otherwise, compare the attributes one by one. */
Bindings::iterator i, j;
for (i = v1.attrs->begin(), j = v2.attrs->begin(); i != v1.attrs->end(); ++i, ++j)
if (i->name != j->name || !eqValues(*i->value, *j->value, pos, errorCtx))
if (i->name != j->name || !eqValues(*i->value, *j->value))
return false;
return true;
@@ -2309,7 +2413,9 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
return v1.fpoint == v2.fpoint;
default:
error("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow<EvalError>();
throwEvalError("cannot compare %1% with %2%",
showType(v1),
showType(v2));
}
}
@@ -2433,13 +2539,12 @@ void EvalState::printStats()
}
std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, std::string_view errorCtx) const
std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const
{
auto e = TypeError({
.msg = hintfmt("cannot coerce %1% to a string", showType())
throw TypeError({
.msg = hintfmt("cannot coerce %1% to a string", showType()),
.errPos = pos
});
e.addTrace(pos, errorCtx);
throw e;
}

View File

@@ -86,43 +86,6 @@ struct DebugTrace {
void debugError(Error * e, Env & env, Expr & expr);
class ErrorBuilder
{
private:
EvalState & state;
ErrorInfo info;
ErrorBuilder(EvalState & s, ErrorInfo && i): state(s), info(i) { }
public:
template<typename... Args>
[[nodiscard, gnu::noinline]]
static ErrorBuilder * create(EvalState & s, const Args & ... args)
{
return new ErrorBuilder(s, ErrorInfo { .msg = hintfmt(args...) });
}
[[nodiscard, gnu::noinline]]
ErrorBuilder & atPos(PosIdx pos);
[[nodiscard, gnu::noinline]]
ErrorBuilder & withTrace(PosIdx pos, const std::string_view text);
[[nodiscard, gnu::noinline]]
ErrorBuilder & withFrameTrace(PosIdx pos, const std::string_view text);
[[nodiscard, gnu::noinline]]
ErrorBuilder & withSuggestions(Suggestions & s);
[[nodiscard, gnu::noinline]]
ErrorBuilder & withFrame(const Env & e, const Expr & ex);
template<class ErrorType>
[[gnu::noinline, gnu::noreturn]]
void debugThrow();
};
class EvalState : public std::enable_shared_from_this<EvalState>
{
public:
@@ -182,36 +145,30 @@ public:
template<class E>
[[gnu::noinline, gnu::noreturn]]
void debugThrowLastTrace(E && error)
void debugThrow(E && error, const Env & env, const Expr & expr)
{
debugThrow(error, nullptr, nullptr);
}
template<class E>
[[gnu::noinline, gnu::noreturn]]
void debugThrow(E && error, const Env * env, const Expr * expr)
{
if (debugRepl && ((env && expr) || !debugTraces.empty())) {
if (!env || !expr) {
const DebugTrace & last = debugTraces.front();
env = &last.env;
expr = &last.expr;
}
runDebugRepl(&error, *env, *expr);
}
if (debugRepl)
runDebugRepl(&error, env, expr);
throw std::move(error);
}
ErrorBuilder * errorBuilder;
template<class E>
[[gnu::noinline, gnu::noreturn]]
void debugThrowLastTrace(E && e)
{
// Call this in the situation where Expr and Env are inaccessible.
// The debugger will start in the last context that's in the
// DebugTrace stack.
if (debugRepl && !debugTraces.empty()) {
const DebugTrace & last = debugTraces.front();
runDebugRepl(&e, last.env, last.expr);
}
template<typename... Args>
[[nodiscard, gnu::noinline]]
ErrorBuilder & error(const Args & ... args) {
errorBuilder = ErrorBuilder::create(*this, args...);
return *errorBuilder;
throw std::move(e);
}
private:
SrcToStore srcToStore;
@@ -325,8 +282,8 @@ public:
/* Evaluation the expression, then verify that it has the expected
type. */
inline bool evalBool(Env & env, Expr * e);
inline bool evalBool(Env & env, Expr * e, const PosIdx pos, std::string_view errorCtx);
inline void evalAttrs(Env & env, Expr * e, Value & v, const PosIdx pos, std::string_view errorCtx);
inline bool evalBool(Env & env, Expr * e, const PosIdx pos);
inline void evalAttrs(Env & env, Expr * e, Value & v);
/* If `v' is a thunk, enter it and overwrite `v' with the result
of the evaluation of the thunk. If `v' is a delayed function
@@ -342,25 +299,89 @@ public:
void forceValueDeep(Value & v);
/* Force `v', and then verify that it has the expected type. */
NixInt forceInt(Value & v, const PosIdx pos, std::string_view errorCtx);
NixFloat forceFloat(Value & v, const PosIdx pos, std::string_view errorCtx);
bool forceBool(Value & v, const PosIdx pos, std::string_view errorCtx);
NixInt forceInt(Value & v, const PosIdx pos);
NixFloat forceFloat(Value & v, const PosIdx pos);
bool forceBool(Value & v, const PosIdx pos);
void forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx);
void forceAttrs(Value & v, const PosIdx pos);
template <typename Callable>
inline void forceAttrs(Value & v, Callable getPos, std::string_view errorCtx);
inline void forceAttrs(Value & v, Callable getPos);
inline void forceList(Value & v, const PosIdx pos, std::string_view errorCtx);
void forceFunction(Value & v, const PosIdx pos, std::string_view errorCtx); // either lambda or primop
std::string_view forceString(Value & v, const PosIdx pos, std::string_view errorCtx);
std::string_view forceString(Value & v, PathSet & context, const PosIdx pos, std::string_view errorCtx);
std::string_view forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx);
inline void forceList(Value & v, const PosIdx pos);
void forceFunction(Value & v, const PosIdx pos); // either lambda or primop
std::string_view forceString(Value & v, const PosIdx pos = noPos);
std::string_view forceString(Value & v, PathSet & context, const PosIdx pos = noPos);
std::string_view forceStringNoCtx(Value & v, const PosIdx pos = noPos);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const char * s, const std::string & s2);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const char * s, const std::string & s2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const char * s, const std::string & s2, const std::string & s3,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const char * s, const std::string & s2, const std::string & s3);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, const std::string & s2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const char * s, const Value & v);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const char * s, const Value & v,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const char * s);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const char * s,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, const Symbol s2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s, const ExprLambda & fun, const Symbol s2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const char * s, const Value & v,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwAssertionError(const PosIdx pos, const char * s, const std::string & s1,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1,
Env & env, Expr & expr);
[[gnu::noinline]]
void addErrorTrace(Error & e, const char * s, const std::string & s2) const;
[[gnu::noinline]]
void addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2, bool frame = false) const;
void addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const;
public:
/* Return true iff the value `v' denotes a derivation (i.e. a
@@ -376,18 +397,17 @@ public:
referenced paths are copied to the Nix store as a side effect. */
BackedStringView coerceToString(const PosIdx pos, Value & v, PathSet & context,
bool coerceMore = false, bool copyToStore = true,
bool canonicalizePath = true,
std::string_view errorCtx = "");
bool canonicalizePath = true);
StorePath copyPathToStore(PathSet & context, const Path & path);
/* Path coercion. Converts strings, paths and derivations to a
path. The result is guaranteed to be a canonicalised, absolute
path. Nothing is copied to the store. */
Path coerceToPath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx);
Path coerceToPath(const PosIdx pos, Value & v, PathSet & context);
/* Like coerceToPath, but the result must be a store path. */
StorePath coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx);
StorePath coerceToStorePath(const PosIdx pos, Value & v, PathSet & context);
public:
@@ -447,7 +467,7 @@ public:
/* Do a deep equality test between two values. That is, list
elements and attributes are compared recursively. */
bool eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_view errorCtx);
bool eqValues(Value & v1, Value & v2);
bool isFunctor(Value & fun);
@@ -482,7 +502,7 @@ public:
void mkThunk_(Value & v, Expr * expr);
void mkPos(Value & v, PosIdx pos);
void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx);
void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos);
/* Print statistics. */
void printStats();
@@ -645,13 +665,6 @@ extern EvalSettings evalSettings;
static const std::string corepkgsPrefix{"/__corepkgs__/"};
template<class ErrorType>
void ErrorBuilder::debugThrow()
{
// NOTE: We always use the -LastTrace version as we push the new trace in withFrame()
state.debugThrowLastTrace(ErrorType(info));
}
}
#include "eval-inline.hh"

View File

@@ -259,28 +259,28 @@ static Flake getFlake(
if (setting.value->type() == nString)
flake.config.settings.emplace(
state.symbols[setting.name],
std::string(state.forceStringNoCtx(*setting.value, setting.pos, "")));
std::string(state.forceStringNoCtx(*setting.value, setting.pos)));
else if (setting.value->type() == nPath) {
PathSet emptyContext = {};
flake.config.settings.emplace(
state.symbols[setting.name],
state.coerceToString(setting.pos, *setting.value, emptyContext, false, true, true, "") .toOwned());
state.coerceToString(setting.pos, *setting.value, emptyContext, false, true, true) .toOwned());
}
else if (setting.value->type() == nInt)
flake.config.settings.emplace(
state.symbols[setting.name],
state.forceInt(*setting.value, setting.pos, ""));
state.forceInt(*setting.value, setting.pos));
else if (setting.value->type() == nBool)
flake.config.settings.emplace(
state.symbols[setting.name],
Explicit<bool> { state.forceBool(*setting.value, setting.pos, "") });
Explicit<bool> { state.forceBool(*setting.value, setting.pos) });
else if (setting.value->type() == nList) {
std::vector<std::string> ss;
for (auto elem : setting.value->listItems()) {
if (elem->type() != nString)
throw TypeError("list element in flake configuration setting '%s' is %s while a string is expected",
state.symbols[setting.name], showType(*setting.value));
ss.emplace_back(state.forceStringNoCtx(*elem, setting.pos, ""));
ss.emplace_back(state.forceStringNoCtx(*elem, setting.pos));
}
flake.config.settings.emplace(state.symbols[setting.name], ss);
}
@@ -741,7 +741,7 @@ void callFlake(EvalState & state,
static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake"));
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos));
auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
if (evalSettings.pureEval && !flakeRef.input.isLocked())
throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, state.positions[pos]);

View File

@@ -51,7 +51,7 @@ std::string DrvInfo::queryName() const
if (name == "" && attrs) {
auto i = attrs->find(state->sName);
if (i == attrs->end()) throw TypeError("derivation name missing");
name = state->forceStringNoCtx(*i->value, noPos, "while evaluating the 'name' attribute of a derivation");
name = state->forceStringNoCtx(*i->value);
}
return name;
}
@@ -61,7 +61,7 @@ std::string DrvInfo::querySystem() const
{
if (system == "" && attrs) {
auto i = attrs->find(state->sSystem);
system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, i->pos, "while evaluating the 'system' attribute of a derivation");
system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, i->pos);
}
return system;
}
@@ -75,7 +75,7 @@ std::optional<StorePath> DrvInfo::queryDrvPath() const
if (i == attrs->end())
drvPath = {std::nullopt};
else
drvPath = {state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the 'drvPath' attribute of a derivation")};
drvPath = {state->coerceToStorePath(i->pos, *i->value, context)};
}
return drvPath.value_or(std::nullopt);
}
@@ -95,7 +95,7 @@ StorePath DrvInfo::queryOutPath() const
Bindings::iterator i = attrs->find(state->sOutPath);
PathSet context;
if (i != attrs->end())
outPath = state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the output path of a derivation");
outPath = state->coerceToStorePath(i->pos, *i->value, context);
}
if (!outPath)
throw UnimplementedError("CA derivations are not yet supported");
@@ -109,23 +109,23 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall
/* Get the outputs list. */
Bindings::iterator i;
if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) {
state->forceList(*i->value, i->pos, "while evaluating the 'outputs' attribute of a derivation");
state->forceList(*i->value, i->pos);
/* For each output... */
for (auto elem : i->value->listItems()) {
std::string output(state->forceStringNoCtx(*elem, i->pos, "while evaluating the name of an output of a derivation"));
std::string output(state->forceStringNoCtx(*elem, i->pos));
if (withPaths) {
/* Evaluate the corresponding set. */
Bindings::iterator out = attrs->find(state->symbols.create(output));
if (out == attrs->end()) continue; // FIXME: throw error?
state->forceAttrs(*out->value, i->pos, "while evaluating an output of a derivation");
state->forceAttrs(*out->value, i->pos);
/* And evaluate its outPath attribute. */
Bindings::iterator outPath = out->value->attrs->find(state->sOutPath);
if (outPath == out->value->attrs->end()) continue; // FIXME: throw error?
PathSet context;
outputs.emplace(output, state->coerceToStorePath(outPath->pos, *outPath->value, context, "while evaluating an output path of a derivation"));
outputs.emplace(output, state->coerceToStorePath(outPath->pos, *outPath->value, context));
} else
outputs.emplace(output, std::nullopt);
}
@@ -137,7 +137,7 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall
return outputs;
Bindings::iterator i;
if (attrs && (i = attrs->find(state->sOutputSpecified)) != attrs->end() && state->forceBool(*i->value, i->pos, "while evaluating the 'outputSpecified' attribute of a derivation")) {
if (attrs && (i = attrs->find(state->sOutputSpecified)) != attrs->end() && state->forceBool(*i->value, i->pos)) {
Outputs result;
auto out = outputs.find(queryOutputName());
if (out == outputs.end())
@@ -169,7 +169,7 @@ std::string DrvInfo::queryOutputName() const
{
if (outputName == "" && attrs) {
Bindings::iterator i = attrs->find(state->sOutputName);
outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value, noPos, "while evaluating the output name of a derivation") : "";
outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value) : "";
}
return outputName;
}
@@ -181,7 +181,7 @@ Bindings * DrvInfo::getMeta()
if (!attrs) return 0;
Bindings::iterator a = attrs->find(state->sMeta);
if (a == attrs->end()) return 0;
state->forceAttrs(*a->value, a->pos, "while evaluating the 'meta' attribute of a derivation");
state->forceAttrs(*a->value, a->pos);
meta = a->value->attrs;
return meta;
}
@@ -382,7 +382,7 @@ static void getDerivations(EvalState & state, Value & vIn,
`recurseForDerivations = true' attribute. */
if (i->value->type() == nAttrs) {
Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations);
if (j != i->value->attrs->end() && state.forceBool(*j->value, j->pos, "while evaluating the attribute `recurseForDerivations`"))
if (j != i->value->attrs->end() && state.forceBool(*j->value, j->pos))
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
}
}

View File

@@ -8,6 +8,7 @@
#include "error.hh"
#include "chunked-vector.hh"
namespace nix {

View File

@@ -400,21 +400,21 @@ expr_op
| '-' expr_op %prec NEGATE { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {new ExprInt(0), $2}); }
| expr_op EQ expr_op { $$ = new ExprOpEq($1, $3); }
| expr_op NEQ expr_op { $$ = new ExprOpNEq($1, $3); }
| expr_op '<' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$1, $3}); }
| expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$3, $1})); }
| expr_op '>' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$3, $1}); }
| expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$1, $3})); }
| expr_op AND expr_op { $$ = new ExprOpAnd(makeCurPos(@2, data), $1, $3); }
| expr_op OR expr_op { $$ = new ExprOpOr(makeCurPos(@2, data), $1, $3); }
| expr_op IMPL expr_op { $$ = new ExprOpImpl(makeCurPos(@2, data), $1, $3); }
| expr_op UPDATE expr_op { $$ = new ExprOpUpdate(makeCurPos(@2, data), $1, $3); }
| expr_op '<' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$1, $3}); }
| expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$3, $1})); }
| expr_op '>' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$3, $1}); }
| expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$1, $3})); }
| expr_op AND expr_op { $$ = new ExprOpAnd(CUR_POS, $1, $3); }
| expr_op OR expr_op { $$ = new ExprOpOr(CUR_POS, $1, $3); }
| expr_op IMPL expr_op { $$ = new ExprOpImpl(CUR_POS, $1, $3); }
| expr_op UPDATE expr_op { $$ = new ExprOpUpdate(CUR_POS, $1, $3); }
| expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); }
| expr_op '+' expr_op
{ $$ = new ExprConcatStrings(makeCurPos(@2, data), false, new std::vector<std::pair<PosIdx, Expr *> >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); }
| expr_op '-' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__sub")), {$1, $3}); }
| expr_op '*' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__mul")), {$1, $3}); }
| expr_op '/' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__div")), {$1, $3}); }
| expr_op CONCAT expr_op { $$ = new ExprOpConcatLists(makeCurPos(@2, data), $1, $3); }
{ $$ = new ExprConcatStrings(CUR_POS, false, new std::vector<std::pair<PosIdx, Expr *>>({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); }
| expr_op '-' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {$1, $3}); }
| expr_op '*' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__mul")), {$1, $3}); }
| expr_op '/' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__div")), {$1, $3}); }
| expr_op CONCAT expr_op { $$ = new ExprOpConcatLists(CUR_POS, $1, $3); }
| expr_app
;
@@ -782,13 +782,13 @@ Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, c
if (hasPrefix(path, "nix/"))
return concatStrings(corepkgsPrefix, path.substr(4));
debugThrow(ThrownError({
debugThrowLastTrace(ThrownError({
.msg = hintfmt(evalSettings.pureEval
? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)"
: "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)",
path),
.errPos = positions[pos]
}), 0, 0);
}));
}
@@ -802,7 +802,7 @@ std::pair<bool, std::string> EvalState::resolveSearchPathElem(const SearchPathEl
if (EvalSettings::isPseudoUrl(elem.second)) {
try {
auto storePath = fetchers::downloadTarball(
store, EvalSettings::resolvePseudoUrl(elem.second), "source", false).first.storePath;
store, EvalSettings::resolvePseudoUrl(elem.second), "source", false).tree.storePath;
res = { true, store->toRealPath(storePath) };
} catch (FileTransferError & e) {
logWarning({

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ namespace nix {
static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardStringContext");
auto s = state.coerceToString(pos, *args[0], context);
v.mkString(*s);
}
@@ -18,7 +18,7 @@ static RegisterPrimOp primop_unsafeDiscardStringContext("__unsafeDiscardStringCo
static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.hasContext");
state.forceString(*args[0], context, pos);
v.mkBool(!context.empty());
}
@@ -34,7 +34,7 @@ static RegisterPrimOp primop_hasContext("__hasContext", 1, prim_hasContext);
static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardOutputDependency");
auto s = state.coerceToString(pos, *args[0], context);
PathSet context2;
for (auto && p : context) {
@@ -80,18 +80,16 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
Strings outputs;
};
PathSet context;
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getContext");
state.forceString(*args[0], context, pos);
auto contextInfos = std::map<StorePath, ContextInfo>();
for (const auto & p : context) {
Path drv;
std::string output;
NixStringContextElem ctx = NixStringContextElem::parse(*state.store, p);
std::visit(overloaded {
[&](NixStringContextElem::DrvDeep & d) {
contextInfos[d.drvPath].allOutputs = true;
},
[&](NixStringContextElem::Built & b) {
contextInfos[b.drvPath].outputs.emplace_back(std::move(output));
contextInfos[b.drvPath].outputs.emplace_back(std::move(b.output));
},
[&](NixStringContextElem::Opaque & o) {
contextInfos[o.path].path = true;
@@ -132,9 +130,9 @@ static RegisterPrimOp primop_getContext("__getContext", 1, prim_getContext);
static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
auto orig = state.forceString(*args[0], context, noPos, "while evaluating the first argument passed to builtins.appendContext");
auto orig = state.forceString(*args[0], context, pos);
state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.appendContext");
state.forceAttrs(*args[1], pos);
auto sPath = state.symbols.create("path");
auto sAllOutputs = state.symbols.create("allOutputs");
@@ -142,24 +140,24 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
const auto & name = state.symbols[i.name];
if (!state.store->isStorePath(name))
throw EvalError({
.msg = hintfmt("context key '%s' is not a store path", name),
.msg = hintfmt("Context key '%s' is not a store path", name),
.errPos = state.positions[i.pos]
});
if (!settings.readOnlyMode)
state.store->ensurePath(state.store->parseStorePath(name));
state.forceAttrs(*i.value, i.pos, "while evaluating the value of a string context");
state.forceAttrs(*i.value, i.pos);
auto iter = i.value->attrs->find(sPath);
if (iter != i.value->attrs->end()) {
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `path` attribute of a string context"))
if (state.forceBool(*iter->value, iter->pos))
context.emplace(name);
}
iter = i.value->attrs->find(sAllOutputs);
if (iter != i.value->attrs->end()) {
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `allOutputs` attribute of a string context")) {
if (state.forceBool(*iter->value, iter->pos)) {
if (!isDerivation(name)) {
throw EvalError({
.msg = hintfmt("tried to add all-outputs context of %s, which is not a derivation, to a string", name),
.msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", name),
.errPos = state.positions[i.pos]
});
}
@@ -169,15 +167,15 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
iter = i.value->attrs->find(state.sOutputs);
if (iter != i.value->attrs->end()) {
state.forceList(*iter->value, iter->pos, "while evaluating the `outputs` attribute of a string context");
state.forceList(*iter->value, iter->pos);
if (iter->value->listSize() && !isDerivation(name)) {
throw EvalError({
.msg = hintfmt("tried to add derivation output context of %s, which is not a derivation, to a string", name),
.msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", name),
.errPos = state.positions[i.pos]
});
}
for (auto elem : iter->value->listItems()) {
auto outputName = state.forceStringNoCtx(*elem, iter->pos, "while evaluating an output name within a string context");
auto outputName = state.forceStringNoCtx(*elem, iter->pos);
context.insert(concatStrings("!", outputName, "!", name));
}
}

View File

@@ -7,7 +7,7 @@ namespace nix {
static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.fetchClosure");
state.forceAttrs(*args[0], pos);
std::optional<std::string> fromStoreUrl;
std::optional<StorePath> fromPath;
@@ -19,8 +19,7 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
if (attrName == "fromPath") {
PathSet context;
fromPath = state.coerceToStorePath(attr.pos, *attr.value, context,
"while evaluating the 'fromPath' attribute passed to builtins.fetchClosure");
fromPath = state.coerceToStorePath(attr.pos, *attr.value, context);
}
else if (attrName == "toPath") {
@@ -28,14 +27,12 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
toCA = true;
if (attr.value->type() != nString || attr.value->string.s != std::string("")) {
PathSet context;
toPath = state.coerceToStorePath(attr.pos, *attr.value, context,
"while evaluating the 'toPath' attribute passed to builtins.fetchClosure");
toPath = state.coerceToStorePath(attr.pos, *attr.value, context);
}
}
else if (attrName == "fromStore")
fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos,
"while evaluating the 'fromStore' attribute passed to builtins.fetchClosure");
fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos);
else
throw Error({

View File

@@ -19,21 +19,23 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
if (args[0]->type() == nAttrs) {
state.forceAttrs(*args[0], pos);
for (auto & attr : *args[0]->attrs) {
std::string_view n(state.symbols[attr.name]);
if (n == "url")
url = state.coerceToString(attr.pos, *attr.value, context, false, false, "while evaluating the `url` attribute passed to builtins.fetchMercurial").toOwned();
url = state.coerceToString(attr.pos, *attr.value, context, false, false).toOwned();
else if (n == "rev") {
// Ugly: unlike fetchGit, here the "rev" attribute can
// be both a revision or a branch/tag name.
auto value = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `rev` attribute passed to builtins.fetchMercurial");
auto value = state.forceStringNoCtx(*attr.value, attr.pos);
if (std::regex_match(value.begin(), value.end(), revRegex))
rev = Hash::parseAny(value, htSHA1);
else
ref = value;
}
else if (n == "name")
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.fetchMercurial");
name = state.forceStringNoCtx(*attr.value, attr.pos);
else
throw EvalError({
.msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]),
@@ -48,7 +50,7 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
});
} else
url = state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to builtins.fetchMercurial").toOwned();
url = state.coerceToString(pos, *args[0], context, false, false).toOwned();
// FIXME: git externals probably can be used to bypass the URI
// whitelist. Ah well.

View File

@@ -102,7 +102,7 @@ static void fetchTree(
state.forceValue(*args[0], pos);
if (args[0]->type() == nAttrs) {
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.fetchTree");
state.forceAttrs(*args[0], pos);
fetchers::Attrs attrs;
@@ -112,7 +112,7 @@ static void fetchTree(
.msg = hintfmt("unexpected attribute 'type'"),
.errPos = state.positions[pos]
}));
type = state.forceStringNoCtx(*aType->value, aType->pos, "while evaluating the `type` attribute passed to builtins.fetchTree");
type = state.forceStringNoCtx(*aType->value, aType->pos);
} else if (!type)
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"),
@@ -125,7 +125,7 @@ static void fetchTree(
if (attr.name == state.sType) continue;
state.forceValue(*attr.value, attr.pos);
if (attr.value->type() == nPath || attr.value->type() == nString) {
auto s = state.coerceToString(attr.pos, *attr.value, context, false, false, "").toOwned();
auto s = state.coerceToString(attr.pos, *attr.value, context, false, false).toOwned();
attrs.emplace(state.symbols[attr.name],
state.symbols[attr.name] == "url"
? type == "git"
@@ -151,7 +151,7 @@ static void fetchTree(
input = fetchers::Input::fromAttrs(std::move(attrs));
} else {
auto url = state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to the fetcher").toOwned();
auto url = state.coerceToString(pos, *args[0], context, false, false).toOwned();
if (type == "git") {
fetchers::Attrs attrs;
@@ -195,14 +195,16 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
if (args[0]->type() == nAttrs) {
state.forceAttrs(*args[0], pos);
for (auto & attr : *args[0]->attrs) {
std::string_view n(state.symbols[attr.name]);
if (n == "url")
url = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the url we should fetch");
url = state.forceStringNoCtx(*attr.value, attr.pos);
else if (n == "sha256")
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the sha256 of the content we should fetch"), htSHA256);
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos), htSHA256);
else if (n == "name")
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the name of the content we should fetch");
name = state.forceStringNoCtx(*attr.value, attr.pos);
else
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("unsupported argument '%s' to '%s'", n, who),
@@ -216,7 +218,10 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
.errPos = state.positions[pos]
}));
} else
url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch");
url = state.forceStringNoCtx(*args[0], pos);
if (who == "fetchTarball")
url = evalSettings.resolvePseudoUrl(*url);
state.checkURI(*url);
@@ -243,7 +248,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
// https://github.com/NixOS/nix/issues/4313
auto storePath =
unpack
? fetchers::downloadTarball(state.store, *url, name, (bool) expectedHash).first.storePath
? fetchers::downloadTarball(state.store, *url, name, (bool) expectedHash).tree.storePath
: fetchers::downloadFile(state.store, *url, name, (bool) expectedHash).storePath;
if (expectedHash) {

View File

@@ -7,7 +7,7 @@ namespace nix {
static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, Value & val)
{
auto toml = state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.fromTOML");
auto toml = state.forceStringNoCtx(*args[0], pos);
std::istringstream tomlStream(std::string{toml});

View File

@@ -1,94 +0,0 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "libexprtests.hh"
namespace nix {
using namespace testing;
// Testing eval of PrimOp's
class ErrorTraceTest : public LibExprTest { };
#define ASSERT_TRACE1(args, type, message) \
ASSERT_THROW( \
try { \
eval("builtins." args); \
} catch (BaseError & e) { \
ASSERT_EQ(PrintToString(e.info().msg), \
PrintToString(message)); \
auto trace = e.info().traces.rbegin(); \
ASSERT_EQ(PrintToString(trace->hint), \
PrintToString(hintfmt("while calling the '%s' builtin", "genericClosure"))); \
throw; \
} \
, type \
)
#define ASSERT_TRACE2(args, type, message, context) \
ASSERT_THROW( \
try { \
eval("builtins." args); \
} catch (BaseError & e) { \
ASSERT_EQ(PrintToString(e.info().msg), \
PrintToString(message)); \
auto trace = e.info().traces.rbegin(); \
ASSERT_EQ(PrintToString(trace->hint), \
PrintToString(context)); \
++trace; \
ASSERT_EQ(PrintToString(trace->hint), \
PrintToString(hintfmt("while calling the '%s' builtin", "genericClosure"))); \
throw; \
} \
, type \
)
TEST_F(ErrorTraceTest, genericClosure) { \
ASSERT_TRACE2("genericClosure 1",
TypeError,
hintfmt("value is %s while a set was expected", "an integer"),
hintfmt("while evaluating the first argument passed to builtins.genericClosure"));
ASSERT_TRACE1("genericClosure {}",
TypeError,
hintfmt("attribute '%s' missing %s", "startSet", normaltxt("in the attrset passed as argument to builtins.genericClosure")));
ASSERT_TRACE2("genericClosure { startSet = 1; }",
TypeError,
hintfmt("value is %s while a list was expected", "an integer"),
hintfmt("while evaluating the 'startSet' attribute passed as argument to builtins.genericClosure"));
// Okay: "genericClosure { startSet = []; }"
ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = true; }",
TypeError,
hintfmt("value is %s while a function was expected", "a Boolean"),
hintfmt("while evaluating the 'operator' attribute passed as argument to builtins.genericClosure"));
ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: true; }",
TypeError,
hintfmt("value is %s while a list was expected", "a Boolean"),
hintfmt("while evaluating the return value of the `operator` passed to builtins.genericClosure")); // TODO: inconsistent naming
ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: [ true ]; }",
TypeError,
hintfmt("value is %s while a set was expected", "a Boolean"),
hintfmt("while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure"));
ASSERT_TRACE1("genericClosure { startSet = [{ key = 1;}]; operator = item: [ {} ]; }",
TypeError,
hintfmt("attribute '%s' missing %s", "key", normaltxt("in one of the attrsets generated by (or initially passed to) builtins.genericClosure")));
ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: [{ key = ''a''; }]; }",
EvalError,
hintfmt("cannot compare %s with %s", "a string", "an integer"),
hintfmt("while comparing the `key` attributes of two genericClosure elements"));
ASSERT_TRACE2("genericClosure { startSet = [ true ]; operator = item: [{ key = ''a''; }]; }",
TypeError,
hintfmt("value is %s while a set was expected", "a Boolean"),
hintfmt("while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure"));
}
} /* namespace nix */

View File

@@ -823,10 +823,4 @@ namespace nix {
for (const auto [n, elem] : enumerate(v.listItems()))
ASSERT_THAT(*elem, IsStringEq(expected[n]));
}
TEST_F(PrimOpTest, genericClosure_not_strict) {
// Operator should not be used when startSet is empty
auto v = eval("builtins.genericClosure { startSet = []; }");
ASSERT_THAT(v, IsListOfSize(0));
}
} /* namespace nix */

View File

@@ -89,7 +89,7 @@ class ExternalValueBase
/* Coerce the value to a string. Defaults to uncoercable, i.e. throws an
* error.
*/
virtual std::string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, std::string_view errorCtx) const;
virtual std::string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const;
/* Compare to another value of the same type. Defaults to uncomparable,
* i.e. always false.

View File

@@ -1,6 +1,7 @@
#pragma once
#include "types.hh"
#include "hash.hh"
#include <variant>

View File

@@ -159,6 +159,12 @@ std::pair<Tree, Input> Input::fetch(ref<Store> store) const
input.to_string(), *prevLastModified);
}
if (auto prevRev = getRev()) {
if (input.getRev() != prevRev)
throw Error("'rev' attribute mismatch in input '%s', expected %s",
input.to_string(), prevRev->gitRev());
}
if (auto prevRevCount = getRevCount()) {
if (input.getRevCount() != prevRevCount)
throw Error("'revCount' attribute mismatch in input '%s', expected %d",

View File

@@ -141,6 +141,7 @@ struct DownloadFileResult
StorePath storePath;
std::string etag;
std::string effectiveUrl;
std::optional<std::string> immutableUrl;
};
DownloadFileResult downloadFile(
@@ -150,7 +151,14 @@ DownloadFileResult downloadFile(
bool locked,
const Headers & headers = {});
std::pair<Tree, time_t> downloadTarball(
struct DownloadTarballResult
{
Tree tree;
time_t lastModified;
std::optional<std::string> immutableUrl;
};
DownloadTarballResult downloadTarball(
ref<Store> store,
const std::string & url,
const std::string & name,

View File

@@ -207,21 +207,21 @@ struct GitArchiveInputScheme : InputScheme
auto url = getDownloadUrl(input);
auto [tree, lastModified] = downloadTarball(store, url.url, input.getName(), true, url.headers);
auto result = downloadTarball(store, url.url, input.getName(), true, url.headers);
input.attrs.insert_or_assign("lastModified", uint64_t(lastModified));
input.attrs.insert_or_assign("lastModified", uint64_t(result.lastModified));
getCache()->add(
store,
lockedAttrs,
{
{"rev", rev->gitRev()},
{"lastModified", uint64_t(lastModified)}
{"lastModified", uint64_t(result.lastModified)}
},
tree.storePath,
result.tree.storePath,
true);
return {std::move(tree.storePath), input};
return {result.tree.storePath, input};
}
};

View File

@@ -32,7 +32,8 @@ DownloadFileResult downloadFile(
return {
.storePath = std::move(cached->storePath),
.etag = getStrAttr(cached->infoAttrs, "etag"),
.effectiveUrl = getStrAttr(cached->infoAttrs, "url")
.effectiveUrl = getStrAttr(cached->infoAttrs, "url"),
.immutableUrl = maybeGetStrAttr(cached->infoAttrs, "immutableUrl"),
};
};
@@ -55,12 +56,14 @@ DownloadFileResult downloadFile(
}
// FIXME: write to temporary file.
Attrs infoAttrs({
{"etag", res.etag},
{"url", res.effectiveUri},
});
if (res.immutableUrl)
infoAttrs.emplace("immutableUrl", *res.immutableUrl);
std::optional<StorePath> storePath;
if (res.cached) {
@@ -107,10 +110,11 @@ DownloadFileResult downloadFile(
.storePath = std::move(*storePath),
.etag = res.etag,
.effectiveUrl = res.effectiveUri,
.immutableUrl = res.immutableUrl,
};
}
std::pair<Tree, time_t> downloadTarball(
DownloadTarballResult downloadTarball(
ref<Store> store,
const std::string & url,
const std::string & name,
@@ -127,8 +131,9 @@ std::pair<Tree, time_t> downloadTarball(
if (cached && !cached->expired)
return {
Tree { .actualPath = store->toRealPath(cached->storePath), .storePath = std::move(cached->storePath) },
getIntAttr(cached->infoAttrs, "lastModified")
.tree = Tree { .actualPath = store->toRealPath(cached->storePath), .storePath = std::move(cached->storePath) },
.lastModified = (time_t) getIntAttr(cached->infoAttrs, "lastModified"),
.immutableUrl = maybeGetStrAttr(cached->infoAttrs, "immutableUrl"),
};
auto res = downloadFile(store, url, name, locked, headers);
@@ -156,6 +161,9 @@ std::pair<Tree, time_t> downloadTarball(
{"etag", res.etag},
});
if (res.immutableUrl)
infoAttrs.emplace("immutableUrl", *res.immutableUrl);
getCache()->add(
store,
inAttrs,
@@ -164,8 +172,9 @@ std::pair<Tree, time_t> downloadTarball(
locked);
return {
Tree { .actualPath = store->toRealPath(*unpackedStorePath), .storePath = std::move(*unpackedStorePath) },
lastModified,
.tree = Tree { .actualPath = store->toRealPath(*unpackedStorePath), .storePath = std::move(*unpackedStorePath) },
.lastModified = lastModified,
.immutableUrl = res.immutableUrl,
};
}
@@ -185,21 +194,33 @@ struct CurlInputScheme : InputScheme
virtual bool isValidURL(const ParsedURL & url) const = 0;
std::optional<Input> inputFromURL(const ParsedURL & url) const override
std::optional<Input> inputFromURL(const ParsedURL & _url) const override
{
if (!isValidURL(url))
if (!isValidURL(_url))
return std::nullopt;
Input input;
auto urlWithoutApplicationScheme = url;
urlWithoutApplicationScheme.scheme = parseUrlScheme(url.scheme).transport;
auto url = _url;
url.scheme = parseUrlScheme(url.scheme).transport;
input.attrs.insert_or_assign("type", inputType());
input.attrs.insert_or_assign("url", urlWithoutApplicationScheme.to_string());
auto narHash = url.query.find("narHash");
if (narHash != url.query.end())
input.attrs.insert_or_assign("narHash", narHash->second);
if (auto i = get(url.query, "rev"))
input.attrs.insert_or_assign("rev", *i);
if (auto i = get(url.query, "revCount"))
if (auto n = string2Int<uint64_t>(*i))
input.attrs.insert_or_assign("revCount", *n);
url.query.erase("rev");
url.query.erase("revCount");
input.attrs.insert_or_assign("type", inputType());
input.attrs.insert_or_assign("url", url.to_string());
return input;
}
@@ -208,7 +229,8 @@ struct CurlInputScheme : InputScheme
auto type = maybeGetStrAttr(attrs, "type");
if (type != inputType()) return {};
std::set<std::string> allowedNames = {"type", "url", "narHash", "name", "unpack"};
// FIXME: some of these only apply to TarballInputScheme.
std::set<std::string> allowedNames = {"type", "url", "narHash", "name", "unpack", "rev", "revCount"};
for (auto & [name, value] : attrs)
if (!allowedNames.count(name))
throw Error("unsupported %s input attribute '%s'", *type, name);
@@ -271,10 +293,22 @@ struct TarballInputScheme : CurlInputScheme
: hasTarballExtension(url.path));
}
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) override
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & _input) override
{
auto tree = downloadTarball(store, getStrAttr(input.attrs, "url"), input.getName(), false).first;
return {std::move(tree.storePath), input};
Input input(_input);
auto url = getStrAttr(input.attrs, "url");
auto result = downloadTarball(store, url, input.getName(), false);
if (result.immutableUrl) {
auto immutableInput = Input::fromURL(*result.immutableUrl);
// FIXME: would be nice to support arbitrary flakerefs
// here, e.g. git flakes.
if (immutableInput.getType() != "tarball")
throw Error("tarball 'Link' headers that redirect to non-tarball URLs are not supported");
input = immutableInput;
}
return {result.tree.storePath, std::move(input)};
}
};

View File

@@ -404,6 +404,8 @@ int handleExceptions(const std::string & programName, std::function<void()> fun)
return 1;
} catch (BaseError & e) {
logError(e.info());
if (e.hasTrace() && !loggerSettings.showTrace.get())
printError("(use '--show-trace' to show detailed location information)");
return e.status;
} catch (std::bad_alloc & e) {
printError(error + "out of memory");

View File

@@ -344,7 +344,7 @@ bool LocalDerivationGoal::cleanupDecideWhetherDiskFull()
for (auto & [_, status] : initialOutputs) {
if (!status.known) continue;
if (buildMode != bmCheck && status.known->isValid()) continue;
auto p = worker.store.printStorePath(status.known->path);
auto p = worker.store.toRealPath(status.known->path);
if (pathExists(chrootRootDir + p))
renameFile((chrootRootDir + p), p);
}
@@ -663,7 +663,8 @@ void LocalDerivationGoal::startBuilder()
nobody account. The latter is kind of a hack to support
Samba-in-QEMU. */
createDirs(chrootRootDir + "/etc");
chownToBuilder(chrootRootDir + "/etc");
if (parsedDrv->useUidRange())
chownToBuilder(chrootRootDir + "/etc");
if (parsedDrv->useUidRange() && (!buildUser || buildUser->getUIDCount() < 65536))
throw Error("feature 'uid-range' requires the setting '%s' to be enabled", settings.autoAllocateUids.name);
@@ -1210,10 +1211,10 @@ void LocalDerivationGoal::writeStructuredAttrs()
writeFile(tmpDir + "/.attrs.sh", rewriteStrings(jsonSh, inputRewrites));
chownToBuilder(tmpDir + "/.attrs.sh");
env["NIX_ATTRS_SH_FILE"] = tmpDir + "/.attrs.sh";
env["NIX_ATTRS_SH_FILE"] = tmpDirInSandbox + "/.attrs.sh";
writeFile(tmpDir + "/.attrs.json", rewriteStrings(json.dump(), inputRewrites));
chownToBuilder(tmpDir + "/.attrs.json");
env["NIX_ATTRS_JSON_FILE"] = tmpDir + "/.attrs.json";
env["NIX_ATTRS_JSON_FILE"] = tmpDirInSandbox + "/.attrs.json";
}
}
@@ -1907,6 +1908,10 @@ void LocalDerivationGoal::runChild()
}
}
/* Make /etc unwritable */
if (!parsedDrv->useUidRange())
chmod_(chrootRootDir + "/etc", 0555);
/* Unshare this mount namespace. This is necessary because
pivot_root() below changes the root of the mount
namespace. This means that the call to setns() in

View File

@@ -13,6 +13,7 @@
namespace nix {
class Store;
/* Abstract syntax of derivations. */

View File

@@ -183,9 +183,9 @@ struct curlFileTransfer : public FileTransfer
size_t realSize = size * nmemb;
std::string line((char *) contents, realSize);
printMsg(lvlVomit, format("got header for '%s': %s") % request.uri % trim(line));
static std::regex statusLine("HTTP/[^ ]+ +[0-9]+(.*)", std::regex::extended | std::regex::icase);
std::smatch match;
if (std::regex_match(line, match, statusLine)) {
if (std::smatch match; std::regex_match(line, match, statusLine)) {
result.etag = "";
result.data.clear();
result.bodySize = 0;
@@ -193,9 +193,11 @@ struct curlFileTransfer : public FileTransfer
acceptRanges = false;
encoding = "";
} else {
auto i = line.find(':');
if (i != std::string::npos) {
std::string name = toLower(trim(line.substr(0, i)));
if (name == "etag") {
result.etag = trim(line.substr(i + 1));
/* Hack to work around a GitHub bug: it sends
@@ -209,10 +211,22 @@ struct curlFileTransfer : public FileTransfer
debug(format("shutting down on 200 HTTP response with expected ETag"));
return 0;
}
} else if (name == "content-encoding")
}
else if (name == "content-encoding")
encoding = trim(line.substr(i + 1));
else if (name == "accept-ranges" && toLower(trim(line.substr(i + 1))) == "bytes")
acceptRanges = true;
else if (name == "link" || name == "x-amz-meta-link") {
auto value = trim(line.substr(i + 1));
static std::regex linkRegex("<([^>]*)>; rel=\"immutable\"", std::regex::extended | std::regex::icase);
if (std::smatch match; std::regex_match(value, match, linkRegex))
result.immutableUrl = match.str(1);
else
debug("got invalid link header '%s'", value);
}
}
}
return realSize;
@@ -342,7 +356,7 @@ struct curlFileTransfer : public FileTransfer
{
auto httpStatus = getHTTPStatus();
char * effectiveUriCStr;
char * effectiveUriCStr = nullptr;
curl_easy_getinfo(req, CURLINFO_EFFECTIVE_URL, &effectiveUriCStr);
if (effectiveUriCStr)
result.effectiveUri = effectiveUriCStr;

View File

@@ -79,6 +79,10 @@ struct FileTransferResult
std::string effectiveUri;
std::string data;
uint64_t bodySize = 0;
/* An "immutable" URL for this resource (i.e. one whose contents
will never change), as returned by the `Link: <url>;
rel="immutable"` header. */
std::optional<std::string> immutableUrl;
};
class Store;

View File

@@ -564,7 +564,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
/* On macOS, accepted sockets inherit the
non-blocking flag from the server socket, so
explicitly make it blocking. */
if (fcntl(fdServer.get(), F_SETFL, fcntl(fdServer.get(), F_GETFL) & ~O_NONBLOCK) == -1)
if (fcntl(fdClient.get(), F_SETFL, fcntl(fdClient.get(), F_GETFL) & ~O_NONBLOCK) == -1)
abort();
while (true) {

View File

@@ -56,7 +56,7 @@ public:
void init() override
{
// FIXME: do this lazily?
if (auto cacheInfo = diskCache->cacheExists(cacheUri)) {
if (auto cacheInfo = diskCache->upToDateCacheExists(cacheUri)) {
wantMassQuery.setDefault(cacheInfo->wantMassQuery);
priority.setDefault(cacheInfo->priority);
} else {

View File

@@ -84,11 +84,10 @@ public:
Sync<State> _state;
NarInfoDiskCacheImpl()
NarInfoDiskCacheImpl(Path dbPath = getCacheDir() + "/nix/binary-cache-v6.sqlite")
{
auto state(_state.lock());
Path dbPath = getCacheDir() + "/nix/binary-cache-v6.sqlite";
createDirs(dirOf(dbPath));
state->db = SQLite(dbPath);
@@ -98,7 +97,7 @@ public:
state->db.exec(schema);
state->insertCache.create(state->db,
"insert or replace into BinaryCaches(url, timestamp, storeDir, wantMassQuery, priority) values (?, ?, ?, ?, ?)");
"insert into BinaryCaches(url, timestamp, storeDir, wantMassQuery, priority) values (?1, ?2, ?3, ?4, ?5) on conflict (url) do update set timestamp = ?2, storeDir = ?3, wantMassQuery = ?4, priority = ?5 returning id;");
state->queryCache.create(state->db,
"select id, storeDir, wantMassQuery, priority from BinaryCaches where url = ? and timestamp > ?");
@@ -166,6 +165,8 @@ public:
return i->second;
}
private:
std::optional<Cache> queryCacheRaw(State & state, const std::string & uri)
{
auto i = state.caches.find(uri);
@@ -173,15 +174,21 @@ public:
auto queryCache(state.queryCache.use()(uri)(time(0) - cacheInfoTtl));
if (!queryCache.next())
return std::nullopt;
state.caches.emplace(uri,
Cache{(int) queryCache.getInt(0), queryCache.getStr(1), queryCache.getInt(2) != 0, (int) queryCache.getInt(3)});
auto cache = Cache {
.id = (int) queryCache.getInt(0),
.storeDir = queryCache.getStr(1),
.wantMassQuery = queryCache.getInt(2) != 0,
.priority = (int) queryCache.getInt(3),
};
state.caches.emplace(uri, cache);
}
return getCache(state, uri);
}
void createCache(const std::string & uri, const Path & storeDir, bool wantMassQuery, int priority) override
public:
int createCache(const std::string & uri, const Path & storeDir, bool wantMassQuery, int priority) override
{
retrySQLite<void>([&]() {
return retrySQLite<int>([&]() {
auto state(_state.lock());
SQLiteTxn txn(state->db);
@@ -190,17 +197,29 @@ public:
auto cache(queryCacheRaw(*state, uri));
if (cache)
return;
return cache->id;
state->insertCache.use()(uri)(time(0))(storeDir)(wantMassQuery)(priority).exec();
assert(sqlite3_changes(state->db) == 1);
state->caches[uri] = Cache{(int) sqlite3_last_insert_rowid(state->db), storeDir, wantMassQuery, priority};
Cache ret {
.id = -1, // set below
.storeDir = storeDir,
.wantMassQuery = wantMassQuery,
.priority = priority,
};
{
auto r(state->insertCache.use()(uri)(time(0))(storeDir)(wantMassQuery)(priority));
assert(r.next());
ret.id = (int) r.getInt(0);
}
state->caches[uri] = ret;
txn.commit();
return ret.id;
});
}
std::optional<CacheInfo> cacheExists(const std::string & uri) override
std::optional<CacheInfo> upToDateCacheExists(const std::string & uri) override
{
return retrySQLite<std::optional<CacheInfo>>([&]() -> std::optional<CacheInfo> {
auto state(_state.lock());
@@ -208,6 +227,7 @@ public:
if (!cache)
return std::nullopt;
return CacheInfo {
.id = cache->id,
.wantMassQuery = cache->wantMassQuery,
.priority = cache->priority
};
@@ -371,4 +391,9 @@ ref<NarInfoDiskCache> getNarInfoDiskCache()
return cache;
}
ref<NarInfoDiskCache> getTestNarInfoDiskCache(Path dbPath)
{
return make_ref<NarInfoDiskCacheImpl>(dbPath);
}
}

View File

@@ -13,16 +13,17 @@ public:
virtual ~NarInfoDiskCache() { }
virtual void createCache(const std::string & uri, const Path & storeDir,
virtual int createCache(const std::string & uri, const Path & storeDir,
bool wantMassQuery, int priority) = 0;
struct CacheInfo
{
int id;
bool wantMassQuery;
int priority;
};
virtual std::optional<CacheInfo> cacheExists(const std::string & uri) = 0;
virtual std::optional<CacheInfo> upToDateCacheExists(const std::string & uri) = 0;
virtual std::pair<Outcome, std::shared_ptr<NarInfo>> lookupNarInfo(
const std::string & uri, const std::string & hashPart) = 0;
@@ -45,4 +46,6 @@ public:
multiple threads. */
ref<NarInfoDiskCache> getNarInfoDiskCache();
ref<NarInfoDiskCache> getTestNarInfoDiskCache(Path dbPath);
}

View File

@@ -1,8 +1,10 @@
#include "util.hh"
#include "outputs-spec.hh"
#include "nlohmann/json.hpp"
#include <regex>
#include <nlohmann/json.hpp>
#include "util.hh"
#include "regex-combinators.hh"
#include "outputs-spec.hh"
#include "path-regex.hh"
namespace nix {
@@ -18,10 +20,14 @@ bool OutputsSpec::contains(const std::string & outputName) const
}, raw());
}
static std::string outputSpecRegexStr =
regex::either(
regex::group(R"(\*)"),
regex::group(regex::list(nameRegexStr)));
std::optional<OutputsSpec> OutputsSpec::parseOpt(std::string_view s)
{
static std::regex regex(R"((\*)|([a-z]+(,[a-z]+)*))");
static std::regex regex(std::string { outputSpecRegexStr });
std::smatch match;
std::string s2 { s }; // until some improves std::regex
@@ -42,7 +48,7 @@ OutputsSpec OutputsSpec::parse(std::string_view s)
{
std::optional spec = parseOpt(s);
if (!spec)
throw Error("Invalid outputs specifier: '%s'", s);
throw Error("invalid outputs specifier '%s'", s);
return *spec;
}
@@ -65,7 +71,7 @@ std::pair<std::string_view, ExtendedOutputsSpec> ExtendedOutputsSpec::parse(std:
{
std::optional spec = parseOpt(s);
if (!spec)
throw Error("Invalid extended outputs specifier: '%s'", s);
throw Error("invalid extended outputs specifier '%s'", s);
return *spec;
}
@@ -163,7 +169,7 @@ void adl_serializer<OutputsSpec>::to_json(json & json, OutputsSpec t) {
[&](const OutputsSpec::Names & names) {
json = names;
},
}, t);
}, t.raw());
}
@@ -183,7 +189,7 @@ void adl_serializer<ExtendedOutputsSpec>::to_json(json & json, ExtendedOutputsSp
[&](const ExtendedOutputsSpec::Explicit & e) {
adl_serializer<OutputsSpec>::to_json(json, e);
},
}, t);
}, t.raw());
}
}

View File

@@ -0,0 +1,7 @@
#pragma once
namespace nix {
static constexpr std::string_view nameRegexStr = R"([0-9a-zA-Z\+\-\._\?=]+)";
}

View File

@@ -8,8 +8,10 @@ static void checkName(std::string_view path, std::string_view name)
{
if (name.empty())
throw BadStorePath("store path '%s' has an empty name", path);
if (name.size() > 211)
throw BadStorePath("store path '%s' has a name longer than 211 characters", path);
if (name.size() > StorePath::MaxPathLen)
throw BadStorePath("store path '%s' has a name longer than '%d characters",
StorePath::MaxPathLen, path);
// See nameRegexStr for the definition
for (auto c : name)
if (!((c >= '0' && c <= '9')
|| (c >= 'a' && c <= 'z')

View File

@@ -5,7 +5,6 @@
namespace nix {
class Store;
struct Hash;
class StorePath
@@ -17,6 +16,8 @@ public:
/* Size of the hash part of store paths, in base-32 characters. */
constexpr static size_t HashLen = 32; // i.e. 160 bits
constexpr static size_t MaxPathLen = 211;
StorePath() = delete;
StorePath(std::string_view baseName);

View File

@@ -7,6 +7,8 @@
namespace nix {
class Store;
struct DrvOutput {
// The hash modulo of the derivation
Hash drvHash;

View File

@@ -238,7 +238,7 @@ struct S3BinaryCacheStoreImpl : virtual S3BinaryCacheStoreConfig, public virtual
void init() override
{
if (auto cacheInfo = diskCache->cacheExists(getUri())) {
if (auto cacheInfo = diskCache->upToDateCacheExists(getUri())) {
wantMassQuery.setDefault(cacheInfo->wantMassQuery);
priority.setDefault(cacheInfo->priority);
} else {

View File

@@ -41,6 +41,15 @@ SQLiteError::SQLiteError(const char *path, const char *errMsg, int errNo, int ex
throw SQLiteError(path, errMsg, err, exterr, offset, std::move(hf));
}
static void traceSQL(void * x, const char * sql)
{
// wacky delimiters:
// so that we're quite unambiguous without escaping anything
// notice instead of trace:
// so that this can be enabled without getting the firehose in our face.
notice("SQL<[%1%]>", sql);
};
SQLite::SQLite(const Path & path, bool create)
{
// useSQLiteWAL also indicates what virtual file system we need. Using
@@ -58,6 +67,11 @@ SQLite::SQLite(const Path & path, bool create)
if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK)
SQLiteError::throw_(db, "setting timeout");
if (getEnv("NIX_DEBUG_SQLITE_TRACES") == "1") {
// To debug sqlite statements; trace all of them
sqlite3_trace(db, &traceSQL, nullptr);
}
exec("pragma foreign_keys = 1");
}

View File

@@ -0,0 +1,23 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "store-api.hh"
namespace nix {
class LibStoreTest : public ::testing::Test {
public:
static void SetUpTestSuite() {
initLibStore();
}
protected:
LibStoreTest()
: store(openStore("dummy://"))
{ }
ref<Store> store;
};
} /* namespace nix */

View File

@@ -0,0 +1,122 @@
#include "nar-info-disk-cache.hh"
#include <gtest/gtest.h>
#include "sqlite.hh"
#include <sqlite3.h>
namespace nix {
TEST(NarInfoDiskCacheImpl, create_and_read) {
// This is a large single test to avoid some setup overhead.
int prio = 12345;
bool wantMassQuery = true;
Path tmpDir = createTempDir();
AutoDelete delTmpDir(tmpDir);
Path dbPath(tmpDir + "/test-narinfo-disk-cache.sqlite");
int savedId;
int barId;
SQLite db;
SQLiteStmt getIds;
{
auto cache = getTestNarInfoDiskCache(dbPath);
// Set up "background noise" and check that different caches receive different ids
{
auto bc1 = cache->createCache("https://bar", "/nix/storedir", wantMassQuery, prio);
auto bc2 = cache->createCache("https://xyz", "/nix/storedir", false, 12);
ASSERT_NE(bc1, bc2);
barId = bc1;
}
// Check that the fields are saved and returned correctly. This does not test
// the select statement yet, because of in-memory caching.
savedId = cache->createCache("http://foo", "/nix/storedir", wantMassQuery, prio);;
{
auto r = cache->upToDateCacheExists("http://foo");
ASSERT_TRUE(r);
ASSERT_EQ(r->priority, prio);
ASSERT_EQ(r->wantMassQuery, wantMassQuery);
ASSERT_EQ(savedId, r->id);
}
// We're going to pay special attention to the id field because we had a bug
// that changed it.
db = SQLite(dbPath);
getIds.create(db, "select id from BinaryCaches where url = 'http://foo'");
{
auto q(getIds.use());
ASSERT_TRUE(q.next());
ASSERT_EQ(savedId, q.getInt(0));
ASSERT_FALSE(q.next());
}
// Pretend that the caches are older, but keep one up to date, as "background noise"
db.exec("update BinaryCaches set timestamp = timestamp - 1 - 7 * 24 * 3600 where url <> 'https://xyz';");
// This shows that the in-memory cache works
{
auto r = cache->upToDateCacheExists("http://foo");
ASSERT_TRUE(r);
ASSERT_EQ(r->priority, prio);
ASSERT_EQ(r->wantMassQuery, wantMassQuery);
}
}
{
// We can't clear the in-memory cache, so we use a new cache object. This is
// more realistic anyway.
auto cache2 = getTestNarInfoDiskCache(dbPath);
{
auto r = cache2->upToDateCacheExists("http://foo");
ASSERT_FALSE(r);
}
// "Update", same data, check that the id number is reused
cache2->createCache("http://foo", "/nix/storedir", wantMassQuery, prio);
{
auto r = cache2->upToDateCacheExists("http://foo");
ASSERT_TRUE(r);
ASSERT_EQ(r->priority, prio);
ASSERT_EQ(r->wantMassQuery, wantMassQuery);
ASSERT_EQ(r->id, savedId);
}
{
auto q(getIds.use());
ASSERT_TRUE(q.next());
auto currentId = q.getInt(0);
ASSERT_FALSE(q.next());
ASSERT_EQ(currentId, savedId);
}
// Check that the fields can be modified, and the id remains the same
{
auto r0 = cache2->upToDateCacheExists("https://bar");
ASSERT_FALSE(r0);
cache2->createCache("https://bar", "/nix/storedir", !wantMassQuery, prio + 10);
auto r = cache2->upToDateCacheExists("https://bar");
ASSERT_EQ(r->wantMassQuery, !wantMassQuery);
ASSERT_EQ(r->priority, prio + 10);
ASSERT_EQ(r->id, barId);
}
// // Force update (no use case yet; we only retrieve cache metadata when stale based on timestamp)
// {
// cache2->createCache("https://bar", "/nix/storedir", wantMassQuery, prio + 20);
// auto r = cache2->upToDateCacheExists("https://bar");
// ASSERT_EQ(r->wantMassQuery, wantMassQuery);
// ASSERT_EQ(r->priority, prio + 20);
// }
}
}
}

View File

@@ -40,6 +40,20 @@ TEST(OutputsSpec, names_out) {
ASSERT_EQ(expected.to_string(), str);
}
TEST(OutputsSpec, names_underscore) {
std::string_view str = "a_b";
OutputsSpec expected = OutputsSpec::Names { "a_b" };
ASSERT_EQ(OutputsSpec::parse(str), expected);
ASSERT_EQ(expected.to_string(), str);
}
TEST(OutputsSpec, names_numberic) {
std::string_view str = "01";
OutputsSpec expected = OutputsSpec::Names { "01" };
ASSERT_EQ(OutputsSpec::parse(str), expected);
ASSERT_EQ(expected.to_string(), str);
}
TEST(OutputsSpec, names_out_bin) {
OutputsSpec expected = OutputsSpec::Names { "out", "bin" };
ASSERT_EQ(OutputsSpec::parse("out,bin"), expected);

View File

@@ -0,0 +1,66 @@
#include <regex>
#include <nlohmann/json.hpp>
#include <gtest/gtest.h>
#include "path-regex.hh"
#include "store-api.hh"
#include "libstoretests.hh"
namespace nix {
#define STORE_DIR "/nix/store/"
#define HASH_PART "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q"
class StorePathTest : public LibStoreTest
{
};
static std::regex nameRegex { std::string { nameRegexStr } };
#define TEST_DONT_PARSE(NAME, STR) \
TEST_F(StorePathTest, bad_ ## NAME) { \
std::string_view str = \
STORE_DIR HASH_PART "-" STR; \
ASSERT_THROW( \
store->parseStorePath(str), \
BadStorePath); \
std::string name { STR }; \
EXPECT_FALSE(std::regex_match(name, nameRegex)); \
}
TEST_DONT_PARSE(empty, "")
TEST_DONT_PARSE(garbage, "&*()")
TEST_DONT_PARSE(double_star, "**")
TEST_DONT_PARSE(star_first, "*,foo")
TEST_DONT_PARSE(star_second, "foo,*")
TEST_DONT_PARSE(bang, "foo!o")
#undef TEST_DONT_PARSE
#define TEST_DO_PARSE(NAME, STR) \
TEST_F(StorePathTest, good_ ## NAME) { \
std::string_view str = \
STORE_DIR HASH_PART "-" STR; \
auto p = store->parseStorePath(str); \
std::string name { p.name() }; \
EXPECT_TRUE(std::regex_match(name, nameRegex)); \
}
// 0-9 a-z A-Z + - . _ ? =
TEST_DO_PARSE(numbers, "02345")
TEST_DO_PARSE(lower_case, "foo")
TEST_DO_PARSE(upper_case, "FOO")
TEST_DO_PARSE(plus, "foo+bar")
TEST_DO_PARSE(dash, "foo-dev")
TEST_DO_PARSE(underscore, "foo_bar")
TEST_DO_PARSE(period, "foo.txt")
TEST_DO_PARSE(question_mark, "foo?why")
TEST_DO_PARSE(equals_sign, "foo=foo")
#undef TEST_DO_PARSE
}

View File

@@ -9,9 +9,9 @@ namespace nix {
const std::string nativeSystem = SYSTEM;
void BaseError::addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint, bool frame)
void BaseError::addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint)
{
err.traces.push_front(Trace { .pos = std::move(e), .hint = hint, .frame = frame });
err.traces.push_front(Trace { .pos = std::move(e), .hint = hint });
}
// c++ std::exception descendants must have a 'const char* what()' function.
@@ -200,125 +200,13 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
auto noSource = ANSI_ITALIC " (source not available)" ANSI_NORMAL "\n";
/*
* Traces
* ------
*
* The semantics of traces is a bit weird. We have only one option to
* print them and to make them verbose (--show-trace). In the code they
* are always collected, but they are not printed by default. The code
* also collects more traces when the option is on. This means that there
* is no way to print the simplified traces at all.
*
* I (layus) designed the code to attach positions to a restricted set of
* messages. This means that we have a lot of traces with no position at
* all, including most of the base error messages. For example "type
* error: found a string while a set was expected" has no position, but
* will come with several traces detailing it's precise relation to the
* closest know position. This makes erroring without printing traces
* quite useless.
*
* This is why I introduced the idea to always print a few traces on
* error. The number 3 is quite arbitrary, and was selected so as not to
* clutter the console on error. For the same reason, a trace with an
* error position takes more space, and counts as two traces towards the
* limit.
*
* The rest is truncated, unless --show-trace is passed. This preserves
* the same bad semantics of --show-trace to both show the trace and
* augment it with new data. Not too sure what is the best course of
* action.
*
* The issue is that it is fundamentally hard to provide a trace for a
* lazy language. The trace will only cover the current spine of the
* evaluation, missing things that have been evaluated before. For
* example, most type errors are hard to inspect because there is not
* trace for the faulty value. These errors should really print the faulty
* value itself.
*
* In function calls, the --show-trace flag triggers extra traces for each
* function invocation. These work as scopes, allowing to follow the
* current spine of the evaluation graph. Without that flag, the error
* trace should restrict itself to a restricted prefix of that trace,
* until the first scope. If we ever get to such a precise error
* reporting, there would be no need to add an arbitrary limit here. We
* could always print the full trace, and it would just be small without
* the flag.
*
* One idea I had is for XxxError.addTrace() to perform nothing if one
* scope has already been traced. Alternatively, we could stop here when
* we encounter such a scope instead of after an arbitrary number of
* traces. This however requires to augment traces with the notion of
* "scope".
*
* This is particularly visible in code like evalAttrs(...) where we have
* to make a decision between the two following options.
*
* ``` long traces
* inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, std::string_view errorCtx)
* {
* try {
* e->eval(*this, env, v);
* if (v.type() != nAttrs)
* throwTypeError("value is %1% while a set was expected", v);
* } catch (Error & e) {
* e.addTrace(pos, errorCtx);
* throw;
* }
* }
* ```
*
* ``` short traces
* inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, std::string_view errorCtx)
* {
* e->eval(*this, env, v);
* try {
* if (v.type() != nAttrs)
* throwTypeError("value is %1% while a set was expected", v);
* } catch (Error & e) {
* e.addTrace(pos, errorCtx);
* throw;
* }
* }
* ```
*
* The second example can be rewritten more concisely, but kept in this
* form to highlight the symmetry. The first option adds more information,
* because whatever caused an error down the line, in the generic eval
* function, will get annotated with the code location that uses and
* required it. The second option is less verbose, but does not provide
* any context at all as to where and why a failing value was required.
*
* Scopes would fix that, by adding context only when --show-trace is
* passed, and keeping the trace terse otherwise.
*
*/
// Enough indent to align with with the `... `
// prepended to each element of the trace
auto ellipsisIndent = " ";
bool frameOnly = false;
if (!einfo.traces.empty()) {
size_t count = 0;
// traces
if (showTrace && !einfo.traces.empty()) {
for (const auto & trace : einfo.traces) {
if (!showTrace && count > 3) {
oss << "\n" << ANSI_WARNING "(stack trace truncated; use '--show-trace' to show the full trace)" ANSI_NORMAL << "\n";
break;
}
if (trace.hint.str().empty()) continue;
if (frameOnly && !trace.frame) continue;
count++;
frameOnly = trace.frame;
oss << "\n" << "" << trace.hint.str() << "\n";
if (trace.pos) {
count++;
oss << "\n" << ellipsisIndent << ANSI_BLUE << "at " ANSI_WARNING << *trace.pos << ANSI_NORMAL << ":";
oss << "\n" << ANSI_BLUE << "at " ANSI_WARNING << *trace.pos << ANSI_NORMAL << ":";
if (auto loc = trace.pos->getCodeLines()) {
oss << "\n";

View File

@@ -86,7 +86,6 @@ void printCodeLines(std::ostream & out,
struct Trace {
std::shared_ptr<AbstractPos> pos;
hintformat hint;
bool frame;
};
struct ErrorInfo {
@@ -115,8 +114,6 @@ protected:
public:
unsigned int status = 1; // exit status
BaseError(const BaseError &) = default;
template<typename... Args>
BaseError(unsigned int status, const Args & ... args)
: err { .level = lvlError, .msg = hintfmt(args...) }
@@ -155,22 +152,15 @@ public:
const std::string & msg() const { return calcWhat(); }
const ErrorInfo & info() const { calcWhat(); return err; }
void pushTrace(Trace trace)
{
err.traces.push_front(trace);
}
template<typename... Args>
void addTrace(std::shared_ptr<AbstractPos> && e, std::string_view fs, const Args & ... args)
void addTrace(std::shared_ptr<AbstractPos> && e, const std::string & fs, const Args & ... args)
{
addTrace(std::move(e), hintfmt(std::string(fs), args...));
addTrace(std::move(e), hintfmt(fs, args...));
}
void addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint, bool frame = false);
void addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint);
bool hasTrace() const { return !err.traces.empty(); }
const ErrorInfo & info() { return err; };
};
#define MakeError(newClass, superClass) \

View File

@@ -0,0 +1,30 @@
#pragma once
#include <string_view>
namespace nix::regex {
// TODO use constexpr string building like
// https://github.com/akrzemi1/static_string/blob/master/include/ak_toolkit/static_string.hpp
static inline std::string either(std::string_view a, std::string_view b)
{
return std::string { a } + "|" + b;
}
static inline std::string group(std::string_view a)
{
return std::string { "(" } + a + ")";
}
static inline std::string many(std::string_view a)
{
return std::string { "(?:" } + a + ")*";
}
static inline std::string list(std::string_view a)
{
return std::string { a } + many(group("," + a));
}
}

View File

@@ -186,6 +186,22 @@ static DefaultStackAllocator defaultAllocatorSingleton;
StackAllocator *StackAllocator::defaultAllocator = &defaultAllocatorSingleton;
std::shared_ptr<void> (*create_coro_gc_hook)() = []() -> std::shared_ptr<void> {
return {};
};
/* This class is used for entry and exit hooks on coroutines */
class CoroutineContext {
/* Disable GC when entering the coroutine without the boehm patch,
* since it doesn't find the main thread stack in this case.
* std::shared_ptr<void> performs type-erasure, so it will call the right
* deleter. */
const std::shared_ptr<void> coro_gc_hook = create_coro_gc_hook();
public:
CoroutineContext() {};
~CoroutineContext() {};
};
std::unique_ptr<FinishSink> sourceToSink(std::function<void(Source &)> fun)
{
struct SourceToSink : FinishSink
@@ -206,7 +222,8 @@ std::unique_ptr<FinishSink> sourceToSink(std::function<void(Source &)> fun)
if (in.empty()) return;
cur = in;
if (!coro)
if (!coro) {
CoroutineContext ctx;
coro = coro_t::push_type(VirtualStackAllocator{}, [&](coro_t::pull_type & yield) {
LambdaSource source([&](char *out, size_t out_len) {
if (cur.empty()) {
@@ -223,17 +240,24 @@ std::unique_ptr<FinishSink> sourceToSink(std::function<void(Source &)> fun)
});
fun(source);
});
}
if (!*coro) { abort(); }
if (!cur.empty()) (*coro)(false);
if (!cur.empty()) {
CoroutineContext ctx;
(*coro)(false);
}
}
void finish() override
{
if (!coro) return;
if (!*coro) abort();
(*coro)(true);
{
CoroutineContext ctx;
(*coro)(true);
}
if (*coro) abort();
}
};
@@ -264,18 +288,23 @@ std::unique_ptr<Source> sinkToSource(
size_t read(char * data, size_t len) override
{
if (!coro)
if (!coro) {
CoroutineContext ctx;
coro = coro_t::pull_type(VirtualStackAllocator{}, [&](coro_t::push_type & yield) {
LambdaSink sink([&](std::string_view data) {
if (!data.empty()) yield(std::string(data));
});
fun(sink);
});
}
if (!*coro) { eof(); abort(); }
if (pos == cur.size()) {
if (!cur.empty()) (*coro)();
if (!cur.empty()) {
CoroutineContext ctx;
(*coro)();
}
cur = coro->get();
pos = 0;
}

View File

@@ -501,4 +501,10 @@ struct StackAllocator {
static StackAllocator *defaultAllocator;
};
/* Disabling GC when entering a coroutine (without the boehm patch).
mutable to avoid boehm gc dependency in libutil.
*/
extern std::shared_ptr<void> (*create_coro_gc_hook)();
}

View File

@@ -1736,6 +1736,7 @@ void setStackSize(size_t stackSize)
#if __linux__
static AutoCloseFD fdSavedMountNamespace;
static AutoCloseFD fdSavedRoot;
#endif
void saveMountNamespace()
@@ -1743,10 +1744,11 @@ void saveMountNamespace()
#if __linux__
static std::once_flag done;
std::call_once(done, []() {
AutoCloseFD fd = open("/proc/self/ns/mnt", O_RDONLY);
if (!fd)
fdSavedMountNamespace = open("/proc/self/ns/mnt", O_RDONLY);
if (!fdSavedMountNamespace)
throw SysError("saving parent mount namespace");
fdSavedMountNamespace = std::move(fd);
fdSavedRoot = open("/proc/self/root", O_RDONLY);
});
#endif
}
@@ -1759,9 +1761,16 @@ void restoreMountNamespace()
if (fdSavedMountNamespace && setns(fdSavedMountNamespace.get(), CLONE_NEWNS) == -1)
throw SysError("restoring parent mount namespace");
if (chdir(savedCwd.c_str()) == -1) {
throw SysError("restoring cwd");
if (fdSavedRoot) {
if (fchdir(fdSavedRoot.get()))
throw SysError("chdir into saved root");
if (chroot("."))
throw SysError("chroot into saved root");
}
if (chdir(savedCwd.c_str()) == -1)
throw SysError("restoring cwd");
} catch (Error & e) {
debug(e.msg());
}

View File

@@ -134,9 +134,9 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
state.forceValue(topLevel, [&]() { return topLevel.determinePos(noPos); });
PathSet context;
Attr & aDrvPath(*topLevel.attrs->find(state.sDrvPath));
auto topLevelDrv = state.coerceToStorePath(aDrvPath.pos, *aDrvPath.value, context, "");
auto topLevelDrv = state.coerceToStorePath(aDrvPath.pos, *aDrvPath.value, context);
Attr & aOutPath(*topLevel.attrs->find(state.sOutPath));
auto topLevelOut = state.coerceToStorePath(aOutPath.pos, *aOutPath.value, context, "");
auto topLevelOut = state.coerceToStorePath(aOutPath.pos, *aOutPath.value, context);
/* Realise the resulting store expression. */
debug("building user environment");

View File

@@ -97,13 +97,13 @@ struct CmdBundle : InstallableCommand
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
PathSet context2;
auto drvPath = evalState->coerceToStorePath(attr1->pos, *attr1->value, context2, "");
auto drvPath = evalState->coerceToStorePath(attr1->pos, *attr1->value, context2);
auto attr2 = vRes->attrs->get(evalState->sOutPath);
if (!attr2)
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
auto outPath = evalState->coerceToStorePath(attr2->pos, *attr2->value, context2, "");
auto outPath = evalState->coerceToStorePath(attr2->pos, *attr2->value, context2);
store->buildPaths({
DerivedPath::Built {
@@ -118,7 +118,7 @@ struct CmdBundle : InstallableCommand
auto * attr = vRes->attrs->get(evalState->sName);
if (!attr)
throw Error("attribute 'name' missing");
outLink = evalState->forceStringNoCtx(*attr->value, attr->pos, "");
outLink = evalState->forceStringNoCtx(*attr->value, attr->pos);
}
// TODO: will crash if not a localFSStore?

View File

@@ -112,7 +112,7 @@ struct CmdEval : MixJSON, InstallableCommand
else if (raw) {
stopProgressBar();
std::cout << *state->coerceToString(noPos, *v, context, "while generating the eval command output");
std::cout << *state->coerceToString(noPos, *v, context);
}
else if (json) {

View File

@@ -126,12 +126,12 @@ static void enumerateOutputs(EvalState & state, Value & vFlake,
std::function<void(const std::string & name, Value & vProvide, const PosIdx pos)> callback)
{
auto pos = vFlake.determinePos(noPos);
state.forceAttrs(vFlake, pos, "while evaluating a flake to get its outputs");
state.forceAttrs(vFlake, pos);
auto aOutputs = vFlake.attrs->get(state.symbols.create("outputs"));
assert(aOutputs);
state.forceAttrs(*aOutputs->value, pos, "while evaluating the outputs of a flake");
state.forceAttrs(*aOutputs->value, pos);
auto sHydraJobs = state.symbols.create("hydraJobs");
@@ -391,13 +391,13 @@ struct CmdFlakeCheck : FlakeCommand
checkHydraJobs = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
try {
state->forceAttrs(v, pos, "");
state->forceAttrs(v, pos);
if (state->isDerivation(v))
throw Error("jobset should not be a derivation at top-level");
for (auto & attr : *v.attrs) {
state->forceAttrs(*attr.value, attr.pos, "");
state->forceAttrs(*attr.value, attr.pos);
auto attrPath2 = concatStrings(attrPath, ".", state->symbols[attr.name]);
if (state->isDerivation(*attr.value)) {
Activity act(*logger, lvlChatty, actUnknown,
@@ -419,7 +419,7 @@ struct CmdFlakeCheck : FlakeCommand
fmt("checking NixOS configuration '%s'", attrPath));
Bindings & bindings(*state->allocBindings(0));
auto vToplevel = findAlongAttrPath(*state, "config.system.build.toplevel", bindings, v).first;
state->forceValue(*vToplevel, pos);
state->forceAttrs(*vToplevel, pos);
if (!state->isDerivation(*vToplevel))
throw Error("attribute 'config.system.build.toplevel' is not a derivation");
} catch (Error & e) {
@@ -433,12 +433,12 @@ struct CmdFlakeCheck : FlakeCommand
Activity act(*logger, lvlChatty, actUnknown,
fmt("checking template '%s'", attrPath));
state->forceAttrs(v, pos, "");
state->forceAttrs(v, pos);
if (auto attr = v.attrs->get(state->symbols.create("path"))) {
if (attr->name == state->symbols.create("path")) {
PathSet context;
auto path = state->coerceToPath(attr->pos, *attr->value, context, "");
auto path = state->coerceToPath(attr->pos, *attr->value, context);
if (!store->isInStore(path))
throw Error("template '%s' has a bad 'path' attribute");
// TODO: recursively check the flake in 'path'.
@@ -447,7 +447,7 @@ struct CmdFlakeCheck : FlakeCommand
throw Error("template '%s' lacks attribute 'path'", attrPath);
if (auto attr = v.attrs->get(state->symbols.create("description")))
state->forceStringNoCtx(*attr->value, attr->pos, "");
state->forceStringNoCtx(*attr->value, attr->pos);
else
throw Error("template '%s' lacks attribute 'description'", attrPath);
@@ -504,11 +504,11 @@ struct CmdFlakeCheck : FlakeCommand
warn("flake output attribute '%s' is deprecated; use '%s' instead", name, replacement);
if (name == "checks") {
state->forceAttrs(vOutput, pos, "");
state->forceAttrs(vOutput, pos);
for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
state->forceAttrs(*attr.value, attr.pos, "");
state->forceAttrs(*attr.value, attr.pos);
for (auto & attr2 : *attr.value->attrs) {
auto drvPath = checkDerivation(
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
@@ -524,7 +524,7 @@ struct CmdFlakeCheck : FlakeCommand
}
else if (name == "formatter") {
state->forceAttrs(vOutput, pos, "");
state->forceAttrs(vOutput, pos);
for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
@@ -535,11 +535,11 @@ struct CmdFlakeCheck : FlakeCommand
}
else if (name == "packages" || name == "devShells") {
state->forceAttrs(vOutput, pos, "");
state->forceAttrs(vOutput, pos);
for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
state->forceAttrs(*attr.value, attr.pos, "");
state->forceAttrs(*attr.value, attr.pos);
for (auto & attr2 : *attr.value->attrs)
checkDerivation(
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
@@ -548,11 +548,11 @@ struct CmdFlakeCheck : FlakeCommand
}
else if (name == "apps") {
state->forceAttrs(vOutput, pos, "");
state->forceAttrs(vOutput, pos);
for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
state->forceAttrs(*attr.value, attr.pos, "");
state->forceAttrs(*attr.value, attr.pos);
for (auto & attr2 : *attr.value->attrs)
checkApp(
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
@@ -561,7 +561,7 @@ struct CmdFlakeCheck : FlakeCommand
}
else if (name == "defaultPackage" || name == "devShell") {
state->forceAttrs(vOutput, pos, "");
state->forceAttrs(vOutput, pos);
for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
@@ -572,7 +572,7 @@ struct CmdFlakeCheck : FlakeCommand
}
else if (name == "defaultApp") {
state->forceAttrs(vOutput, pos, "");
state->forceAttrs(vOutput, pos);
for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
@@ -583,7 +583,7 @@ struct CmdFlakeCheck : FlakeCommand
}
else if (name == "legacyPackages") {
state->forceAttrs(vOutput, pos, "");
state->forceAttrs(vOutput, pos);
for (auto & attr : *vOutput.attrs) {
checkSystemName(state->symbols[attr.name], attr.pos);
// FIXME: do getDerivations?
@@ -594,7 +594,7 @@ struct CmdFlakeCheck : FlakeCommand
checkOverlay(name, vOutput, pos);
else if (name == "overlays") {
state->forceAttrs(vOutput, pos, "");
state->forceAttrs(vOutput, pos);
for (auto & attr : *vOutput.attrs)
checkOverlay(fmt("%s.%s", name, state->symbols[attr.name]),
*attr.value, attr.pos);
@@ -604,14 +604,14 @@ struct CmdFlakeCheck : FlakeCommand
checkModule(name, vOutput, pos);
else if (name == "nixosModules") {
state->forceAttrs(vOutput, pos, "");
state->forceAttrs(vOutput, pos);
for (auto & attr : *vOutput.attrs)
checkModule(fmt("%s.%s", name, state->symbols[attr.name]),
*attr.value, attr.pos);
}
else if (name == "nixosConfigurations") {
state->forceAttrs(vOutput, pos, "");
state->forceAttrs(vOutput, pos);
for (auto & attr : *vOutput.attrs)
checkNixOSConfiguration(fmt("%s.%s", name, state->symbols[attr.name]),
*attr.value, attr.pos);
@@ -624,14 +624,14 @@ struct CmdFlakeCheck : FlakeCommand
checkTemplate(name, vOutput, pos);
else if (name == "templates") {
state->forceAttrs(vOutput, pos, "");
state->forceAttrs(vOutput, pos);
for (auto & attr : *vOutput.attrs)
checkTemplate(fmt("%s.%s", name, state->symbols[attr.name]),
*attr.value, attr.pos);
}
else if (name == "defaultBundler") {
state->forceAttrs(vOutput, pos, "");
state->forceAttrs(vOutput, pos);
for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
@@ -642,11 +642,11 @@ struct CmdFlakeCheck : FlakeCommand
}
else if (name == "bundlers") {
state->forceAttrs(vOutput, pos, "");
state->forceAttrs(vOutput, pos);
for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
state->forceAttrs(*attr.value, attr.pos, "");
state->forceAttrs(*attr.value, attr.pos);
for (auto & attr2 : *attr.value->attrs) {
checkBundler(
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),

View File

@@ -199,7 +199,7 @@ static void showHelp(std::vector<std::string> subcommand, MultiCommand & topleve
if (!attr)
throw UsageError("Nix has no subcommand '%s'", concatStringsSep("", subcommand));
auto markdown = state.forceString(*attr->value, noPos, "while evaluating the lowdown help text");
auto markdown = state.forceString(*attr->value);
RunPager pager;
std::cout << renderMarkdownToTerminal(markdown) << "\n";

View File

@@ -28,17 +28,17 @@ std::string resolveMirrorUrl(EvalState & state, const std::string & url)
Value vMirrors;
// FIXME: use nixpkgs flake
state.eval(state.parseExprFromString("import <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>", "."), vMirrors);
state.forceAttrs(vMirrors, noPos, "while evaluating the set of all mirrors");
state.forceAttrs(vMirrors, noPos);
auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName));
if (mirrorList == vMirrors.attrs->end())
throw Error("unknown mirror name '%s'", mirrorName);
state.forceList(*mirrorList->value, noPos, "while evaluating one mirror configuration");
state.forceList(*mirrorList->value, noPos);
if (mirrorList->value->listSize() < 1)
throw Error("mirror URL '%s' did not expand to anything", url);
std::string mirror(state.forceString(*mirrorList->value->listElems()[0], noPos, "while evaluating the first available mirror"));
std::string mirror(state.forceString(*mirrorList->value->listElems()[0]));
return mirror + (hasSuffix(mirror, "/") ? "" : "/") + s.substr(p + 1);
}
@@ -196,29 +196,29 @@ static int main_nix_prefetch_url(int argc, char * * argv)
Value vRoot;
state->evalFile(path, vRoot);
Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first);
state->forceAttrs(v, noPos, "while evaluating the source attribute to prefetch");
state->forceAttrs(v, noPos);
/* Extract the URL. */
auto * attr = v.attrs->get(state->symbols.create("urls"));
if (!attr)
throw Error("attribute 'urls' missing");
state->forceList(*attr->value, noPos, "while evaluating the urls to prefetch");
state->forceList(*attr->value, noPos);
if (attr->value->listSize() < 1)
throw Error("'urls' list is empty");
url = state->forceString(*attr->value->listElems()[0], noPos, "while evaluating the first url from the urls list");
url = state->forceString(*attr->value->listElems()[0]);
/* Extract the hash mode. */
auto attr2 = v.attrs->get(state->symbols.create("outputHashMode"));
if (!attr2)
printInfo("warning: this does not look like a fetchurl call");
else
unpack = state->forceString(*attr2->value, noPos, "while evaluating the outputHashMode of the source to prefetch") == "recursive";
unpack = state->forceString(*attr2->value) == "recursive";
/* Extract the name. */
if (!name) {
auto attr3 = v.attrs->get(state->symbols.create("name"));
if (!attr3)
name = state->forceString(*attr3->value, noPos, "while evaluating the name of the source to prefetch");
name = state->forceString(*attr3->value);
}
}

View File

@@ -144,7 +144,7 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
Bindings & bindings(*state->allocBindings(0));
auto v2 = findAlongAttrPath(*state, settings.thisSystem, bindings, *v).first;
return store->parseStorePath(state->forceString(*v2, noPos, "while evaluating the path tho latest nix version"));
return store->parseStorePath(state->forceString(*v2));
}
};

View File

@@ -42,20 +42,21 @@ nix build -f multiple-outputs.nix --json 'a^*' --no-link | jq --exit-status '
nix build -f multiple-outputs.nix --json e --no-link | jq --exit-status '
(.[0] |
(.drvPath | match(".*multiple-outputs-e.drv")) and
(.outputs | keys == ["a", "b"]))
(.outputs | keys == ["a_a", "b"]))
'
# But not when it's overriden.
nix build -f multiple-outputs.nix --json e^a --no-link | jq --exit-status '
nix build -f multiple-outputs.nix --json e^a_a --no-link
nix build -f multiple-outputs.nix --json e^a_a --no-link | jq --exit-status '
(.[0] |
(.drvPath | match(".*multiple-outputs-e.drv")) and
(.outputs | keys == ["a"]))
(.outputs | keys == ["a_a"]))
'
nix build -f multiple-outputs.nix --json 'e^*' --no-link | jq --exit-status '
(.[0] |
(.drvPath | match(".*multiple-outputs-e.drv")) and
(.outputs | keys == ["a", "b", "c"]))
(.outputs | keys == ["a_a", "b", "c"]))
'
# Test building from raw store path to drv not expression.
@@ -88,7 +89,7 @@ nix build "$drv^first,second" --no-link --json | jq --exit-status '
(.outputs |
(keys | length == 2) and
(.first | match(".*multiple-outputs-a-first")) and
(.second | match(".*multiple-outputs-a-second"))))
(.second | match(".*multiple-outputs-a-second"))))
'
nix build "$drv^*" --no-link --json | jq --exit-status '
@@ -97,14 +98,14 @@ nix build "$drv^*" --no-link --json | jq --exit-status '
(.outputs |
(keys | length == 2) and
(.first | match(".*multiple-outputs-a-first")) and
(.second | match(".*multiple-outputs-a-second"))))
(.second | match(".*multiple-outputs-a-second"))))
'
# Make sure that `--impure` works (regression test for https://github.com/NixOS/nix/issues/6488)
nix build --impure -f multiple-outputs.nix --json e --no-link | jq --exit-status '
(.[0] |
(.drvPath | match(".*multiple-outputs-e.drv")) and
(.outputs | keys == ["a", "b"]))
(.outputs | keys == ["a_a", "b"]))
'
testNormalization () {

View File

@@ -1 +1 @@
true
[ true true true true true true ]

View File

@@ -18,7 +18,24 @@ let
};
};
legit-context = builtins.getContext "${path}${drv.outPath}${drv.foo.outPath}${drv.drvPath}";
combo-path = "${path}${drv.outPath}${drv.foo.outPath}${drv.drvPath}";
legit-context = builtins.getContext combo-path;
constructed-context = builtins.getContext (builtins.appendContext "" desired-context);
in legit-context == constructed-context
reconstructed-path = builtins.appendContext
(builtins.unsafeDiscardStringContext combo-path)
desired-context;
# Eta rule for strings with context.
etaRule = str:
str == builtins.appendContext
(builtins.unsafeDiscardStringContext str)
(builtins.getContext str);
in [
(legit-context == desired-context)
(reconstructed-path == combo-path)
(etaRule "foo")
(etaRule drv.drvPath)
(etaRule drv.foo.outPath)
(etaRule (builtins.unsafeDiscardOutputDependency drv.drvPath))
]

View File

@@ -1 +1 @@
{ bar = "regular"; foo = "directory"; }
{ bar = "regular"; foo = "directory"; ldir = "symlink"; linked = "symlink"; }

View File

@@ -0,0 +1 @@
{ bar = "regular"; foo = "directory"; ldir = "symlink"; linked = "symlink"; }

View File

@@ -0,0 +1,6 @@
{
bar = builtins.readFileType ./readDir/bar;
foo = builtins.readFileType ./readDir/foo;
linked = builtins.readFileType ./readDir/linked;
ldir = builtins.readFileType ./readDir/ldir;
}

1
tests/lang/readDir/ldir Symbolic link
View File

@@ -0,0 +1 @@
foo

1
tests/lang/readDir/linked Symbolic link
View File

@@ -0,0 +1 @@
foo/git-hates-directories

View File

@@ -37,3 +37,6 @@ nix-build check.nix -A nondeterministic --sandbox-paths /nix/store --no-out-link
(! nix-build check.nix -A nondeterministic --sandbox-paths /nix/store --no-out-link --check -K 2> $TEST_ROOT/log)
if grep -q 'error: renaming' $TEST_ROOT/log; then false; fi
grep -q 'may not be deterministic' $TEST_ROOT/log
# Test that sandboxed builds cannot write to /etc easily
(! nix-build -E 'with import ./config.nix; mkDerivation { name = "etc-write"; buildCommand = "echo > /etc/test"; }' --no-out-link --sandbox-paths /nix/store)

View File

@@ -91,9 +91,9 @@ rec {
e = mkDerivation {
name = "multiple-outputs-e";
outputs = [ "a" "b" "c" ];
meta.outputsToInstall = [ "a" "b" ];
buildCommand = "mkdir $a $b $c";
outputs = [ "a_a" "b" "c" ];
meta.outputsToInstall = [ "a_a" "b" ];
buildCommand = "mkdir $a_a $b $c";
};
independent = mkDerivation {
@@ -117,4 +117,14 @@ rec {
'';
};
invalid-output-name-1 = mkDerivation {
name = "invalid-output-name-1";
outputs = [ "out/"];
};
invalid-output-name-2 = mkDerivation {
name = "invalid-output-name-2";
outputs = [ "x" "foo$"];
};
}

View File

@@ -83,3 +83,6 @@ nix-store --gc --keep-derivations --keep-outputs
nix-store --gc --print-roots
rm -rf $NIX_STORE_DIR/.links
rmdir $NIX_STORE_DIR
nix build -f multiple-outputs.nix invalid-output-name-1 2>&1 | grep 'contains illegal character'
nix build -f multiple-outputs.nix invalid-output-name-2 2>&1 | grep 'contains illegal character'

View File

@@ -56,12 +56,12 @@ runCommand "test"
# Make /run a tmpfs to shut up a systemd warning.
mkdir /run
mount -t tmpfs none /run
chmod 0700 /run
mount -t cgroup2 none /sys/fs/cgroup
mkdir -p $out
chmod +w /etc
touch /etc/os-release
echo a5ea3f98dedc0278b6f3cc8c37eeaeac > /etc/machine-id