mirror of
https://github.com/NixOS/nix
synced 2025-07-03 18:41:47 +02:00
Merge remote-tracking branch 'origin/master' into lazy-trees
This commit is contained in:
commit
3621d0769d
97 changed files with 3007 additions and 727 deletions
28
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
28
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Motivation
|
||||
<!-- Briefly explain what the change is about and why it is desirable. -->
|
||||
|
||||
# Context
|
||||
<!-- Provide context. Reference open issues if available. -->
|
||||
|
||||
<!-- Non-trivial change: Briefly outline the implementation strategy. -->
|
||||
|
||||
<!-- Invasive change: Discuss alternative designs or approaches you considered. -->
|
||||
|
||||
<!-- Large change: Provide instructions to reviewers how to read the diff. -->
|
||||
|
||||
# Checklist for maintainers
|
||||
|
||||
<!-- Contributors: please leave this as is -->
|
||||
|
||||
Maintainers: tick if completed or explain if not relevant
|
||||
|
||||
- [ ] agreed on idea
|
||||
- [ ] agreed on implementation strategy
|
||||
- [ ] tests, as appropriate
|
||||
- functional tests - `tests/**.sh`
|
||||
- unit tests - `src/*/tests`
|
||||
- integration tests
|
||||
- [ ] documentation in the manual
|
||||
- [ ] code and comments are self-explanatory
|
||||
- [ ] commit message explains why the change was made
|
||||
- [ ] new feature or bug fix: updated release notes
|
2
.github/workflows/backport.yml
vendored
2
.github/workflows/backport.yml
vendored
|
@ -21,7 +21,7 @@ jobs:
|
|||
fetch-depth: 0
|
||||
- name: Create backport PRs
|
||||
# should be kept in sync with `version`
|
||||
uses: zeebe-io/backport-action@v1.0.1
|
||||
uses: zeebe-io/backport-action@v1.1.0
|
||||
with:
|
||||
# Config README: https://github.com/zeebe-io/backport-action#backport-action
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
2
.version
2
.version
|
@ -1 +1 @@
|
|||
2.13.0
|
||||
2.14.0
|
77
boehmgc-coroutine-sp-fallback.diff
Normal file
77
boehmgc-coroutine-sp-fallback.diff
Normal file
|
@ -0,0 +1,77 @@
|
|||
diff --git a/darwin_stop_world.c b/darwin_stop_world.c
|
||||
index 3dbaa3fb..36a1d1f7 100644
|
||||
--- a/darwin_stop_world.c
|
||||
+++ b/darwin_stop_world.c
|
||||
@@ -352,6 +352,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();
|
||||
|
||||
@@ -407,6 +408,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/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
|
|
@ -277,6 +277,12 @@ fi
|
|||
PKG_CHECK_MODULES([GTEST], [gtest_main])
|
||||
|
||||
|
||||
# Look for rapidcheck.
|
||||
# No pkg-config yet, https://github.com/emil-e/rapidcheck/issues/302
|
||||
AC_CHECK_HEADERS([rapidcheck/gtest.h], [], [], [#include <gtest/gtest.h>])
|
||||
AC_CHECK_LIB([rapidcheck], [])
|
||||
|
||||
|
||||
# Look for nlohmann/json.
|
||||
PKG_CHECK_MODULES([NLOHMANN_JSON], [nlohmann_json >= 3.9])
|
||||
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
- [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)
|
||||
- [Release 2.10 (2022-07-11)](release-notes/rl-2.10.md)
|
||||
|
|
|
@ -66,11 +66,11 @@ The operation `--realise` essentially “builds” the specified store
|
|||
paths. Realisation is a somewhat overloaded term:
|
||||
|
||||
- If the store path is a *derivation*, realisation ensures that the
|
||||
output paths of the derivation are [valid](../glossary.md) (i.e.,
|
||||
output paths of the derivation are [valid] (i.e.,
|
||||
the output path and its closure exist in the file system). This
|
||||
can be done in several ways. First, it is possible that the
|
||||
outputs are already valid, in which case we are done
|
||||
immediately. Otherwise, there may be [substitutes](../glossary.md)
|
||||
immediately. Otherwise, there may be [substitutes]
|
||||
that produce the outputs (e.g., by downloading them). Finally, the
|
||||
outputs can be produced by running the build task described
|
||||
by the derivation.
|
||||
|
@ -82,6 +82,9 @@ paths. Realisation is a somewhat overloaded term:
|
|||
produced through substitutes. If there are no (successful)
|
||||
substitutes, realisation fails.
|
||||
|
||||
[valid]: ../glossary.md#gloss-validity
|
||||
[substitutes]: ../glossary.md#gloss-substitute
|
||||
|
||||
The output path of each derivation is printed on standard output. (For
|
||||
non-derivations argument, the argument itself is printed.)
|
||||
|
||||
|
@ -295,8 +298,8 @@ error: cannot delete path `/nix/store/zq0h41l75vlb4z45kzgjjmsjxvcv1qk7-mesa-6.4'
|
|||
|
||||
## Description
|
||||
|
||||
The operation `--query` displays various bits of information about the
|
||||
store paths . The queries are described below. At most one query can be
|
||||
The operation `--query` displays information about [store path]s.
|
||||
The queries are described below. At most one query can be
|
||||
specified. The default query is `--outputs`.
|
||||
|
||||
The paths *paths* may also be symlinks from outside of the Nix store, to
|
||||
|
@ -316,12 +319,12 @@ symlink.
|
|||
## Queries
|
||||
|
||||
- `--outputs`\
|
||||
Prints out the [output paths](../glossary.md) of the store
|
||||
Prints out the [output path]s of the store
|
||||
derivations *paths*. These are the paths that will be produced when
|
||||
the derivation is built.
|
||||
|
||||
- `--requisites`; `-R`\
|
||||
Prints out the [closure](../glossary.md) of the store path *paths*.
|
||||
Prints out the [closure] of the given *paths*.
|
||||
|
||||
This query has one option:
|
||||
|
||||
|
@ -338,10 +341,12 @@ symlink.
|
|||
derivation and specifying the option `--include-outputs`.
|
||||
|
||||
- `--references`\
|
||||
Prints the set of [references](../glossary.md) of the store paths
|
||||
Prints the set of [references]s of the store paths
|
||||
*paths*, that is, their immediate dependencies. (For *all*
|
||||
dependencies, use `--requisites`.)
|
||||
|
||||
[reference]: ../glossary.md#gloss-reference
|
||||
|
||||
- `--referrers`\
|
||||
Prints the set of *referrers* of the store paths *paths*, that is,
|
||||
the store paths currently existing in the Nix store that refer to
|
||||
|
@ -356,11 +361,13 @@ symlink.
|
|||
in the Nix store that are dependent on *paths*.
|
||||
|
||||
- `--deriver`; `-d`\
|
||||
Prints the [deriver](../glossary.md) of the store paths *paths*. If
|
||||
Prints the [deriver] of the store paths *paths*. If
|
||||
the path has no deriver (e.g., if it is a source file), or if the
|
||||
deriver is not known (e.g., in the case of a binary-only
|
||||
deployment), the string `unknown-deriver` is printed.
|
||||
|
||||
[deriver]: ../glossary.md#gloss-deriver
|
||||
|
||||
- `--graph`\
|
||||
Prints the references graph of the store paths *paths* in the format
|
||||
of the `dot` tool of AT\&T's [Graphviz
|
||||
|
|
|
@ -92,7 +92,8 @@ $ nix develop
|
|||
|
||||
The unit-tests for each Nix library (`libexpr`, `libstore`, etc..) are defined
|
||||
under `src/{library_name}/tests` using the
|
||||
[googletest](https://google.github.io/googletest/) framework.
|
||||
[googletest](https://google.github.io/googletest/) and
|
||||
[rapidcheck](https://github.com/emil-e/rapidcheck) frameworks.
|
||||
|
||||
You can run the whole testsuite with `make check`, or the tests for a specific component with `make libfoo-tests_RUN`. Finer-grained filtering is also possible using the [--gtest_filter](https://google.github.io/googletest/advanced.html#running-a-subset-of-the-tests) command-line option.
|
||||
|
||||
|
|
|
@ -19,6 +19,17 @@
|
|||
|
||||
[store derivation]: #gloss-store-derivation
|
||||
|
||||
- [realise]{#gloss-realise}, realisation\
|
||||
Ensure a [store path] is [valid][validity].
|
||||
|
||||
This means either running the `builder` executable as specified in the corresponding [derivation] or fetching a pre-built [store object] from a [substituter].
|
||||
|
||||
See [`nix-build`](./command-ref/nix-build.md) and [`nix-store --realise`](./command-ref/nix-store.md#operation---realise).
|
||||
|
||||
See [`nix build`](./command-ref/new-cli/nix3-build.md) (experimental).
|
||||
|
||||
[realise]: #gloss-realise
|
||||
|
||||
- [content-addressed derivation]{#gloss-content-addressed-derivation}\
|
||||
A derivation which has the
|
||||
[`__contentAddressed`](./language/advanced-attributes.md#adv-attr-__contentAddressed)
|
||||
|
@ -101,6 +112,8 @@
|
|||
copy store objects it doesn't have. For details, see the
|
||||
[`substituters` option](./command-ref/conf-file.md#conf-substituters).
|
||||
|
||||
[substituter]: #gloss-substituter
|
||||
|
||||
- [purity]{#gloss-purity}\
|
||||
The assumption that equal Nix derivations when run always produce
|
||||
the same output. This cannot be guaranteed in general (e.g., a
|
||||
|
@ -143,19 +156,25 @@
|
|||
to path `Q`, then `Q` is in the closure of `P`. Further, if `Q`
|
||||
references `R` then `R` is also in the closure of `P`.
|
||||
|
||||
[closure]: #gloss-closure
|
||||
|
||||
- [output path]{#gloss-output-path}\
|
||||
A [store path] produced by a [derivation].
|
||||
|
||||
[output path]: #gloss-output-path
|
||||
|
||||
- [deriver]{#gloss-deriver}\
|
||||
The deriver of an *output path* is the store
|
||||
derivation that built it.
|
||||
The [store derivation] that produced an [output path].
|
||||
|
||||
- [validity]{#gloss-validity}\
|
||||
A store path is considered *valid* if it exists in the file system,
|
||||
is listed in the Nix database as being valid, and if all paths in
|
||||
its closure are also valid.
|
||||
A store path is valid if all [store object]s in its [closure] can be read from the [store].
|
||||
|
||||
For a local store, this means:
|
||||
- The store path leads to an existing [store object] in that [store].
|
||||
- The store path is listed in the Nix database as being valid.
|
||||
- All paths in the store path's [closure] are valid.
|
||||
|
||||
[validity]: #gloss-validity
|
||||
|
||||
- [user environment]{#gloss-user-env}\
|
||||
An automatically generated store object that consists of a set of
|
||||
|
|
|
@ -191,12 +191,12 @@ This is an incomplete overview of language features, by example.
|
|||
<tr>
|
||||
<td>
|
||||
|
||||
<nixpkgs>
|
||||
`<nixpkgs>`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Search path. Value determined by [`$NIX_PATH` environment variable](../command-ref/env-common.md#env-NIX_PATH).
|
||||
Search path for Nix files. Value determined by [`$NIX_PATH` environment variable](../command-ref/env-common.md#env-NIX_PATH).
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
| [Equality] | *expr* `==` *expr* | none | 11 |
|
||||
| Inequality | *expr* `!=` *expr* | none | 11 |
|
||||
| Logical conjunction (`AND`) | *bool* `&&` *bool* | left | 12 |
|
||||
| Logical disjunction (`OR`) | *bool* `||` *bool* | left | 13 |
|
||||
| Logical disjunction (`OR`) | *bool* `\|\|` *bool* | left | 13 |
|
||||
| [Logical implication] | *bool* `->` *bool* | none | 14 |
|
||||
|
||||
[string]: ./values.md#type-string
|
||||
|
@ -120,12 +120,12 @@ The result is a string.
|
|||
|
||||
## Update
|
||||
|
||||
> *attrset1* + *attrset2*
|
||||
> *attrset1* // *attrset2*
|
||||
|
||||
Update [attribute set] *attrset1* with names and values from *attrset2*.
|
||||
|
||||
The returned attribute set will have of all the attributes in *e1* and *e2*.
|
||||
If an attribute name is present in both, the attribute value from the former is taken.
|
||||
The returned attribute set will have of all the attributes in *attrset1* and *attrset2*.
|
||||
If an attribute name is present in both, the attribute value from the latter is taken.
|
||||
|
||||
[Update]: #update
|
||||
|
||||
|
|
|
@ -32,13 +32,13 @@ which should print something like:
|
|||
Priority: 30
|
||||
|
||||
On the client side, you can tell Nix to use your binary cache using
|
||||
`--option extra-binary-caches`, e.g.:
|
||||
`--substituters`, e.g.:
|
||||
|
||||
```console
|
||||
$ nix-env -iA nixpkgs.firefox --option extra-binary-caches http://avalon:8080/
|
||||
$ nix-env -iA nixpkgs.firefox --substituters http://avalon:8080/
|
||||
```
|
||||
|
||||
The option `extra-binary-caches` tells Nix to use this binary cache in
|
||||
The option `substituters` tells Nix to use this binary cache in
|
||||
addition to your default caches, such as <https://cache.nixos.org>.
|
||||
Thus, for any path in the closure of Firefox, Nix will first check if
|
||||
the path is available on the server `avalon` or another binary caches.
|
||||
|
@ -47,4 +47,4 @@ If not, it will fall back to building from source.
|
|||
You can also tell Nix to always use your binary cache by adding a line
|
||||
to the `nix.conf` configuration file like this:
|
||||
|
||||
binary-caches = http://avalon:8080/ https://cache.nixos.org/
|
||||
substituters = http://avalon:8080/ https://cache.nixos.org/
|
||||
|
|
44
doc/manual/src/release-notes/rl-2.13.md
Normal file
44
doc/manual/src/release-notes/rl-2.13.md
Normal file
|
@ -0,0 +1,44 @@
|
|||
# Release 2.13 (2023-01-17)
|
||||
|
||||
* The `repeat` and `enforce-determinism` options have been removed
|
||||
since they had been broken under many circumstances for a long time.
|
||||
|
||||
* You can now use [flake references] in the [old command line interface], e.g.
|
||||
|
||||
[flake references]: ../command-ref/new-cli/nix3-flake.md#flake-references
|
||||
[old command line interface]: ../command-ref/main-commands.md
|
||||
|
||||
```shell-session
|
||||
# nix-build flake:nixpkgs -A hello
|
||||
# nix-build -I nixpkgs=flake:github:NixOS/nixpkgs/nixos-22.05 \
|
||||
'<nixpkgs>' -A hello
|
||||
# NIX_PATH=nixpkgs=flake:nixpkgs nix-build '<nixpkgs>' -A hello
|
||||
```
|
||||
|
||||
* 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
|
||||
```
|
||||
now works just as
|
||||
```shell-session
|
||||
# nix build nixpkgs#glibc^dev
|
||||
```
|
||||
does already.
|
||||
|
||||
* On Linux, `nix develop` now sets the
|
||||
[*personality*](https://man7.org/linux/man-pages/man2/personality.2.html)
|
||||
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 ""`.
|
|
@ -1,23 +1,10 @@
|
|||
# Release X.Y (202?-??-??)
|
||||
|
||||
* The `repeat` and `enforce-determinism` options have been removed
|
||||
since they had been broken under many circumstances for a long time.
|
||||
* A new function `builtins.readFileType` is available. It is similar to
|
||||
`builtins.readDir` but acts on a single file or directory.
|
||||
|
||||
* You can now use [flake references] in the [old command line interface], e.g.
|
||||
|
||||
[flake references]: ../command-ref/new-cli/nix3-flake.md#flake-references
|
||||
[old command line interface]: ../command-ref/main-commands.md
|
||||
|
||||
```
|
||||
# nix-build flake:nixpkgs -A hello
|
||||
# nix-build -I nixpkgs=flake:github:NixOS/nixpkgs/nixos-22.05 \
|
||||
'<nixpkgs>' -A hello
|
||||
# NIX_PATH=nixpkgs=flake:nixpkgs nix-build '<nixpkgs>' -A hello
|
||||
```
|
||||
|
||||
* 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.
|
||||
* 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 is/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.
|
||||
|
|
30
flake.nix
30
flake.nix
|
@ -82,7 +82,9 @@
|
|||
});
|
||||
|
||||
configureFlags =
|
||||
lib.optionals stdenv.isLinux [
|
||||
[
|
||||
"CXXFLAGS=-I${lib.getDev rapidcheck}/extras/gtest/include"
|
||||
] ++ lib.optionals stdenv.isLinux [
|
||||
"--with-boost=${boost}/lib"
|
||||
"--with-sandbox-shell=${sh}/bin/busybox"
|
||||
]
|
||||
|
@ -121,6 +123,7 @@
|
|||
boost
|
||||
lowdown-nix
|
||||
gtest
|
||||
rapidcheck
|
||||
]
|
||||
++ lib.optionals stdenv.isLinux [libseccomp]
|
||||
++ lib.optional (stdenv.isLinux || stdenv.isDarwin) libsodium
|
||||
|
@ -133,9 +136,14 @@
|
|||
});
|
||||
|
||||
propagatedDeps =
|
||||
[ (boehmgc.override {
|
||||
[ ((boehmgc.override {
|
||||
enableLargeConfig = true;
|
||||
}).overrideAttrs(o: {
|
||||
patches = (o.patches or []) ++ [
|
||||
./boehmgc-coroutine-sp-fallback.diff
|
||||
];
|
||||
})
|
||||
)
|
||||
nlohmann_json
|
||||
];
|
||||
};
|
||||
|
@ -462,6 +470,10 @@
|
|||
|
||||
src = self;
|
||||
|
||||
configureFlags = [
|
||||
"CXXFLAGS=-I${lib.getDev pkgs.rapidcheck}/extras/gtest/include"
|
||||
];
|
||||
|
||||
enableParallelBuilding = true;
|
||||
|
||||
nativeBuildInputs = nativeBuildDeps;
|
||||
|
@ -537,6 +549,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;
|
||||
|
@ -567,6 +585,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};
|
||||
});
|
||||
|
@ -648,6 +667,7 @@
|
|||
inherit system crossSystem;
|
||||
overlays = [ self.overlays.default ];
|
||||
};
|
||||
inherit (nixpkgsCross) lib;
|
||||
in with commonDeps { pkgs = nixpkgsCross; }; nixpkgsCross.stdenv.mkDerivation {
|
||||
name = "nix-${version}";
|
||||
|
||||
|
@ -660,7 +680,11 @@
|
|||
nativeBuildInputs = nativeBuildDeps;
|
||||
buildInputs = buildDeps ++ propagatedDeps;
|
||||
|
||||
configureFlags = [ "--sysconfdir=/etc" "--disable-doc-gen" ];
|
||||
configureFlags = [
|
||||
"CXXFLAGS=-I${lib.getDev nixpkgsCross.rapidcheck}/extras/gtest/include"
|
||||
"--sysconfdir=/etc"
|
||||
"--disable-doc-gen"
|
||||
];
|
||||
|
||||
enableParallelBuilding = true;
|
||||
|
||||
|
|
|
@ -36,17 +36,45 @@ Issues on the board progress through the following states:
|
|||
|
||||
- No Status
|
||||
|
||||
Team members can add pull requests or issues to discuss or review together.
|
||||
|
||||
During the discussion meeting, the team triages new items.
|
||||
To be considered, issues and pull requests must have a high-level description to provide the whole team with the necessary context at a glance.
|
||||
|
||||
On every meeting, at least one item from each of the following categories is inspected:
|
||||
|
||||
1. [critical](https://github.com/NixOS/nix/labels/critical)
|
||||
2. [security](https://github.com/NixOS/nix/labels/security)
|
||||
3. [regression](https://github.com/NixOS/nix/labels/regression)
|
||||
4. [bug](https://github.com/NixOS/nix/issues?q=is%3Aopen+label%3Abug+sort%3Areactions-%2B1-desc)
|
||||
|
||||
- [oldest pull requests](https://github.com/NixOS/nix/pulls?q=is%3Apr+is%3Aopen+sort%3Acreated-asc)
|
||||
- [most popular pull requests](https://github.com/NixOS/nix/pulls?q=is%3Apr+is%3Aopen+sort%3Areactions-%2B1-desc)
|
||||
- [oldest issues](https://github.com/NixOS/nix/issues?q=is%3Aissue+is%3Aopen+sort%3Acreated-asc)
|
||||
- [most popular issues](https://github.com/NixOS/nix/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc)
|
||||
|
||||
Team members can also add pull requests or issues they would like the whole team to consider.
|
||||
|
||||
If there is disagreement on the general idea behind an issue or pull request, it is moved to _To discuss_, otherwise to _In review_.
|
||||
|
||||
- To discuss
|
||||
|
||||
Pull requests and issues that are important and controversial are discussed by the team during discussion meetings.
|
||||
Pull requests and issues that are deemed important and controversial are discussed by the team during discussion meetings.
|
||||
|
||||
This may be where the merit of the change itself or the implementation strategy is contested by a team member.
|
||||
|
||||
As a general guideline, the order of items is determined as follows:
|
||||
|
||||
- Prioritise pull requests over issues
|
||||
|
||||
Contributors who took the time to implement concrete change proposals should not wait indefinitely.
|
||||
|
||||
- Prioritise fixing bugs over documentation, improvements or new features
|
||||
|
||||
The team values stability and accessibility higher than raw functionality.
|
||||
|
||||
- Interleave issues and PRs
|
||||
|
||||
This way issues without attempts at a solution get a chance to get addressed.
|
||||
|
||||
- In review
|
||||
|
||||
Pull requests in this column are reviewed together during work meetings.
|
||||
|
|
|
@ -24,12 +24,17 @@ $1
|
|||
EOF
|
||||
}
|
||||
|
||||
escape_systemd_env() {
|
||||
temp_var="${1//\'/\\\'}"
|
||||
echo "${temp_var//\%/%%}"
|
||||
}
|
||||
|
||||
# Gather all non-empty proxy environment variables into a string
|
||||
create_systemd_proxy_env() {
|
||||
vars="http_proxy https_proxy ftp_proxy no_proxy HTTP_PROXY HTTPS_PROXY FTP_PROXY NO_PROXY"
|
||||
for v in $vars; do
|
||||
if [ "x${!v:-}" != "x" ]; then
|
||||
echo "Environment=${v}=${!v}"
|
||||
echo "Environment=${v}=$(escape_systemd_env ${!v})"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "globals.hh"
|
||||
#include "installables.hh"
|
||||
#include "outputs-spec.hh"
|
||||
#include "util.hh"
|
||||
#include "command.hh"
|
||||
#include "attr-path.hh"
|
||||
|
@ -402,18 +403,6 @@ struct InstallableStorePath : Installable
|
|||
ref<Store> store;
|
||||
DerivedPath req;
|
||||
|
||||
InstallableStorePath(ref<Store> store, StorePath && storePath)
|
||||
: store(store),
|
||||
req(storePath.isDerivation()
|
||||
? (DerivedPath) DerivedPath::Built {
|
||||
.drvPath = std::move(storePath),
|
||||
.outputs = {},
|
||||
}
|
||||
: (DerivedPath) DerivedPath::Opaque {
|
||||
.path = std::move(storePath),
|
||||
})
|
||||
{ }
|
||||
|
||||
InstallableStorePath(ref<Store> store, DerivedPath && req)
|
||||
: store(store), req(std::move(req))
|
||||
{ }
|
||||
|
@ -446,19 +435,19 @@ struct InstallableAttrPath : InstallableValue
|
|||
SourceExprCommand & cmd;
|
||||
RootValue v;
|
||||
std::string attrPath;
|
||||
OutputsSpec outputsSpec;
|
||||
ExtendedOutputsSpec extendedOutputsSpec;
|
||||
|
||||
InstallableAttrPath(
|
||||
ref<EvalState> state,
|
||||
SourceExprCommand & cmd,
|
||||
Value * v,
|
||||
const std::string & attrPath,
|
||||
OutputsSpec outputsSpec)
|
||||
ExtendedOutputsSpec extendedOutputsSpec)
|
||||
: InstallableValue(state)
|
||||
, cmd(cmd)
|
||||
, v(allocRootValue(v))
|
||||
, attrPath(attrPath)
|
||||
, outputsSpec(std::move(outputsSpec))
|
||||
, extendedOutputsSpec(std::move(extendedOutputsSpec))
|
||||
{ }
|
||||
|
||||
std::string what() const override { return attrPath; }
|
||||
|
@ -481,30 +470,39 @@ struct InstallableAttrPath : InstallableValue
|
|||
|
||||
// Backward compatibility hack: group results by drvPath. This
|
||||
// helps keep .all output together.
|
||||
std::map<StorePath, DerivedPath::Built> byDrvPath;
|
||||
std::map<StorePath, OutputsSpec> byDrvPath;
|
||||
|
||||
for (auto & drvInfo : drvInfos) {
|
||||
auto drvPath = drvInfo.queryDrvPath();
|
||||
if (!drvPath)
|
||||
throw Error("'%s' is not a derivation", what());
|
||||
|
||||
std::set<std::string> outputsToInstall;
|
||||
auto newOutputs = std::visit(overloaded {
|
||||
[&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec {
|
||||
std::set<std::string> outputsToInstall;
|
||||
for (auto & output : drvInfo.queryOutputs(false, true))
|
||||
outputsToInstall.insert(output.first);
|
||||
return OutputsSpec::Names { std::move(outputsToInstall) };
|
||||
},
|
||||
[&](const ExtendedOutputsSpec::Explicit & e) -> OutputsSpec {
|
||||
return e;
|
||||
},
|
||||
}, extendedOutputsSpec.raw());
|
||||
|
||||
if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
|
||||
outputsToInstall = *outputNames;
|
||||
else
|
||||
for (auto & output : drvInfo.queryOutputs(false, std::get_if<DefaultOutputs>(&outputsSpec)))
|
||||
outputsToInstall.insert(output.first);
|
||||
auto [iter, didInsert] = byDrvPath.emplace(*drvPath, newOutputs);
|
||||
|
||||
auto derivedPath = byDrvPath.emplace(*drvPath, DerivedPath::Built { .drvPath = *drvPath }).first;
|
||||
|
||||
for (auto & output : outputsToInstall)
|
||||
derivedPath->second.outputs.insert(output);
|
||||
if (!didInsert)
|
||||
iter->second = iter->second.union_(newOutputs);
|
||||
}
|
||||
|
||||
DerivedPathsWithInfo res;
|
||||
for (auto & [_, info] : byDrvPath)
|
||||
res.push_back({ .path = { info } });
|
||||
for (auto & [drvPath, outputs] : byDrvPath)
|
||||
res.push_back({
|
||||
.path = DerivedPath::Built {
|
||||
.drvPath = drvPath,
|
||||
.outputs = outputs,
|
||||
},
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
@ -581,7 +579,7 @@ InstallableFlake::InstallableFlake(
|
|||
ref<EvalState> state,
|
||||
FlakeRef && flakeRef,
|
||||
std::string_view fragment,
|
||||
OutputsSpec outputsSpec,
|
||||
ExtendedOutputsSpec extendedOutputsSpec,
|
||||
Strings attrPaths,
|
||||
Strings prefixes,
|
||||
const flake::LockFlags & lockFlags)
|
||||
|
@ -589,7 +587,7 @@ InstallableFlake::InstallableFlake(
|
|||
flakeRef(flakeRef),
|
||||
attrPaths(fragment == "" ? attrPaths : Strings{(std::string) fragment}),
|
||||
prefixes(fragment == "" ? Strings{} : prefixes),
|
||||
outputsSpec(std::move(outputsSpec)),
|
||||
extendedOutputsSpec(std::move(extendedOutputsSpec)),
|
||||
lockFlags(lockFlags)
|
||||
{
|
||||
if (cmd && cmd->getAutoArgs(*state)->size())
|
||||
|
@ -638,48 +636,47 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
|
|||
|
||||
auto drvPath = attr->forceDerivation();
|
||||
|
||||
std::set<std::string> outputsToInstall;
|
||||
std::optional<NixInt> priority;
|
||||
|
||||
if (auto aOutputSpecified = attr->maybeGetAttr(state->sOutputSpecified)) {
|
||||
if (aOutputSpecified->getBool()) {
|
||||
if (auto aOutputName = attr->maybeGetAttr("outputName"))
|
||||
outputsToInstall = { aOutputName->getString() };
|
||||
}
|
||||
}
|
||||
|
||||
else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) {
|
||||
if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall"))
|
||||
for (auto & s : aOutputsToInstall->getListOfStrings())
|
||||
outputsToInstall.insert(s);
|
||||
if (attr->maybeGetAttr(state->sOutputSpecified)) {
|
||||
} else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) {
|
||||
if (auto aPriority = aMeta->maybeGetAttr("priority"))
|
||||
priority = aPriority->getInt();
|
||||
}
|
||||
|
||||
if (outputsToInstall.empty() || std::get_if<AllOutputs>(&outputsSpec)) {
|
||||
outputsToInstall.clear();
|
||||
if (auto aOutputs = attr->maybeGetAttr(state->sOutputs))
|
||||
for (auto & s : aOutputs->getListOfStrings())
|
||||
outputsToInstall.insert(s);
|
||||
}
|
||||
|
||||
if (outputsToInstall.empty())
|
||||
outputsToInstall.insert("out");
|
||||
|
||||
if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
|
||||
outputsToInstall = *outputNames;
|
||||
|
||||
return {{
|
||||
.path = DerivedPath::Built {
|
||||
.drvPath = std::move(drvPath),
|
||||
.outputs = std::move(outputsToInstall),
|
||||
.outputs = std::visit(overloaded {
|
||||
[&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec {
|
||||
std::set<std::string> outputsToInstall;
|
||||
if (auto aOutputSpecified = attr->maybeGetAttr(state->sOutputSpecified)) {
|
||||
if (aOutputSpecified->getBool()) {
|
||||
if (auto aOutputName = attr->maybeGetAttr("outputName"))
|
||||
outputsToInstall = { aOutputName->getString() };
|
||||
}
|
||||
} else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) {
|
||||
if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall"))
|
||||
for (auto & s : aOutputsToInstall->getListOfStrings())
|
||||
outputsToInstall.insert(s);
|
||||
}
|
||||
|
||||
if (outputsToInstall.empty())
|
||||
outputsToInstall.insert("out");
|
||||
|
||||
return OutputsSpec::Names { std::move(outputsToInstall) };
|
||||
},
|
||||
[&](const ExtendedOutputsSpec::Explicit & e) -> OutputsSpec {
|
||||
return e;
|
||||
},
|
||||
}, extendedOutputsSpec.raw()),
|
||||
},
|
||||
.info = {
|
||||
.priority = priority,
|
||||
.originalRef = flakeRef,
|
||||
.resolvedRef = getLockedFlake()->flake.lockedRef,
|
||||
.attrPath = attrPath,
|
||||
.outputsSpec = outputsSpec,
|
||||
.extendedOutputsSpec = extendedOutputsSpec,
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
@ -796,12 +793,12 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
|
|||
}
|
||||
|
||||
for (auto & s : ss) {
|
||||
auto [prefix, outputsSpec] = parseOutputsSpec(s);
|
||||
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(s);
|
||||
result.push_back(
|
||||
std::make_shared<InstallableAttrPath>(
|
||||
state, *this, vFile,
|
||||
prefix == "." ? "" : prefix,
|
||||
outputsSpec));
|
||||
prefix == "." ? "" : std::string { prefix },
|
||||
extendedOutputsSpec));
|
||||
}
|
||||
|
||||
} else {
|
||||
|
@ -809,24 +806,46 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
|
|||
for (auto & s : ss) {
|
||||
std::exception_ptr ex;
|
||||
|
||||
auto found = s.rfind('^');
|
||||
if (found != std::string::npos) {
|
||||
try {
|
||||
result.push_back(std::make_shared<InstallableStorePath>(
|
||||
store,
|
||||
DerivedPath::Built::parse(*store, s.substr(0, found), s.substr(found + 1))));
|
||||
continue;
|
||||
} catch (BadStorePath &) {
|
||||
} catch (...) {
|
||||
if (!ex)
|
||||
ex = std::current_exception();
|
||||
}
|
||||
}
|
||||
auto [prefix_, extendedOutputsSpec_] = ExtendedOutputsSpec::parse(s);
|
||||
// To avoid clang's pedantry
|
||||
auto prefix = std::move(prefix_);
|
||||
auto extendedOutputsSpec = std::move(extendedOutputsSpec_);
|
||||
|
||||
found = s.find('/');
|
||||
auto found = prefix.find('/');
|
||||
if (found != std::string::npos) {
|
||||
try {
|
||||
result.push_back(std::make_shared<InstallableStorePath>(store, store->followLinksToStorePath(s)));
|
||||
auto derivedPath = std::visit(overloaded {
|
||||
// If the user did not use ^, we treat the output more liberally.
|
||||
[&](const ExtendedOutputsSpec::Default &) -> DerivedPath {
|
||||
// First, we accept a symlink chain or an actual store path.
|
||||
auto storePath = store->followLinksToStorePath(prefix);
|
||||
// Second, we see if the store path ends in `.drv` to decide what sort
|
||||
// of derived path they want.
|
||||
//
|
||||
// This handling predates the `^` syntax. The `^*` in
|
||||
// `/nix/store/hash-foo.drv^*` unambiguously means "do the
|
||||
// `DerivedPath::Built` case", so plain `/nix/store/hash-foo.drv` could
|
||||
// also unambiguously mean "do the DerivedPath::Opaque` case".
|
||||
//
|
||||
// Issue #7261 tracks reconsidering this `.drv` dispatching.
|
||||
return storePath.isDerivation()
|
||||
? (DerivedPath) DerivedPath::Built {
|
||||
.drvPath = std::move(storePath),
|
||||
.outputs = OutputsSpec::All {},
|
||||
}
|
||||
: (DerivedPath) DerivedPath::Opaque {
|
||||
.path = std::move(storePath),
|
||||
};
|
||||
},
|
||||
// If the user did use ^, we just do exactly what is written.
|
||||
[&](const ExtendedOutputsSpec::Explicit & outputSpec) -> DerivedPath {
|
||||
return DerivedPath::Built {
|
||||
.drvPath = store->parseStorePath(prefix),
|
||||
.outputs = outputSpec,
|
||||
};
|
||||
},
|
||||
}, extendedOutputsSpec.raw());
|
||||
result.push_back(std::make_shared<InstallableStorePath>(store, std::move(derivedPath)));
|
||||
continue;
|
||||
} catch (BadStorePath &) {
|
||||
} catch (...) {
|
||||
|
@ -836,13 +855,13 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
|
|||
}
|
||||
|
||||
try {
|
||||
auto [flakeRef, fragment, outputsSpec] = parseFlakeRefWithFragmentAndOutputsSpec(s, absPath("."));
|
||||
auto [flakeRef, fragment] = parseFlakeRefWithFragment(std::string { prefix }, absPath("."));
|
||||
result.push_back(std::make_shared<InstallableFlake>(
|
||||
this,
|
||||
getEvalState(),
|
||||
std::move(flakeRef),
|
||||
fragment,
|
||||
outputsSpec,
|
||||
extendedOutputsSpec,
|
||||
getDefaultFlakeAttrPaths(),
|
||||
getDefaultFlakeAttrPathPrefixes(),
|
||||
lockFlags));
|
||||
|
@ -917,32 +936,7 @@ std::vector<std::pair<std::shared_ptr<Installable>, BuiltPathWithResult>> Instal
|
|||
for (auto & aux : backmap[path]) {
|
||||
std::visit(overloaded {
|
||||
[&](const DerivedPath::Built & bfd) {
|
||||
OutputPathMap outputs;
|
||||
auto drv = evalStore->readDerivation(bfd.drvPath);
|
||||
auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive
|
||||
auto drvOutputs = drv.outputsAndOptPaths(*store);
|
||||
for (auto & output : bfd.outputs) {
|
||||
auto outputHash = get(outputHashes, output);
|
||||
if (!outputHash)
|
||||
throw Error(
|
||||
"the derivation '%s' doesn't have an output named '%s'",
|
||||
store->printStorePath(bfd.drvPath), output);
|
||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
||||
DrvOutput outputId { *outputHash, output };
|
||||
auto realisation = store->queryRealisation(outputId);
|
||||
if (!realisation)
|
||||
throw MissingRealisation(outputId);
|
||||
outputs.insert_or_assign(output, realisation->outPath);
|
||||
} else {
|
||||
// If ca-derivations isn't enabled, assume that
|
||||
// the output path is statically known.
|
||||
auto drvOutput = get(drvOutputs, output);
|
||||
assert(drvOutput);
|
||||
assert(drvOutput->second);
|
||||
outputs.insert_or_assign(
|
||||
output, *drvOutput->second);
|
||||
}
|
||||
}
|
||||
auto outputs = resolveDerivedPath(*store, bfd, &*evalStore);
|
||||
res.push_back({aux.installable, {
|
||||
.path = BuiltPath::Built { bfd.drvPath, outputs },
|
||||
.info = aux.info}});
|
||||
|
|
|
@ -59,7 +59,7 @@ struct ExtraPathInfo
|
|||
std::optional<FlakeRef> resolvedRef;
|
||||
std::optional<std::string> attrPath;
|
||||
// FIXME: merge with DerivedPath's 'outputs' field?
|
||||
std::optional<OutputsSpec> outputsSpec;
|
||||
std::optional<ExtendedOutputsSpec> extendedOutputsSpec;
|
||||
};
|
||||
|
||||
/* A derived path with any additional info that commands might
|
||||
|
@ -169,7 +169,7 @@ struct InstallableFlake : InstallableValue
|
|||
FlakeRef flakeRef;
|
||||
Strings attrPaths;
|
||||
Strings prefixes;
|
||||
OutputsSpec outputsSpec;
|
||||
ExtendedOutputsSpec extendedOutputsSpec;
|
||||
const flake::LockFlags & lockFlags;
|
||||
mutable std::shared_ptr<flake::LockedFlake> _lockedFlake;
|
||||
|
||||
|
@ -178,7 +178,7 @@ struct InstallableFlake : InstallableValue
|
|||
ref<EvalState> state,
|
||||
FlakeRef && flakeRef,
|
||||
std::string_view fragment,
|
||||
OutputsSpec outputsSpec,
|
||||
ExtendedOutputsSpec extendedOutputsSpec,
|
||||
Strings attrPaths,
|
||||
Strings prefixes,
|
||||
const flake::LockFlags & lockFlags);
|
||||
|
|
|
@ -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, "while evaluating an attrset for the purpose of completion (this error should not be displayed; file an issue?)");
|
||||
|
||||
for (auto & i : *v.attrs) {
|
||||
std::string_view name = state->symbols[i.name];
|
||||
|
@ -641,7 +641,12 @@ bool NixRepl::processLine(std::string line)
|
|||
Path drvPathRaw = state->store->printStorePath(drvPath);
|
||||
|
||||
if (command == ":b" || command == ":bl") {
|
||||
state->store->buildPaths({DerivedPath::Built{drvPath}});
|
||||
state->store->buildPaths({
|
||||
DerivedPath::Built {
|
||||
.drvPath = drvPath,
|
||||
.outputs = OutputsSpec::All { },
|
||||
},
|
||||
});
|
||||
auto drv = state->store->readDerivation(drvPath);
|
||||
logger->cout("\nThis derivation produced the following outputs:");
|
||||
for (auto & [outputName, outputPath] : state->store->queryDerivationOutputMap(drvPath)) {
|
||||
|
|
|
@ -12,7 +12,9 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
|
@ -1891,7 +1893,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
|||
state.error("cannot add %1% to a float", showType(*vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>();
|
||||
} else if (firstType == nPath) {
|
||||
if (!first) {
|
||||
auto part = state.coerceToString(i_pos, *vTmp, context, false, false);
|
||||
auto part = state.coerceToString(i_pos, *vTmp, context, "while evaluating a path segment", false, false);
|
||||
if (sSize <= 1 && !hasPrefix(*part, "/") && accessor != state.rootFS.get_ptr())
|
||||
state.error(
|
||||
"cannot append non-absolute path '%1%' to '%2%' (hint: change it to '/%1%')",
|
||||
|
@ -1904,7 +1906,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
|||
}
|
||||
} else {
|
||||
if (s.empty()) s.reserve(es->size());
|
||||
auto part = state.coerceToString(i_pos, *vTmp, context, false, firstType == nString, "while evaluating a path segment");
|
||||
auto part = state.coerceToString(i_pos, *vTmp, context, "while evaluating a path segment", false, firstType == nString);
|
||||
sSize += part->size();
|
||||
s.emplace_back(std::move(part));
|
||||
}
|
||||
|
@ -2100,15 +2102,21 @@ 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,
|
||||
"while evaluating the result of the `__toString` attribute",
|
||||
coerceMore, copyToStore).toOwned();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet & context,
|
||||
bool coerceMore, bool copyToStore, std::string_view errorCtx)
|
||||
BackedStringView EvalState::coerceToString(
|
||||
const PosIdx pos,
|
||||
Value & v,
|
||||
PathSet & context,
|
||||
std::string_view errorCtx,
|
||||
bool coerceMore,
|
||||
bool copyToStore)
|
||||
{
|
||||
forceValue(v, pos);
|
||||
|
||||
|
@ -2129,13 +2137,22 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
|
|||
if (maybeString)
|
||||
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, errorCtx);
|
||||
if (i == v.attrs->end()) {
|
||||
error("cannot coerce %1% to a string", showType(v))
|
||||
.withTrace(pos, errorCtx)
|
||||
.debugThrow<TypeError>();
|
||||
}
|
||||
return coerceToString(pos, *i->value, context, errorCtx, coerceMore, copyToStore);
|
||||
}
|
||||
|
||||
if (v.type() == nExternal)
|
||||
return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore, errorCtx);
|
||||
if (v.type() == nExternal) {
|
||||
try {
|
||||
return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore);
|
||||
} catch (Error & e) {
|
||||
e.addTrace(nullptr, errorCtx);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
if (coerceMore) {
|
||||
/* Note that `false' is represented as an empty string for
|
||||
|
@ -2150,8 +2167,9 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
|
|||
std::string result;
|
||||
for (auto [n, v2] : enumerate(v.listItems())) {
|
||||
try {
|
||||
result += *coerceToString(noPos, *v2, context, coerceMore, copyToStore,
|
||||
"while evaluating one element of the list");
|
||||
result += *coerceToString(noPos, *v2, context,
|
||||
"while evaluating one element of the list",
|
||||
coerceMore, copyToStore);
|
||||
} catch (Error & e) {
|
||||
e.addTrace(positions[pos], errorCtx);
|
||||
throw;
|
||||
|
@ -2165,7 +2183,9 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
|
|||
}
|
||||
}
|
||||
|
||||
error("cannot coerce %1% to a string", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
|
||||
error("cannot coerce %1% to a string", showType(v))
|
||||
.withTrace(pos, errorCtx)
|
||||
.debugThrow<TypeError>();
|
||||
}
|
||||
|
||||
|
||||
|
@ -2215,7 +2235,7 @@ SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & contex
|
|||
|
||||
StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
|
||||
{
|
||||
auto path = coerceToString(pos, v, context, false, false, errorCtx).toOwned();
|
||||
auto path = coerceToString(pos, v, context, errorCtx, 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>();
|
||||
|
@ -2421,13 +2441,11 @@ 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({
|
||||
throw TypeError({
|
||||
.msg = hintfmt("cannot coerce %1% to a string", showType())
|
||||
});
|
||||
e.addTrace(pos, errorCtx);
|
||||
throw e;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -206,6 +206,9 @@ public:
|
|||
throw std::move(error);
|
||||
}
|
||||
|
||||
// This is dangerous, but gets in line with the idea that error creation and
|
||||
// throwing should not allocate on the stack of hot functions.
|
||||
// as long as errors are immediately thrown, it works.
|
||||
ErrorBuilder * errorBuilder;
|
||||
|
||||
template<typename... Args>
|
||||
|
@ -386,8 +389,8 @@ public:
|
|||
booleans and lists to a string. If `copyToStore' is set,
|
||||
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,
|
||||
std::string_view errorCtx = "");
|
||||
std::string_view errorCtx,
|
||||
bool coerceMore = false, bool copyToStore = true);
|
||||
|
||||
StorePath copyPathToStore(PathSet & context, const SourcePath & path);
|
||||
|
||||
|
|
|
@ -202,7 +202,7 @@ static Flake readFlake(
|
|||
PathSet emptyContext = {};
|
||||
flake.config.settings.emplace(
|
||||
state.symbols[setting.name],
|
||||
state.coerceToString(setting.pos, *setting.value, emptyContext, false, true, "").toOwned());
|
||||
state.coerceToString(setting.pos, *setting.value, emptyContext, "", false, true).toOwned());
|
||||
}
|
||||
else if (setting.value->type() == nInt)
|
||||
flake.config.settings.emplace(
|
||||
|
|
|
@ -239,15 +239,15 @@ std::pair<ref<InputAccessor>, FlakeRef> FlakeRef::lazyFetch(ref<Store> store) co
|
|||
return {accessor, FlakeRef(std::move(lockedInput), subdir)};
|
||||
}
|
||||
|
||||
std::tuple<FlakeRef, std::string, OutputsSpec> parseFlakeRefWithFragmentAndOutputsSpec(
|
||||
std::tuple<FlakeRef, std::string, ExtendedOutputsSpec> parseFlakeRefWithFragmentAndExtendedOutputsSpec(
|
||||
const std::string & url,
|
||||
const std::optional<Path> & baseDir,
|
||||
bool allowMissing,
|
||||
bool isFlake)
|
||||
{
|
||||
auto [prefix, outputsSpec] = parseOutputsSpec(url);
|
||||
auto [flakeRef, fragment] = parseFlakeRefWithFragment(prefix, baseDir, allowMissing, isFlake);
|
||||
return {std::move(flakeRef), fragment, outputsSpec};
|
||||
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(url);
|
||||
auto [flakeRef, fragment] = parseFlakeRefWithFragment(std::string { prefix }, baseDir, allowMissing, isFlake);
|
||||
return {std::move(flakeRef), fragment, extendedOutputsSpec};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
|||
std::optional<std::pair<FlakeRef, std::string>> maybeParseFlakeRefWithFragment(
|
||||
const std::string & url, const std::optional<Path> & baseDir = {});
|
||||
|
||||
std::tuple<FlakeRef, std::string, OutputsSpec> parseFlakeRefWithFragmentAndOutputsSpec(
|
||||
std::tuple<FlakeRef, std::string, ExtendedOutputsSpec> parseFlakeRefWithFragmentAndExtendedOutputsSpec(
|
||||
const std::string & url,
|
||||
const std::optional<Path> & baseDir = {},
|
||||
bool allowMissing = false,
|
||||
|
|
|
@ -54,7 +54,7 @@ StringMap EvalState::realiseContext(const PathSet & context)
|
|||
[&](const NixStringContextElem::Built & b) {
|
||||
drvs.push_back(DerivedPath::Built {
|
||||
.drvPath = b.drvPath,
|
||||
.outputs = std::set { b.output },
|
||||
.outputs = OutputsSpec::Names { b.output },
|
||||
});
|
||||
ensureValid(b.drvPath);
|
||||
},
|
||||
|
@ -85,16 +85,12 @@ StringMap EvalState::realiseContext(const PathSet & context)
|
|||
store->buildPaths(buildReqs);
|
||||
|
||||
/* Get all the output paths corresponding to the placeholders we had */
|
||||
for (auto & [drvPath, outputs] : drvs) {
|
||||
const auto outputPaths = store->queryDerivationOutputMap(drvPath);
|
||||
for (auto & outputName : outputs) {
|
||||
auto outputPath = get(outputPaths, outputName);
|
||||
if (!outputPath)
|
||||
debugThrowLastTrace(Error("derivation '%s' does not have an output named '%s'",
|
||||
store->printStorePath(drvPath), outputName));
|
||||
for (auto & drv : drvs) {
|
||||
auto outputs = resolveDerivedPath(*store, drv);
|
||||
for (auto & [outputName, outputPath] : outputs) {
|
||||
res.insert_or_assign(
|
||||
downstreamPlaceholder(*store, drvPath, outputName),
|
||||
store->printStorePath(*outputPath)
|
||||
downstreamPlaceholder(*store, drv.drvPath, outputName),
|
||||
store->printStorePath(outputPath)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -356,26 +352,22 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
|||
auto elems = args[0]->listElems();
|
||||
auto count = args[0]->listSize();
|
||||
if (count == 0)
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("at least one argument to 'exec' required"),
|
||||
.errPos = state.positions[pos]
|
||||
}));
|
||||
state.error("at least one argument to 'exec' required").atPos(pos).debugThrow<EvalError>();
|
||||
PathSet context;
|
||||
auto program = state.coerceToString(pos, *elems[0], context, false, false,
|
||||
"while evaluating the first element of the argument passed to builtins.exec").toOwned();
|
||||
auto program = state.coerceToString(pos, *elems[0], context,
|
||||
"while evaluating the first element of the argument passed to builtins.exec",
|
||||
false, false).toOwned();
|
||||
Strings commandArgs;
|
||||
for (unsigned int i = 1; i < args[0]->listSize(); ++i) {
|
||||
commandArgs.push_back(state.coerceToString(pos, *elems[i], context, false, false,
|
||||
"while evaluating an element of the argument passed to builtins.exec").toOwned());
|
||||
commandArgs.push_back(
|
||||
state.coerceToString(pos, *elems[i], context,
|
||||
"while evaluating an element of the argument passed to builtins.exec",
|
||||
false, false).toOwned());
|
||||
}
|
||||
try {
|
||||
auto _ = state.realiseContext(context); // FIXME: Handle CA derivations
|
||||
} catch (InvalidPathError & e) {
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("cannot execute '%1%', since path '%2%' is not valid",
|
||||
program, e.path),
|
||||
.errPos = state.positions[pos]
|
||||
}));
|
||||
state.error("cannot execute '%1%', since path '%2%' is not valid", program, e.path).atPos(pos).debugThrow<EvalError>();
|
||||
}
|
||||
|
||||
auto output = runProgram(program, true, commandArgs);
|
||||
|
@ -605,7 +597,8 @@ struct CompareValues
|
|||
state.error("cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow<EvalError>();
|
||||
}
|
||||
} catch (Error & e) {
|
||||
e.addTrace(nullptr, errorCtx);
|
||||
if (!errorCtx.empty())
|
||||
e.addTrace(nullptr, errorCtx);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
@ -627,15 +620,7 @@ static Bindings::iterator getAttr(
|
|||
{
|
||||
Bindings::iterator value = attrSet->find(attrSym);
|
||||
if (value == attrSet->end()) {
|
||||
throw TypeError({
|
||||
.msg = hintfmt("attribute '%s' missing %s", state.symbols[attrSym], normaltxt(errorCtx)),
|
||||
.errPos = state.positions[attrSet->pos],
|
||||
});
|
||||
// TODO XXX
|
||||
// Adding another trace for the function name to make it clear
|
||||
// which call received wrong arguments.
|
||||
//e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", funcName));
|
||||
//state.debugThrowLastTrace(e);
|
||||
state.error("attribute '%s' missing", state.symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow<TypeError>();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
@ -809,7 +794,8 @@ static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * *
|
|||
} catch (Error & e) {
|
||||
PathSet context;
|
||||
e.addTrace(nullptr, state.decodePaths(*state.coerceToString(pos, *args[0], context,
|
||||
"while evaluating the error message passed to 'builtins.addErrorContext'")));
|
||||
"while evaluating the error message passed to 'builtins.addErrorContext'",
|
||||
false, false)));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
@ -1013,6 +999,7 @@ static void prim_second(EvalState & state, const PosIdx pos, Value * * args, Val
|
|||
* Derivations
|
||||
*************************************************************/
|
||||
|
||||
static void derivationStrictInternal(EvalState & state, const std::string & name, Bindings * attrs, Value & v);
|
||||
|
||||
/* Construct (as a unobservable side effect) a Nix derivation
|
||||
expression that performs the derivation described by the argument
|
||||
|
@ -1023,32 +1010,68 @@ static void prim_second(EvalState & state, const PosIdx pos, Value * * args, Val
|
|||
derivation. */
|
||||
static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
using nlohmann::json;
|
||||
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.derivationStrict");
|
||||
|
||||
Bindings * attrs = args[0]->attrs;
|
||||
|
||||
/* Figure out the name first (for stack backtraces). */
|
||||
Bindings::iterator attr = getAttr(state, state.sName, args[0]->attrs, "in the attrset passed as argument to builtins.derivationStrict");
|
||||
Bindings::iterator nameAttr = getAttr(state, state.sName, attrs, "in the attrset passed as argument to builtins.derivationStrict");
|
||||
|
||||
std::string drvName;
|
||||
const auto posDrvName = attr->pos;
|
||||
try {
|
||||
drvName = state.forceStringNoCtx(*attr->value, pos, "while evaluating the `name` attribute passed to builtins.derivationStrict");
|
||||
drvName = state.forceStringNoCtx(*nameAttr->value, pos, "while evaluating the `name` attribute passed to builtins.derivationStrict");
|
||||
} catch (Error & e) {
|
||||
e.addTrace(state.positions[posDrvName], "while evaluating the derivation attribute 'name'");
|
||||
e.addTrace(state.positions[nameAttr->pos], "while evaluating the derivation attribute 'name'");
|
||||
throw;
|
||||
}
|
||||
|
||||
try {
|
||||
derivationStrictInternal(state, drvName, attrs, v);
|
||||
} catch (Error & e) {
|
||||
Pos pos = state.positions[nameAttr->pos];
|
||||
/*
|
||||
* Here we make two abuses of the error system
|
||||
*
|
||||
* 1. We print the location as a string to avoid a code snippet being
|
||||
* printed. While the location of the name attribute is a good hint, the
|
||||
* exact code there is irrelevant.
|
||||
*
|
||||
* 2. We mark this trace as a frame trace, meaning that we stop printing
|
||||
* less important traces from now on. In particular, this prevents the
|
||||
* display of the automatic "while calling builtins.derivationStrict"
|
||||
* trace, which is of little use for the public we target here.
|
||||
*
|
||||
* Please keep in mind that error reporting is done on a best-effort
|
||||
* basis in nix. There is no accurate location for a derivation, as it
|
||||
* often results from the composition of several functions
|
||||
* (derivationStrict, derivation, mkDerivation, mkPythonModule, etc.)
|
||||
*/
|
||||
e.addTrace(nullptr, hintfmt(
|
||||
"while evaluating derivation '%s'\n"
|
||||
" whose name attribute is located at %s",
|
||||
drvName, pos), true);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
static void derivationStrictInternal(EvalState & state, const std::string &
|
||||
drvName, Bindings * attrs, Value & v)
|
||||
{
|
||||
/* Check whether attributes should be passed as a JSON file. */
|
||||
using nlohmann::json;
|
||||
std::optional<json> jsonObject;
|
||||
attr = args[0]->attrs->find(state.sStructuredAttrs);
|
||||
if (attr != args[0]->attrs->end() && state.forceBool(*attr->value, pos, "while evaluating the `__structuredAttrs` attribute passed to builtins.derivationStrict"))
|
||||
auto attr = attrs->find(state.sStructuredAttrs);
|
||||
if (attr != attrs->end() &&
|
||||
state.forceBool(*attr->value, noPos,
|
||||
"while evaluating the `__structuredAttrs` "
|
||||
"attribute passed to builtins.derivationStrict"))
|
||||
jsonObject = json::object();
|
||||
|
||||
/* Check whether null attributes should be ignored. */
|
||||
bool ignoreNulls = false;
|
||||
attr = args[0]->attrs->find(state.sIgnoreNulls);
|
||||
if (attr != args[0]->attrs->end())
|
||||
ignoreNulls = state.forceBool(*attr->value, pos, "while evaluating the `__ignoreNulls` attribute passed to builtins.derivationStrict");
|
||||
attr = attrs->find(state.sIgnoreNulls);
|
||||
if (attr != attrs->end())
|
||||
ignoreNulls = state.forceBool(*attr->value, noPos, "while evaluating the `__ignoreNulls` attribute " "passed to builtins.derivationStrict");
|
||||
|
||||
/* Build the derivation expression by processing the attributes. */
|
||||
Derivation drv;
|
||||
|
@ -1065,7 +1088,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
StringSet outputs;
|
||||
outputs.insert("out");
|
||||
|
||||
for (auto & i : args[0]->attrs->lexicographicOrder(state.symbols)) {
|
||||
for (auto & i : attrs->lexicographicOrder(state.symbols)) {
|
||||
if (i->name == state.sIgnoreNulls) continue;
|
||||
const std::string & key = state.symbols[i->name];
|
||||
vomit("processing attribute '%1%'", key);
|
||||
|
@ -1076,7 +1099,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
else
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s),
|
||||
.errPos = state.positions[posDrvName]
|
||||
.errPos = state.positions[noPos]
|
||||
}));
|
||||
};
|
||||
|
||||
|
@ -1086,7 +1109,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
if (outputs.find(j) != outputs.end())
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("duplicate derivation output '%1%'", j),
|
||||
.errPos = state.positions[posDrvName]
|
||||
.errPos = state.positions[noPos]
|
||||
}));
|
||||
/* !!! Check whether j is a valid attribute
|
||||
name. */
|
||||
|
@ -1096,34 +1119,35 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
if (j == "drv")
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("invalid derivation output name 'drv'" ),
|
||||
.errPos = state.positions[posDrvName]
|
||||
.errPos = state.positions[noPos]
|
||||
}));
|
||||
outputs.insert(j);
|
||||
}
|
||||
if (outputs.empty())
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("derivation cannot have an empty set of outputs"),
|
||||
.errPos = state.positions[posDrvName]
|
||||
.errPos = state.positions[noPos]
|
||||
}));
|
||||
};
|
||||
|
||||
try {
|
||||
// This try-catch block adds context for most errors.
|
||||
// Use this empty error context to signify that we defer to it.
|
||||
const std::string_view context_below("");
|
||||
|
||||
if (ignoreNulls) {
|
||||
state.forceValue(*i->value, pos);
|
||||
state.forceValue(*i->value, noPos);
|
||||
if (i->value->type() == nNull) continue;
|
||||
}
|
||||
|
||||
if (i->name == state.sContentAddressed) {
|
||||
contentAddressed = state.forceBool(*i->value, pos,
|
||||
"while evaluating the `__contentAddressed` attribute passed to builtins.derivationStrict");
|
||||
contentAddressed = state.forceBool(*i->value, noPos, context_below);
|
||||
if (contentAddressed)
|
||||
settings.requireExperimentalFeature(Xp::CaDerivations);
|
||||
}
|
||||
|
||||
else if (i->name == state.sImpure) {
|
||||
isImpure = state.forceBool(*i->value, pos,
|
||||
"while evaluating the 'impure' attribute passed to builtins.derivationStrict");
|
||||
isImpure = state.forceBool(*i->value, noPos, context_below);
|
||||
if (isImpure)
|
||||
settings.requireExperimentalFeature(Xp::ImpureDerivations);
|
||||
}
|
||||
|
@ -1131,11 +1155,11 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
/* The `args' attribute is special: it supplies the
|
||||
command-line arguments to the builder. */
|
||||
else if (i->name == state.sArgs) {
|
||||
state.forceList(*i->value, pos,
|
||||
"while evaluating the `args` attribute passed to builtins.derivationStrict");
|
||||
state.forceList(*i->value, noPos, context_below);
|
||||
for (auto elem : i->value->listItems()) {
|
||||
auto s = state.coerceToString(posDrvName, *elem, context, true,
|
||||
"while evaluating an element of the `args` argument passed to builtins.derivationStrict").toOwned();
|
||||
auto s = state.coerceToString(noPos, *elem, context,
|
||||
"while evaluating an element of the argument list",
|
||||
true).toOwned();
|
||||
drv.args.push_back(s);
|
||||
}
|
||||
}
|
||||
|
@ -1148,29 +1172,29 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
|
||||
if (i->name == state.sStructuredAttrs) continue;
|
||||
|
||||
(*jsonObject)[key] = printValueAsJSON(state, true, *i->value, pos, context);
|
||||
(*jsonObject)[key] = printValueAsJSON(state, true, *i->value, noPos, context);
|
||||
|
||||
if (i->name == state.sBuilder)
|
||||
drv.builder = state.forceString(*i->value, context, posDrvName, "while evaluating the `builder` attribute passed to builtins.derivationStrict");
|
||||
drv.builder = state.forceString(*i->value, context, noPos, context_below);
|
||||
else if (i->name == state.sSystem)
|
||||
drv.platform = state.forceStringNoCtx(*i->value, posDrvName, "while evaluating the `system` attribute passed to builtins.derivationStrict");
|
||||
drv.platform = state.forceStringNoCtx(*i->value, noPos, context_below);
|
||||
else if (i->name == state.sOutputHash)
|
||||
outputHash = state.forceStringNoCtx(*i->value, posDrvName, "while evaluating the `outputHash` attribute passed to builtins.derivationStrict");
|
||||
outputHash = state.forceStringNoCtx(*i->value, noPos, context_below);
|
||||
else if (i->name == state.sOutputHashAlgo)
|
||||
outputHashAlgo = state.forceStringNoCtx(*i->value, posDrvName, "while evaluating the `outputHashAlgo` attribute passed to builtins.derivationStrict");
|
||||
outputHashAlgo = state.forceStringNoCtx(*i->value, noPos, context_below);
|
||||
else if (i->name == state.sOutputHashMode)
|
||||
handleHashMode(state.forceStringNoCtx(*i->value, posDrvName, "while evaluating the `outputHashMode` attribute passed to builtins.derivationStrict"));
|
||||
handleHashMode(state.forceStringNoCtx(*i->value, noPos, context_below));
|
||||
else if (i->name == state.sOutputs) {
|
||||
/* Require ‘outputs’ to be a list of strings. */
|
||||
state.forceList(*i->value, posDrvName, "while evaluating the `outputs` attribute passed to builtins.derivationStrict");
|
||||
state.forceList(*i->value, noPos, context_below);
|
||||
Strings ss;
|
||||
for (auto elem : i->value->listItems())
|
||||
ss.emplace_back(state.forceStringNoCtx(*elem, posDrvName, "while evaluating an element of the `outputs` attribute passed to builtins.derivationStrict"));
|
||||
ss.emplace_back(state.forceStringNoCtx(*elem, noPos, context_below));
|
||||
handleOutputs(ss);
|
||||
}
|
||||
|
||||
} else {
|
||||
auto s = state.coerceToString(i->pos, *i->value, context, true, "while evaluating an attribute passed to builtins.derivationStrict").toOwned();
|
||||
auto s = state.coerceToString(noPos, *i->value, context, context_below, true).toOwned();
|
||||
drv.env.emplace(key, s);
|
||||
if (i->name == state.sBuilder) drv.builder = std::move(s);
|
||||
else if (i->name == state.sSystem) drv.platform = std::move(s);
|
||||
|
@ -1184,8 +1208,8 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
}
|
||||
|
||||
} catch (Error & e) {
|
||||
e.addTrace(nullptr,
|
||||
hintfmt("while evaluating the attribute '%1%' of the derivation '%2%'", key, drvName),
|
||||
e.addTrace(state.positions[i->pos],
|
||||
hintfmt("while evaluating attribute '%1%' of derivation '%2%'", key, drvName),
|
||||
true);
|
||||
throw;
|
||||
}
|
||||
|
@ -1230,20 +1254,20 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
if (drv.builder == "")
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("required attribute 'builder' missing"),
|
||||
.errPos = state.positions[posDrvName]
|
||||
.errPos = state.positions[noPos]
|
||||
}));
|
||||
|
||||
if (drv.platform == "")
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("required attribute 'system' missing"),
|
||||
.errPos = state.positions[posDrvName]
|
||||
.errPos = state.positions[noPos]
|
||||
}));
|
||||
|
||||
/* Check whether the derivation name is valid. */
|
||||
if (isDerivation(drvName))
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("derivation names are not allowed to end in '%s'", drvExtension),
|
||||
.errPos = state.positions[posDrvName]
|
||||
.errPos = state.positions[noPos]
|
||||
}));
|
||||
|
||||
if (outputHash) {
|
||||
|
@ -1254,7 +1278,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
if (outputs.size() != 1 || *(outputs.begin()) != "out")
|
||||
state.debugThrowLastTrace(Error({
|
||||
.msg = hintfmt("multiple outputs are not supported in fixed-output derivations"),
|
||||
.errPos = state.positions[posDrvName]
|
||||
.errPos = state.positions[noPos]
|
||||
}));
|
||||
|
||||
auto h = newHashAllowEmpty(*outputHash, parseHashTypeOpt(outputHashAlgo));
|
||||
|
@ -1275,7 +1299,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
if (contentAddressed && isImpure)
|
||||
throw EvalError({
|
||||
.msg = hintfmt("derivation cannot be both content-addressed and impure"),
|
||||
.errPos = state.positions[posDrvName]
|
||||
.errPos = state.positions[noPos]
|
||||
});
|
||||
|
||||
auto ht = parseHashTypeOpt(outputHashAlgo).value_or(htSHA256);
|
||||
|
@ -1319,7 +1343,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
if (!h)
|
||||
throw AssertionError({
|
||||
.msg = hintfmt("derivation produced no hash for output '%s'", i),
|
||||
.errPos = state.positions[posDrvName],
|
||||
.errPos = state.positions[noPos],
|
||||
});
|
||||
auto outPath = state.store->makeOutputPath(i, *h, drvName);
|
||||
drv.env[i] = state.store->printStorePath(outPath);
|
||||
|
@ -1352,11 +1376,12 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
drvHashes.lock()->insert_or_assign(drvPath, h);
|
||||
}
|
||||
|
||||
auto attrs = state.buildBindings(1 + drv.outputs.size());
|
||||
attrs.alloc(state.sDrvPath).mkString(drvPathS, {"=" + drvPathS});
|
||||
auto result = state.buildBindings(1 + drv.outputs.size());
|
||||
result.alloc(state.sDrvPath).mkString(drvPathS, {"=" + drvPathS});
|
||||
for (auto & i : drv.outputs)
|
||||
mkOutputString(state, attrs, drvPath, drv, i);
|
||||
v.mkAttrs(attrs);
|
||||
mkOutputString(state, result, drvPath, drv, i);
|
||||
|
||||
v.mkAttrs(result);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_derivationStrict(RegisterPrimOp::Info {
|
||||
|
@ -1502,7 +1527,9 @@ static RegisterPrimOp primop_pathExists({
|
|||
static void prim_baseNameOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
PathSet context;
|
||||
v.mkString(baseNameOf(*state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to builtins.baseNameOf")), context);
|
||||
v.mkString(baseNameOf(*state.coerceToString(pos, *args[0], context,
|
||||
"while evaluating the first argument passed to builtins.baseNameOf",
|
||||
false, false)), context);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_baseNameOf({
|
||||
|
@ -1527,8 +1554,9 @@ static void prim_dirOf(EvalState & state, const PosIdx pos, Value * * args, Valu
|
|||
auto path = args[0]->path();
|
||||
v.mkPath(path.path.isRoot() ? path : path.parent());
|
||||
} else {
|
||||
auto path = state.coerceToString(pos, *args[0], context, false, false,
|
||||
"while evaluating the first argument passed to 'builtins.dirOf'");
|
||||
auto path = state.coerceToString(pos, *args[0], context,
|
||||
"while evaluating the first argument passed to 'builtins.dirOf'",
|
||||
false, false);
|
||||
auto dir = dirOf(*path);
|
||||
v.mkString(dir, context);
|
||||
}
|
||||
|
@ -1596,8 +1624,9 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V
|
|||
i = getAttr(state, state.sPath, v2->attrs, "in an element of the __nixPath");
|
||||
|
||||
PathSet context;
|
||||
auto path = state.coerceToString(pos, *i->value, context, false, false,
|
||||
"while evaluating the `path` attribute of an element of the list passed to builtins.findFile").toOwned();
|
||||
auto path = state.coerceToString(pos, *i->value, context,
|
||||
"while evaluating the `path` attribute of an element of the list passed to builtins.findFile",
|
||||
false, false).toOwned();
|
||||
|
||||
try {
|
||||
auto rewrites = state.realiseContext(context);
|
||||
|
@ -1660,18 +1689,56 @@ static std::string_view fileTypeToString(InputAccessor::Type type)
|
|||
"unknown";
|
||||
}
|
||||
|
||||
static void prim_readFileType(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
auto path = realisePath(state, pos, *args[0]);
|
||||
/* Retrieve the directory entry type and stringize it. */
|
||||
v.mkString(fileTypeToString(path.lstat().type));
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_readFileType({
|
||||
.name = "__readFileType",
|
||||
.args = {"p"},
|
||||
.doc = R"(
|
||||
Determine the directory entry type of a filesystem node, being
|
||||
one of "directory", "regular", "symlink", or "unknown".
|
||||
)",
|
||||
.fun = prim_readFileType,
|
||||
});
|
||||
|
||||
/* Read a directory (without . or ..) */
|
||||
static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
auto path = realisePath(state, pos, *args[0]);
|
||||
|
||||
// Retrieve directory entries for all nodes in a directory.
|
||||
// This is similar to `getFileType` but is optimized to reduce system calls
|
||||
// on many systems.
|
||||
auto entries = path.readDirectory();
|
||||
auto attrs = state.buildBindings(entries.size());
|
||||
|
||||
// If we hit unknown directory entry types we may need to fallback to
|
||||
// using `getFileType` on some systems.
|
||||
// In order to reduce system calls we make each lookup lazy by using
|
||||
// `builtins.readFileType` application.
|
||||
Value * readFileType = nullptr;
|
||||
|
||||
for (auto & [name, type] : entries) {
|
||||
if (!type)
|
||||
type = (path + name).lstat().type;
|
||||
attrs.alloc(name).mkString(fileTypeToString(*type));
|
||||
auto & attr = attrs.alloc(name);
|
||||
if (!type) {
|
||||
// Some filesystems or operating systems may not be able to return
|
||||
// detailed node info quickly in this case we produce a thunk to
|
||||
// query the file type lazily.
|
||||
auto epath = state.allocValue();
|
||||
epath->mkPath(path + name);
|
||||
if (!readFileType)
|
||||
readFileType = &state.getBuiltin("readFileType");
|
||||
attr.mkApp(readFileType, epath);
|
||||
} else {
|
||||
// This branch of the conditional is much more likely.
|
||||
// Here we just stringize the directory entry type.
|
||||
attr.mkString(fileTypeToString(*type));
|
||||
}
|
||||
}
|
||||
|
||||
v.mkAttrs(attrs);
|
||||
|
@ -2665,14 +2732,9 @@ static void prim_zipAttrsWith(EvalState & state, const PosIdx pos, Value * * arg
|
|||
|
||||
for (unsigned int n = 0; n < listSize; ++n) {
|
||||
Value * vElem = listElems[n];
|
||||
try {
|
||||
state.forceAttrs(*vElem, noPos, "while evaluating a value of the list passed as second argument to builtins.zipAttrsWith");
|
||||
for (auto & attr : *vElem->attrs)
|
||||
attrsSeen[attr.name].first++;
|
||||
} catch (TypeError & e) {
|
||||
e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", "zipAttrsWith"));
|
||||
state.debugThrowLastTrace(e);
|
||||
}
|
||||
state.forceAttrs(*vElem, noPos, "while evaluating a value of the list passed as second argument to builtins.zipAttrsWith");
|
||||
for (auto & attr : *vElem->attrs)
|
||||
attrsSeen[attr.name].first++;
|
||||
}
|
||||
|
||||
auto attrs = state.buildBindings(attrsSeen.size());
|
||||
|
@ -3056,13 +3118,13 @@ static void prim_genList(EvalState & state, const PosIdx pos, Value * * args, Va
|
|||
auto len = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.genList");
|
||||
|
||||
if (len < 0)
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("cannot create list of size %1%", len),
|
||||
.errPos = state.positions[pos]
|
||||
}));
|
||||
state.error("cannot create list of size %1%", len).debugThrow<EvalError>();
|
||||
|
||||
// More strict than striclty (!) necessary, but acceptable
|
||||
// as evaluating map without accessing any values makes little sense.
|
||||
state.forceFunction(*args[0], noPos, "while evaluating the first argument passed to builtins.genList");
|
||||
|
||||
state.mkList(v, len);
|
||||
|
||||
for (unsigned int n = 0; n < (unsigned int) len; ++n) {
|
||||
auto arg = state.allocValue();
|
||||
arg->mkInt(n);
|
||||
|
@ -3110,6 +3172,8 @@ static void prim_sort(EvalState & state, const PosIdx pos, Value * * args, Value
|
|||
auto comparator = [&](Value * a, Value * b) {
|
||||
/* Optimization: if the comparator is lessThan, bypass
|
||||
callFunction. */
|
||||
/* TODO: (layus) this is absurd. An optimisation like this
|
||||
should be outside the lambda creation */
|
||||
if (args[0]->isPrimOp() && args[0]->primOp->fun == prim_lessThan)
|
||||
return CompareValues(state, noPos, "while evaluating the ordering function passed to builtins.sort")(a, b);
|
||||
|
||||
|
@ -3270,12 +3334,7 @@ static void prim_concatMap(EvalState & state, const PosIdx pos, Value * * args,
|
|||
for (unsigned int n = 0; n < nrLists; ++n) {
|
||||
Value * vElem = args[1]->listElems()[n];
|
||||
state.callFunction(*args[0], *vElem, lists[n], pos);
|
||||
try {
|
||||
state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos)), "while evaluating the return value of the function passed to buitlins.concatMap");
|
||||
} catch (TypeError &e) {
|
||||
e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", "concatMap"));
|
||||
state.debugThrowLastTrace(e);
|
||||
}
|
||||
state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos)), "while evaluating the return value of the function passed to buitlins.concatMap");
|
||||
len += lists[n].listSize();
|
||||
}
|
||||
|
||||
|
@ -3455,7 +3514,7 @@ static void prim_lessThan(EvalState & state, const PosIdx pos, Value * * args, V
|
|||
state.forceValue(*args[0], pos);
|
||||
state.forceValue(*args[1], pos);
|
||||
// pos is exact here, no need for a message.
|
||||
CompareValues comp(state, pos, "");
|
||||
CompareValues comp(state, noPos, "");
|
||||
v.mkBool(comp(args[0], args[1]));
|
||||
}
|
||||
|
||||
|
@ -3482,7 +3541,9 @@ static RegisterPrimOp primop_lessThan({
|
|||
static void prim_toString(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
PathSet context;
|
||||
auto s = state.coerceToString(pos, *args[0], context, true, false, "while evaluating the first argument passed to builtins.toString");
|
||||
auto s = state.coerceToString(pos, *args[0], context,
|
||||
"while evaluating the first argument passed to builtins.toString",
|
||||
true, false);
|
||||
v.mkString(*s, context);
|
||||
}
|
||||
|
||||
|
@ -3834,21 +3895,18 @@ static void prim_replaceStrings(EvalState & state, const PosIdx pos, Value * * a
|
|||
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.replaceStrings");
|
||||
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.replaceStrings");
|
||||
if (args[0]->listSize() != args[1]->listSize())
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("'from' and 'to' arguments to 'replaceStrings' have different lengths"),
|
||||
.errPos = state.positions[pos]
|
||||
}));
|
||||
state.error("'from' and 'to' arguments passed to builtins.replaceStrings have different lengths").atPos(pos).debugThrow<EvalError>();
|
||||
|
||||
std::vector<std::string> from;
|
||||
from.reserve(args[0]->listSize());
|
||||
for (auto elem : args[0]->listItems())
|
||||
from.emplace_back(state.forceString(*elem, pos, "while evaluating one of the strings to replace in builtins.replaceStrings"));
|
||||
from.emplace_back(state.forceString(*elem, pos, "while evaluating one of the strings to replace passed to builtins.replaceStrings"));
|
||||
|
||||
std::vector<std::pair<std::string, PathSet>> to;
|
||||
to.reserve(args[1]->listSize());
|
||||
for (auto elem : args[1]->listItems()) {
|
||||
PathSet ctx;
|
||||
auto s = state.forceString(*elem, ctx, pos, "while evaluating one of the replacement strings of builtins.replaceStrings");
|
||||
auto s = state.forceString(*elem, ctx, pos, "while evaluating one of the replacement strings passed to builtins.replaceStrings");
|
||||
to.emplace_back(s, std::move(ctx));
|
||||
}
|
||||
|
||||
|
|
|
@ -83,15 +83,13 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
|
|||
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getContext");
|
||||
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;
|
||||
|
|
|
@ -22,7 +22,9 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
|
|||
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,
|
||||
"while evaluating the `url` attribute passed to builtins.fetchMercurial",
|
||||
false, false).toOwned();
|
||||
else if (n == "rev") {
|
||||
// Ugly: unlike fetchGit, here the "rev" attribute can
|
||||
// be both a revision or a branch/tag name.
|
||||
|
@ -48,7 +50,9 @@ 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,
|
||||
"while evaluating the first argument passed to builtins.fetchMercurial",
|
||||
false, false).toOwned();
|
||||
|
||||
// FIXME: git externals probably can be used to bypass the URI
|
||||
// whitelist. Ah well.
|
||||
|
|
|
@ -140,7 +140,7 @@ static void fetchTree(
|
|||
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"
|
||||
|
@ -166,7 +166,9 @@ 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,
|
||||
"while evaluating the first argument passed to the fetcher",
|
||||
false, false).toOwned();
|
||||
|
||||
if (type == "git") {
|
||||
fetchers::Attrs attrs;
|
||||
|
@ -255,6 +257,9 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
|
|||
} else
|
||||
url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch");
|
||||
|
||||
if (who == "fetchTarball")
|
||||
url = evalSettings.resolvePseudoUrl(*url);
|
||||
|
||||
state.checkURI(*url);
|
||||
|
||||
if (name == "")
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -90,7 +90,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.
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "types.hh"
|
||||
#include "split.hh"
|
||||
#include "fs-input-accessor.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
namespace nix::fetchers {
|
||||
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
#include "types.hh"
|
||||
#include "path.hh"
|
||||
|
||||
namespace nix {
|
||||
class Store;
|
||||
}
|
||||
|
||||
namespace nix::fetchers {
|
||||
|
||||
struct DownloadFileResult
|
||||
|
|
|
@ -502,22 +502,9 @@ void BinaryCacheStore::addSignatures(const StorePath & storePath, const StringSe
|
|||
writeNarInfo(narInfo);
|
||||
}
|
||||
|
||||
std::optional<std::string> BinaryCacheStore::getBuildLog(const StorePath & path)
|
||||
std::optional<std::string> BinaryCacheStore::getBuildLogExact(const StorePath & path)
|
||||
{
|
||||
auto drvPath = path;
|
||||
|
||||
if (!path.isDerivation()) {
|
||||
try {
|
||||
auto info = queryPathInfo(path);
|
||||
// FIXME: add a "Log" field to .narinfo
|
||||
if (!info->deriver) return std::nullopt;
|
||||
drvPath = *info->deriver;
|
||||
} catch (InvalidPath &) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
auto logPath = "log/" + std::string(baseNameOf(printStorePath(drvPath)));
|
||||
auto logPath = "log/" + std::string(baseNameOf(printStorePath(path)));
|
||||
|
||||
debug("fetching build log from binary cache '%s/%s'", getUri(), logPath);
|
||||
|
||||
|
|
|
@ -129,7 +129,7 @@ public:
|
|||
|
||||
void addSignatures(const StorePath & storePath, const StringSet & sigs) override;
|
||||
|
||||
std::optional<std::string> getBuildLog(const StorePath & path) override;
|
||||
std::optional<std::string> getBuildLogExact(const StorePath & path) override;
|
||||
|
||||
void addBuildLog(const StorePath & drvPath, std::string_view log) override;
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
namespace nix {
|
||||
|
||||
DerivationGoal::DerivationGoal(const StorePath & drvPath,
|
||||
const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode)
|
||||
const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
|
||||
: Goal(worker, DerivedPath::Built { .drvPath = drvPath, .outputs = wantedOutputs })
|
||||
, useDerivation(true)
|
||||
, drvPath(drvPath)
|
||||
|
@ -82,7 +82,7 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath,
|
|||
|
||||
|
||||
DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
|
||||
const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode)
|
||||
const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
|
||||
: Goal(worker, DerivedPath::Built { .drvPath = drvPath, .outputs = wantedOutputs })
|
||||
, useDerivation(false)
|
||||
, drvPath(drvPath)
|
||||
|
@ -142,18 +142,12 @@ void DerivationGoal::work()
|
|||
(this->*state)();
|
||||
}
|
||||
|
||||
void DerivationGoal::addWantedOutputs(const StringSet & outputs)
|
||||
void DerivationGoal::addWantedOutputs(const OutputsSpec & outputs)
|
||||
{
|
||||
/* If we already want all outputs, there is nothing to do. */
|
||||
if (wantedOutputs.empty()) return;
|
||||
|
||||
if (outputs.empty()) {
|
||||
wantedOutputs.clear();
|
||||
auto newWanted = wantedOutputs.union_(outputs);
|
||||
if (!newWanted.isSubsetOf(wantedOutputs))
|
||||
needRestart = true;
|
||||
} else
|
||||
for (auto & i : outputs)
|
||||
if (wantedOutputs.insert(i).second)
|
||||
needRestart = true;
|
||||
wantedOutputs = newWanted;
|
||||
}
|
||||
|
||||
|
||||
|
@ -390,7 +384,7 @@ void DerivationGoal::repairClosure()
|
|||
auto outputs = queryDerivationOutputMap();
|
||||
StorePathSet outputClosure;
|
||||
for (auto & i : outputs) {
|
||||
if (!wantOutput(i.first, wantedOutputs)) continue;
|
||||
if (!wantedOutputs.contains(i.first)) continue;
|
||||
worker.store.computeFSClosure(i.second, outputClosure);
|
||||
}
|
||||
|
||||
|
@ -422,7 +416,7 @@ void DerivationGoal::repairClosure()
|
|||
if (drvPath2 == outputsToDrv.end())
|
||||
addWaitee(upcast_goal(worker.makePathSubstitutionGoal(i, Repair)));
|
||||
else
|
||||
addWaitee(worker.makeDerivationGoal(drvPath2->second, StringSet(), bmRepair));
|
||||
addWaitee(worker.makeDerivationGoal(drvPath2->second, OutputsSpec::All(), bmRepair));
|
||||
}
|
||||
|
||||
if (waitees.empty()) {
|
||||
|
@ -991,10 +985,15 @@ void DerivationGoal::resolvedFinished()
|
|||
|
||||
StorePathSet outputPaths;
|
||||
|
||||
// `wantedOutputs` might be empty, which means “all the outputs”
|
||||
auto realWantedOutputs = wantedOutputs;
|
||||
if (realWantedOutputs.empty())
|
||||
realWantedOutputs = resolvedDrv.outputNames();
|
||||
// `wantedOutputs` might merely indicate “all the outputs”
|
||||
auto realWantedOutputs = std::visit(overloaded {
|
||||
[&](const OutputsSpec::All &) {
|
||||
return resolvedDrv.outputNames();
|
||||
},
|
||||
[&](const OutputsSpec::Names & names) {
|
||||
return static_cast<std::set<std::string>>(names);
|
||||
},
|
||||
}, wantedOutputs.raw());
|
||||
|
||||
for (auto & wantedOutput : realWantedOutputs) {
|
||||
auto initialOutput = get(initialOutputs, wantedOutput);
|
||||
|
@ -1322,7 +1321,14 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
|
|||
if (!drv->type().isPure()) return { false, {} };
|
||||
|
||||
bool checkHash = buildMode == bmRepair;
|
||||
auto wantedOutputsLeft = wantedOutputs;
|
||||
auto wantedOutputsLeft = std::visit(overloaded {
|
||||
[&](const OutputsSpec::All &) {
|
||||
return StringSet {};
|
||||
},
|
||||
[&](const OutputsSpec::Names & names) {
|
||||
return static_cast<StringSet>(names);
|
||||
},
|
||||
}, wantedOutputs.raw());
|
||||
DrvOutputs validOutputs;
|
||||
|
||||
for (auto & i : queryPartialDerivationOutputMap()) {
|
||||
|
@ -1331,7 +1337,7 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
|
|||
// this is an invalid output, gets catched with (!wantedOutputsLeft.empty())
|
||||
continue;
|
||||
auto & info = *initialOutput;
|
||||
info.wanted = wantOutput(i.first, wantedOutputs);
|
||||
info.wanted = wantedOutputs.contains(i.first);
|
||||
if (info.wanted)
|
||||
wantedOutputsLeft.erase(i.first);
|
||||
if (i.second) {
|
||||
|
@ -1369,7 +1375,7 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
|
|||
validOutputs.emplace(drvOutput, Realisation { drvOutput, info.known->path });
|
||||
}
|
||||
|
||||
// If we requested all the outputs via the empty set, we are always fine.
|
||||
// If we requested all the outputs, we are always fine.
|
||||
// If we requested specific elements, the loop above removes all the valid
|
||||
// ones, so any that are left must be invalid.
|
||||
if (!wantedOutputsLeft.empty())
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "parsed-derivations.hh"
|
||||
#include "lock.hh"
|
||||
#include "outputs-spec.hh"
|
||||
#include "store-api.hh"
|
||||
#include "pathlocks.hh"
|
||||
#include "goal.hh"
|
||||
|
@ -55,7 +56,7 @@ struct DerivationGoal : public Goal
|
|||
|
||||
/* The specific outputs that we need to build. Empty means all of
|
||||
them. */
|
||||
StringSet wantedOutputs;
|
||||
OutputsSpec wantedOutputs;
|
||||
|
||||
/* Mapping from input derivations + output names to actual store
|
||||
paths. This is filled in by waiteeDone() as each dependency
|
||||
|
@ -128,10 +129,10 @@ struct DerivationGoal : public Goal
|
|||
std::string machineName;
|
||||
|
||||
DerivationGoal(const StorePath & drvPath,
|
||||
const StringSet & wantedOutputs, Worker & worker,
|
||||
const OutputsSpec & wantedOutputs, Worker & worker,
|
||||
BuildMode buildMode = bmNormal);
|
||||
DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
|
||||
const StringSet & wantedOutputs, Worker & worker,
|
||||
const OutputsSpec & wantedOutputs, Worker & worker,
|
||||
BuildMode buildMode = bmNormal);
|
||||
virtual ~DerivationGoal();
|
||||
|
||||
|
@ -142,7 +143,7 @@ struct DerivationGoal : public Goal
|
|||
void work() override;
|
||||
|
||||
/* Add wanted outputs to an already existing derivation goal. */
|
||||
void addWantedOutputs(const StringSet & outputs);
|
||||
void addWantedOutputs(const OutputsSpec & outputs);
|
||||
|
||||
/* The states. */
|
||||
void getDerivation();
|
||||
|
|
|
@ -80,7 +80,7 @@ BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivat
|
|||
BuildMode buildMode)
|
||||
{
|
||||
Worker worker(*this, *this);
|
||||
auto goal = worker.makeBasicDerivationGoal(drvPath, drv, {}, buildMode);
|
||||
auto goal = worker.makeBasicDerivationGoal(drvPath, drv, OutputsSpec::All {}, buildMode);
|
||||
|
||||
try {
|
||||
worker.run(Goals{goal});
|
||||
|
@ -89,7 +89,10 @@ BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivat
|
|||
return BuildResult {
|
||||
.status = BuildResult::MiscFailure,
|
||||
.errorMsg = e.msg(),
|
||||
.path = DerivedPath::Built { .drvPath = drvPath },
|
||||
.path = DerivedPath::Built {
|
||||
.drvPath = drvPath,
|
||||
.outputs = OutputsSpec::All { },
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -130,7 +133,8 @@ void LocalStore::repairPath(const StorePath & path)
|
|||
auto info = queryPathInfo(path);
|
||||
if (info->deriver && isValidPath(*info->deriver)) {
|
||||
goals.clear();
|
||||
goals.insert(worker.makeDerivationGoal(*info->deriver, StringSet(), bmRepair));
|
||||
// FIXME: Should just build the specific output we need.
|
||||
goals.insert(worker.makeDerivationGoal(*info->deriver, OutputsSpec::All { }, bmRepair));
|
||||
worker.run(goals);
|
||||
} else
|
||||
throw Error(worker.exitStatus(), "cannot repair path '%s'", printStorePath(path));
|
||||
|
|
|
@ -1459,7 +1459,7 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
|
|||
unknown, downloadSize, narSize);
|
||||
}
|
||||
|
||||
virtual std::optional<std::string> getBuildLog(const StorePath & path) override
|
||||
virtual std::optional<std::string> getBuildLogExact(const StorePath & path) override
|
||||
{ return std::nullopt; }
|
||||
|
||||
virtual void addBuildLog(const StorePath & path, std::string_view log) override
|
||||
|
@ -2735,7 +2735,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
signRealisation(thisRealisation);
|
||||
worker.store.registerDrvOutput(thisRealisation);
|
||||
}
|
||||
if (wantOutput(outputName, wantedOutputs))
|
||||
if (wantedOutputs.contains(outputName))
|
||||
builtOutputs.emplace(thisRealisation.id, thisRealisation);
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ Worker::~Worker()
|
|||
|
||||
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon(
|
||||
const StorePath & drvPath,
|
||||
const StringSet & wantedOutputs,
|
||||
const OutputsSpec & wantedOutputs,
|
||||
std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal)
|
||||
{
|
||||
std::weak_ptr<DerivationGoal> & goal_weak = derivationGoals[drvPath];
|
||||
|
@ -59,7 +59,7 @@ std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon(
|
|||
|
||||
|
||||
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(const StorePath & drvPath,
|
||||
const StringSet & wantedOutputs, BuildMode buildMode)
|
||||
const OutputsSpec & wantedOutputs, BuildMode buildMode)
|
||||
{
|
||||
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
|
||||
return !dynamic_cast<LocalStore *>(&store)
|
||||
|
@ -70,7 +70,7 @@ std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(const StorePath & drv
|
|||
|
||||
|
||||
std::shared_ptr<DerivationGoal> Worker::makeBasicDerivationGoal(const StorePath & drvPath,
|
||||
const BasicDerivation & drv, const StringSet & wantedOutputs, BuildMode buildMode)
|
||||
const BasicDerivation & drv, const OutputsSpec & wantedOutputs, BuildMode buildMode)
|
||||
{
|
||||
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
|
||||
return !dynamic_cast<LocalStore *>(&store)
|
||||
|
|
|
@ -140,15 +140,15 @@ public:
|
|||
/* derivation goal */
|
||||
private:
|
||||
std::shared_ptr<DerivationGoal> makeDerivationGoalCommon(
|
||||
const StorePath & drvPath, const StringSet & wantedOutputs,
|
||||
const StorePath & drvPath, const OutputsSpec & wantedOutputs,
|
||||
std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal);
|
||||
public:
|
||||
std::shared_ptr<DerivationGoal> makeDerivationGoal(
|
||||
const StorePath & drvPath,
|
||||
const StringSet & wantedOutputs, BuildMode buildMode = bmNormal);
|
||||
const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal);
|
||||
std::shared_ptr<DerivationGoal> makeBasicDerivationGoal(
|
||||
const StorePath & drvPath, const BasicDerivation & drv,
|
||||
const StringSet & wantedOutputs, BuildMode buildMode = bmNormal);
|
||||
const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal);
|
||||
|
||||
/* substitution goal */
|
||||
std::shared_ptr<PathSubstitutionGoal> makePathSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
||||
|
|
|
@ -222,7 +222,8 @@ struct ClientSettings
|
|||
else if (!hasSuffix(s, "/") && trusted.count(s + "/"))
|
||||
subs.push_back(s + "/");
|
||||
else
|
||||
warn("ignoring untrusted substituter '%s'", s);
|
||||
warn("ignoring untrusted substituter '%s', you are not a trusted user.\n"
|
||||
"Run `man nix.conf` for more information on the `substituters` configuration option.", s);
|
||||
res = subs;
|
||||
return true;
|
||||
};
|
||||
|
|
|
@ -688,12 +688,6 @@ std::map<std::string, Hash> staticOutputHashes(Store & store, const Derivation &
|
|||
}
|
||||
|
||||
|
||||
bool wantOutput(const std::string & output, const std::set<std::string> & wanted)
|
||||
{
|
||||
return wanted.empty() || wanted.find(output) != wanted.end();
|
||||
}
|
||||
|
||||
|
||||
static DerivationOutput readDerivationOutput(Source & in, const Store & store)
|
||||
{
|
||||
const auto pathS = readString(in);
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
class Store;
|
||||
|
||||
/* Abstract syntax of derivations. */
|
||||
|
||||
|
@ -294,8 +295,6 @@ typedef std::map<StorePath, DrvHash> DrvHashes;
|
|||
// FIXME: global, though at least thread-safe.
|
||||
extern Sync<DrvHashes> drvHashes;
|
||||
|
||||
bool wantOutput(const std::string & output, const std::set<std::string> & wanted);
|
||||
|
||||
struct Source;
|
||||
struct Sink;
|
||||
|
||||
|
|
|
@ -19,11 +19,11 @@ nlohmann::json DerivedPath::Built::toJSON(ref<Store> store) const {
|
|||
res["drvPath"] = store->printStorePath(drvPath);
|
||||
// Fallback for the input-addressed derivation case: We expect to always be
|
||||
// able to print the output paths, so let’s do it
|
||||
const auto knownOutputs = store->queryPartialDerivationOutputMap(drvPath);
|
||||
for (const auto & output : outputs) {
|
||||
auto knownOutput = get(knownOutputs, output);
|
||||
if (knownOutput && *knownOutput)
|
||||
res["outputs"][output] = store->printStorePath(**knownOutput);
|
||||
const auto outputMap = store->queryPartialDerivationOutputMap(drvPath);
|
||||
for (const auto & [output, outputPathOpt] : outputMap) {
|
||||
if (!outputs.contains(output)) continue;
|
||||
if (outputPathOpt)
|
||||
res["outputs"][output] = store->printStorePath(*outputPathOpt);
|
||||
else
|
||||
res["outputs"][output] = nullptr;
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ std::string DerivedPath::Built::to_string(const Store & store) const
|
|||
{
|
||||
return store.printStorePath(drvPath)
|
||||
+ "!"
|
||||
+ (outputs.empty() ? std::string { "*" } : concatStringsSep(",", outputs));
|
||||
+ outputs.to_string();
|
||||
}
|
||||
|
||||
std::string DerivedPath::to_string(const Store & store) const
|
||||
|
@ -81,15 +81,10 @@ DerivedPath::Opaque DerivedPath::Opaque::parse(const Store & store, std::string_
|
|||
|
||||
DerivedPath::Built DerivedPath::Built::parse(const Store & store, std::string_view drvS, std::string_view outputsS)
|
||||
{
|
||||
auto drvPath = store.parseStorePath(drvS);
|
||||
std::set<std::string> outputs;
|
||||
if (outputsS != "*") {
|
||||
outputs = tokenizeString<std::set<std::string>>(outputsS, ",");
|
||||
if (outputs.empty())
|
||||
throw Error(
|
||||
"Explicit list of wanted outputs '%s' must not be empty. Consider using '*' as a wildcard meaning all outputs if no output in particular is wanted.", outputsS);
|
||||
}
|
||||
return {drvPath, outputs};
|
||||
return {
|
||||
.drvPath = store.parseStorePath(drvS),
|
||||
.outputs = OutputsSpec::parse(outputsS),
|
||||
};
|
||||
}
|
||||
|
||||
DerivedPath DerivedPath::parse(const Store & store, std::string_view s)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "util.hh"
|
||||
#include "path.hh"
|
||||
#include "realisation.hh"
|
||||
#include "outputs-spec.hh"
|
||||
|
||||
#include <optional>
|
||||
|
||||
|
@ -44,7 +45,7 @@ struct DerivedPathOpaque {
|
|||
*/
|
||||
struct DerivedPathBuilt {
|
||||
StorePath drvPath;
|
||||
std::set<std::string> outputs;
|
||||
OutputsSpec outputs;
|
||||
|
||||
std::string to_string(const Store & store) const;
|
||||
static DerivedPathBuilt parse(const Store & store, std::string_view, std::string_view);
|
||||
|
|
|
@ -570,11 +570,15 @@ public:
|
|||
{"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="},
|
||||
"trusted-public-keys",
|
||||
R"(
|
||||
A whitespace-separated list of public keys. When paths are copied
|
||||
from another Nix store (such as a binary cache), they must be
|
||||
signed with one of these keys. For example:
|
||||
`cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=
|
||||
hydra.nixos.org-1:CNHJZBh9K4tP3EKF6FkkgeVYsS3ohTl+oS0Qa8bezVs=`.
|
||||
A whitespace-separated list of public keys.
|
||||
|
||||
At least one of the following condition must be met
|
||||
for Nix to accept copying a store object from another
|
||||
Nix store (such as a substituter):
|
||||
|
||||
- the store object has been signed using a key in the trusted keys list
|
||||
- the [`require-sigs`](#conf-require-sigs) option has been set to `false`
|
||||
- the store object is [output-addressed](@docroot@/glossary.md#gloss-output-addressed-store-object)
|
||||
)",
|
||||
{"binary-cache-public-keys"}};
|
||||
|
||||
|
@ -670,13 +674,14 @@ public:
|
|||
independently. Lower value means higher priority.
|
||||
The default is `https://cache.nixos.org`, with a Priority of 40.
|
||||
|
||||
Nix will copy a store path from a remote store only if one
|
||||
of the following is true:
|
||||
At least one of the following conditions must be met for Nix to use
|
||||
a substituter:
|
||||
|
||||
- the store object is signed by one of the [`trusted-public-keys`](#conf-trusted-public-keys)
|
||||
- the substituter is in the [`trusted-substituters`](#conf-trusted-substituters) list
|
||||
- the [`require-sigs`](#conf-require-sigs) option has been set to `false`
|
||||
- the store object is [output-addressed](@docroot@/glossary.md#gloss-output-addressed-store-object)
|
||||
- the user calling Nix is in the [`trusted-users`](#conf-trusted-users) list
|
||||
|
||||
In addition, each store path should be trusted as described
|
||||
in [`trusted-public-keys`](#conf-trusted-public-keys)
|
||||
)",
|
||||
{"binary-caches"}};
|
||||
|
||||
|
|
|
@ -279,7 +279,12 @@ public:
|
|||
|
||||
conn->to.flush();
|
||||
|
||||
BuildResult status { .path = DerivedPath::Built { .drvPath = drvPath } };
|
||||
BuildResult status {
|
||||
.path = DerivedPath::Built {
|
||||
.drvPath = drvPath,
|
||||
.outputs = OutputsSpec::All { },
|
||||
},
|
||||
};
|
||||
status.status = (BuildResult::Status) readInt(conn->from);
|
||||
conn->from >> status.errorMsg;
|
||||
|
||||
|
|
|
@ -87,20 +87,8 @@ void LocalFSStore::narFromPath(const StorePath & path, Sink & sink)
|
|||
|
||||
const std::string LocalFSStore::drvsLogDir = "drvs";
|
||||
|
||||
std::optional<std::string> LocalFSStore::getBuildLog(const StorePath & path_)
|
||||
std::optional<std::string> LocalFSStore::getBuildLogExact(const StorePath & path)
|
||||
{
|
||||
auto path = path_;
|
||||
|
||||
if (!path.isDerivation()) {
|
||||
try {
|
||||
auto info = queryPathInfo(path);
|
||||
if (!info->deriver) return std::nullopt;
|
||||
path = *info->deriver;
|
||||
} catch (InvalidPath &) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
auto baseName = path.to_string();
|
||||
|
||||
for (int j = 0; j < 2; j++) {
|
||||
|
|
|
@ -50,7 +50,7 @@ public:
|
|||
return getRealStoreDir() + "/" + std::string(storePath, storeDir.size() + 1);
|
||||
}
|
||||
|
||||
std::optional<std::string> getBuildLog(const StorePath & path) override;
|
||||
std::optional<std::string> getBuildLogExact(const StorePath & path) override;
|
||||
|
||||
};
|
||||
|
||||
|
|
12
src/libstore/log-store.cc
Normal file
12
src/libstore/log-store.cc
Normal file
|
@ -0,0 +1,12 @@
|
|||
#include "log-store.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
std::optional<std::string> LogStore::getBuildLog(const StorePath & path) {
|
||||
auto maybePath = getBuildDerivationPath(path);
|
||||
if (!maybePath)
|
||||
return std::nullopt;
|
||||
return getBuildLogExact(maybePath.value());
|
||||
}
|
||||
|
||||
}
|
|
@ -11,7 +11,9 @@ struct LogStore : public virtual Store
|
|||
|
||||
/* Return the build log of the specified store path, if available,
|
||||
or null otherwise. */
|
||||
virtual std::optional<std::string> getBuildLog(const StorePath & path) = 0;
|
||||
std::optional<std::string> getBuildLog(const StorePath & path);
|
||||
|
||||
virtual std::optional<std::string> getBuildLogExact(const StorePath & path) = 0;
|
||||
|
||||
virtual void addBuildLog(const StorePath & path, std::string_view log) = 0;
|
||||
|
||||
|
|
|
@ -185,7 +185,7 @@ void Store::queryMissing(const std::vector<DerivedPath> & targets,
|
|||
knownOutputPaths = false;
|
||||
break;
|
||||
}
|
||||
if (wantOutput(outputName, bfd.outputs) && !isValidPath(*pathOpt))
|
||||
if (bfd.outputs.contains(outputName) && !isValidPath(*pathOpt))
|
||||
invalid.insert(*pathOpt);
|
||||
}
|
||||
if (knownOutputPaths && invalid.empty()) return;
|
||||
|
@ -301,4 +301,47 @@ std::map<DrvOutput, StorePath> drvOutputReferences(
|
|||
return drvOutputReferences(Realisation::closure(store, inputRealisations), info->references);
|
||||
}
|
||||
|
||||
OutputPathMap resolveDerivedPath(Store & store, const DerivedPath::Built & bfd, Store * evalStore_)
|
||||
{
|
||||
auto & evalStore = evalStore_ ? *evalStore_ : store;
|
||||
|
||||
OutputPathMap outputs;
|
||||
auto drv = evalStore.readDerivation(bfd.drvPath);
|
||||
auto outputHashes = staticOutputHashes(store, drv);
|
||||
auto drvOutputs = drv.outputsAndOptPaths(store);
|
||||
auto outputNames = std::visit(overloaded {
|
||||
[&](const OutputsSpec::All &) {
|
||||
StringSet names;
|
||||
for (auto & [outputName, _] : drv.outputs)
|
||||
names.insert(outputName);
|
||||
return names;
|
||||
},
|
||||
[&](const OutputsSpec::Names & names) {
|
||||
return static_cast<std::set<std::string>>(names);
|
||||
},
|
||||
}, bfd.outputs.raw());
|
||||
for (auto & output : outputNames) {
|
||||
auto outputHash = get(outputHashes, output);
|
||||
if (!outputHash)
|
||||
throw Error(
|
||||
"the derivation '%s' doesn't have an output named '%s'",
|
||||
store.printStorePath(bfd.drvPath), output);
|
||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
||||
DrvOutput outputId { *outputHash, output };
|
||||
auto realisation = store.queryRealisation(outputId);
|
||||
if (!realisation)
|
||||
throw MissingRealisation(outputId);
|
||||
outputs.insert_or_assign(output, realisation->outPath);
|
||||
} else {
|
||||
// If ca-derivations isn't enabled, assume that
|
||||
// the output path is statically known.
|
||||
auto drvOutput = get(drvOutputs, output);
|
||||
assert(drvOutput);
|
||||
assert(drvOutput->second);
|
||||
outputs.insert_or_assign(output, *drvOutput->second);
|
||||
}
|
||||
}
|
||||
return outputs;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,61 +1,195 @@
|
|||
#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 {
|
||||
|
||||
std::pair<std::string, OutputsSpec> parseOutputsSpec(const std::string & s)
|
||||
bool OutputsSpec::contains(const std::string & outputName) const
|
||||
{
|
||||
static std::regex regex(R"((.*)\^((\*)|([a-z]+(,[a-z]+)*)))");
|
||||
|
||||
std::smatch match;
|
||||
if (!std::regex_match(s, match, regex))
|
||||
return {s, DefaultOutputs()};
|
||||
|
||||
if (match[3].matched)
|
||||
return {match[1], AllOutputs()};
|
||||
|
||||
return {match[1], tokenizeString<OutputNames>(match[4].str(), ",")};
|
||||
return std::visit(overloaded {
|
||||
[&](const OutputsSpec::All &) {
|
||||
return true;
|
||||
},
|
||||
[&](const OutputsSpec::Names & outputNames) {
|
||||
return outputNames.count(outputName) > 0;
|
||||
},
|
||||
}, raw());
|
||||
}
|
||||
|
||||
std::string printOutputsSpec(const OutputsSpec & outputsSpec)
|
||||
static std::string outputSpecRegexStr =
|
||||
regex::either(
|
||||
regex::group(R"(\*)"),
|
||||
regex::group(regex::list(nameRegexStr)));
|
||||
|
||||
std::optional<OutputsSpec> OutputsSpec::parseOpt(std::string_view s)
|
||||
{
|
||||
if (std::get_if<DefaultOutputs>(&outputsSpec))
|
||||
return "";
|
||||
static std::regex regex(std::string { outputSpecRegexStr });
|
||||
|
||||
if (std::get_if<AllOutputs>(&outputsSpec))
|
||||
return "^*";
|
||||
std::smatch match;
|
||||
std::string s2 { s }; // until some improves std::regex
|
||||
if (!std::regex_match(s2, match, regex))
|
||||
return std::nullopt;
|
||||
|
||||
if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
|
||||
return "^" + concatStringsSep(",", *outputNames);
|
||||
if (match[1].matched)
|
||||
return { OutputsSpec::All {} };
|
||||
|
||||
if (match[2].matched)
|
||||
return OutputsSpec::Names { tokenizeString<StringSet>(match[2].str(), ",") };
|
||||
|
||||
assert(false);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json & json, const OutputsSpec & outputsSpec)
|
||||
|
||||
OutputsSpec OutputsSpec::parse(std::string_view s)
|
||||
{
|
||||
if (std::get_if<DefaultOutputs>(&outputsSpec))
|
||||
json = nullptr;
|
||||
|
||||
else if (std::get_if<AllOutputs>(&outputsSpec))
|
||||
json = std::vector<std::string>({"*"});
|
||||
|
||||
else if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
|
||||
json = *outputNames;
|
||||
std::optional spec = parseOpt(s);
|
||||
if (!spec)
|
||||
throw Error("invalid outputs specifier '%s'", s);
|
||||
return *spec;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json & json, OutputsSpec & outputsSpec)
|
||||
|
||||
std::optional<std::pair<std::string_view, ExtendedOutputsSpec>> ExtendedOutputsSpec::parseOpt(std::string_view s)
|
||||
{
|
||||
auto found = s.rfind('^');
|
||||
|
||||
if (found == std::string::npos)
|
||||
return std::pair { s, ExtendedOutputsSpec::Default {} };
|
||||
|
||||
auto specOpt = OutputsSpec::parseOpt(s.substr(found + 1));
|
||||
if (!specOpt)
|
||||
return std::nullopt;
|
||||
return std::pair { s.substr(0, found), ExtendedOutputsSpec::Explicit { *std::move(specOpt) } };
|
||||
}
|
||||
|
||||
|
||||
std::pair<std::string_view, ExtendedOutputsSpec> ExtendedOutputsSpec::parse(std::string_view s)
|
||||
{
|
||||
std::optional spec = parseOpt(s);
|
||||
if (!spec)
|
||||
throw Error("invalid extended outputs specifier '%s'", s);
|
||||
return *spec;
|
||||
}
|
||||
|
||||
|
||||
std::string OutputsSpec::to_string() const
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
[&](const OutputsSpec::All &) -> std::string {
|
||||
return "*";
|
||||
},
|
||||
[&](const OutputsSpec::Names & outputNames) -> std::string {
|
||||
return concatStringsSep(",", outputNames);
|
||||
},
|
||||
}, raw());
|
||||
}
|
||||
|
||||
|
||||
std::string ExtendedOutputsSpec::to_string() const
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
[&](const ExtendedOutputsSpec::Default &) -> std::string {
|
||||
return "";
|
||||
},
|
||||
[&](const ExtendedOutputsSpec::Explicit & outputSpec) -> std::string {
|
||||
return "^" + outputSpec.to_string();
|
||||
},
|
||||
}, raw());
|
||||
}
|
||||
|
||||
|
||||
OutputsSpec OutputsSpec::union_(const OutputsSpec & that) const
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
[&](const OutputsSpec::All &) -> OutputsSpec {
|
||||
return OutputsSpec::All { };
|
||||
},
|
||||
[&](const OutputsSpec::Names & theseNames) -> OutputsSpec {
|
||||
return std::visit(overloaded {
|
||||
[&](const OutputsSpec::All &) -> OutputsSpec {
|
||||
return OutputsSpec::All {};
|
||||
},
|
||||
[&](const OutputsSpec::Names & thoseNames) -> OutputsSpec {
|
||||
OutputsSpec::Names ret = theseNames;
|
||||
ret.insert(thoseNames.begin(), thoseNames.end());
|
||||
return ret;
|
||||
},
|
||||
}, that.raw());
|
||||
},
|
||||
}, raw());
|
||||
}
|
||||
|
||||
|
||||
bool OutputsSpec::isSubsetOf(const OutputsSpec & that) const
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
[&](const OutputsSpec::All &) {
|
||||
return true;
|
||||
},
|
||||
[&](const OutputsSpec::Names & thoseNames) {
|
||||
return std::visit(overloaded {
|
||||
[&](const OutputsSpec::All &) {
|
||||
return false;
|
||||
},
|
||||
[&](const OutputsSpec::Names & theseNames) {
|
||||
bool ret = true;
|
||||
for (auto & o : theseNames)
|
||||
if (thoseNames.count(o) == 0)
|
||||
ret = false;
|
||||
return ret;
|
||||
},
|
||||
}, raw());
|
||||
},
|
||||
}, that.raw());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace nlohmann {
|
||||
|
||||
using namespace nix;
|
||||
|
||||
OutputsSpec adl_serializer<OutputsSpec>::from_json(const json & json) {
|
||||
auto names = json.get<StringSet>();
|
||||
if (names == StringSet({"*"}))
|
||||
return OutputsSpec::All {};
|
||||
else
|
||||
return OutputsSpec::Names { std::move(names) };
|
||||
}
|
||||
|
||||
void adl_serializer<OutputsSpec>::to_json(json & json, OutputsSpec t) {
|
||||
std::visit(overloaded {
|
||||
[&](const OutputsSpec::All &) {
|
||||
json = std::vector<std::string>({"*"});
|
||||
},
|
||||
[&](const OutputsSpec::Names & names) {
|
||||
json = names;
|
||||
},
|
||||
}, t.raw());
|
||||
}
|
||||
|
||||
|
||||
ExtendedOutputsSpec adl_serializer<ExtendedOutputsSpec>::from_json(const json & json) {
|
||||
if (json.is_null())
|
||||
outputsSpec = DefaultOutputs();
|
||||
return ExtendedOutputsSpec::Default {};
|
||||
else {
|
||||
auto names = json.get<OutputNames>();
|
||||
if (names == OutputNames({"*"}))
|
||||
outputsSpec = AllOutputs();
|
||||
else
|
||||
outputsSpec = names;
|
||||
return ExtendedOutputsSpec::Explicit { json.get<OutputsSpec>() };
|
||||
}
|
||||
}
|
||||
|
||||
void adl_serializer<ExtendedOutputsSpec>::to_json(json & json, ExtendedOutputsSpec t) {
|
||||
std::visit(overloaded {
|
||||
[&](const ExtendedOutputsSpec::Default &) {
|
||||
json = nullptr;
|
||||
},
|
||||
[&](const ExtendedOutputsSpec::Explicit & e) {
|
||||
adl_serializer<OutputsSpec>::to_json(json, e);
|
||||
},
|
||||
}, t.raw());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,32 +1,95 @@
|
|||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <variant>
|
||||
|
||||
#include "util.hh"
|
||||
|
||||
#include "nlohmann/json_fwd.hpp"
|
||||
#include "json-impls.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
typedef std::set<std::string> OutputNames;
|
||||
struct OutputNames : std::set<std::string> {
|
||||
using std::set<std::string>::set;
|
||||
|
||||
struct AllOutputs {
|
||||
bool operator < (const AllOutputs & _) const { return false; }
|
||||
/* These need to be "inherited manually" */
|
||||
|
||||
OutputNames(const std::set<std::string> & s)
|
||||
: std::set<std::string>(s)
|
||||
{ assert(!empty()); }
|
||||
|
||||
OutputNames(std::set<std::string> && s)
|
||||
: std::set<std::string>(s)
|
||||
{ assert(!empty()); }
|
||||
|
||||
/* This set should always be non-empty, so we delete this
|
||||
constructor in order make creating empty ones by mistake harder.
|
||||
*/
|
||||
OutputNames() = delete;
|
||||
};
|
||||
|
||||
struct DefaultOutputs {
|
||||
bool operator < (const DefaultOutputs & _) const { return false; }
|
||||
struct AllOutputs : std::monostate { };
|
||||
|
||||
typedef std::variant<AllOutputs, OutputNames> _OutputsSpecRaw;
|
||||
|
||||
struct OutputsSpec : _OutputsSpecRaw {
|
||||
using Raw = _OutputsSpecRaw;
|
||||
using Raw::Raw;
|
||||
|
||||
/* Force choosing a variant */
|
||||
OutputsSpec() = delete;
|
||||
|
||||
using Names = OutputNames;
|
||||
using All = AllOutputs;
|
||||
|
||||
inline const Raw & raw() const {
|
||||
return static_cast<const Raw &>(*this);
|
||||
}
|
||||
|
||||
inline Raw & raw() {
|
||||
return static_cast<Raw &>(*this);
|
||||
}
|
||||
|
||||
bool contains(const std::string & output) const;
|
||||
|
||||
/* Create a new OutputsSpec which is the union of this and that. */
|
||||
OutputsSpec union_(const OutputsSpec & that) const;
|
||||
|
||||
/* Whether this OutputsSpec is a subset of that. */
|
||||
bool isSubsetOf(const OutputsSpec & outputs) const;
|
||||
|
||||
/* Parse a string of the form 'output1,...outputN' or
|
||||
'*', returning the outputs spec. */
|
||||
static OutputsSpec parse(std::string_view s);
|
||||
static std::optional<OutputsSpec> parseOpt(std::string_view s);
|
||||
|
||||
std::string to_string() const;
|
||||
};
|
||||
|
||||
typedef std::variant<DefaultOutputs, AllOutputs, OutputNames> OutputsSpec;
|
||||
struct DefaultOutputs : std::monostate { };
|
||||
|
||||
/* Parse a string of the form 'prefix^output1,...outputN' or
|
||||
'prefix^*', returning the prefix and the outputs spec. */
|
||||
std::pair<std::string, OutputsSpec> parseOutputsSpec(const std::string & s);
|
||||
typedef std::variant<DefaultOutputs, OutputsSpec> _ExtendedOutputsSpecRaw;
|
||||
|
||||
std::string printOutputsSpec(const OutputsSpec & outputsSpec);
|
||||
struct ExtendedOutputsSpec : _ExtendedOutputsSpecRaw {
|
||||
using Raw = _ExtendedOutputsSpecRaw;
|
||||
using Raw::Raw;
|
||||
|
||||
void to_json(nlohmann::json &, const OutputsSpec &);
|
||||
void from_json(const nlohmann::json &, OutputsSpec &);
|
||||
using Default = DefaultOutputs;
|
||||
using Explicit = OutputsSpec;
|
||||
|
||||
inline const Raw & raw() const {
|
||||
return static_cast<const Raw &>(*this);
|
||||
}
|
||||
|
||||
/* Parse a string of the form 'prefix^output1,...outputN' or
|
||||
'prefix^*', returning the prefix and the extended outputs spec. */
|
||||
static std::pair<std::string_view, ExtendedOutputsSpec> parse(std::string_view s);
|
||||
static std::optional<std::pair<std::string_view, ExtendedOutputsSpec>> parseOpt(std::string_view s);
|
||||
|
||||
std::string to_string() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
JSON_IMPL(OutputsSpec)
|
||||
JSON_IMPL(ExtendedOutputsSpec)
|
||||
|
|
|
@ -3,6 +3,80 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
std::string ValidPathInfo::fingerprint(const Store & store) const
|
||||
{
|
||||
if (narSize == 0)
|
||||
throw Error("cannot calculate fingerprint of path '%s' because its size is not known",
|
||||
store.printStorePath(path));
|
||||
return
|
||||
"1;" + store.printStorePath(path) + ";"
|
||||
+ narHash.to_string(Base32, true) + ";"
|
||||
+ std::to_string(narSize) + ";"
|
||||
+ concatStringsSep(",", store.printStorePathSet(references));
|
||||
}
|
||||
|
||||
|
||||
void ValidPathInfo::sign(const Store & store, const SecretKey & secretKey)
|
||||
{
|
||||
sigs.insert(secretKey.signDetached(fingerprint(store)));
|
||||
}
|
||||
|
||||
|
||||
bool ValidPathInfo::isContentAddressed(const Store & store) const
|
||||
{
|
||||
if (! ca) return false;
|
||||
|
||||
auto caPath = std::visit(overloaded {
|
||||
[&](const TextHash & th) {
|
||||
return store.makeTextPath(path.name(), th.hash, references);
|
||||
},
|
||||
[&](const FixedOutputHash & fsh) {
|
||||
auto refs = references;
|
||||
bool hasSelfReference = false;
|
||||
if (refs.count(path)) {
|
||||
hasSelfReference = true;
|
||||
refs.erase(path);
|
||||
}
|
||||
return store.makeFixedOutputPath(fsh.method, fsh.hash, path.name(), refs, hasSelfReference);
|
||||
}
|
||||
}, *ca);
|
||||
|
||||
bool res = caPath == path;
|
||||
|
||||
if (!res)
|
||||
printError("warning: path '%s' claims to be content-addressed but isn't", store.printStorePath(path));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
size_t ValidPathInfo::checkSignatures(const Store & store, const PublicKeys & publicKeys) const
|
||||
{
|
||||
if (isContentAddressed(store)) return maxSigs;
|
||||
|
||||
size_t good = 0;
|
||||
for (auto & sig : sigs)
|
||||
if (checkSignature(store, publicKeys, sig))
|
||||
good++;
|
||||
return good;
|
||||
}
|
||||
|
||||
|
||||
bool ValidPathInfo::checkSignature(const Store & store, const PublicKeys & publicKeys, const std::string & sig) const
|
||||
{
|
||||
return verifyDetached(fingerprint(store), sig, publicKeys);
|
||||
}
|
||||
|
||||
|
||||
Strings ValidPathInfo::shortRefs() const
|
||||
{
|
||||
Strings refs;
|
||||
for (auto & r : references)
|
||||
refs.push_back(std::string(r.to_string()));
|
||||
return refs;
|
||||
}
|
||||
|
||||
|
||||
ValidPathInfo ValidPathInfo::read(Source & source, const Store & store, unsigned int format)
|
||||
{
|
||||
return read(source, store, format, store.parseStorePath(readString(source)));
|
||||
|
@ -24,6 +98,7 @@ ValidPathInfo ValidPathInfo::read(Source & source, const Store & store, unsigned
|
|||
return info;
|
||||
}
|
||||
|
||||
|
||||
void ValidPathInfo::write(
|
||||
Sink & sink,
|
||||
const Store & store,
|
||||
|
|
7
src/libstore/path-regex.hh
Normal file
7
src/libstore/path-regex.hh
Normal file
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
namespace nix {
|
||||
|
||||
static constexpr std::string_view nameRegexStr = R"([0-9a-zA-Z\+\-\._\?=]+)";
|
||||
|
||||
}
|
|
@ -15,10 +15,14 @@ std::string StorePathWithOutputs::to_string(const Store & store) const
|
|||
|
||||
DerivedPath StorePathWithOutputs::toDerivedPath() const
|
||||
{
|
||||
if (!outputs.empty() || path.isDerivation())
|
||||
return DerivedPath::Built { path, outputs };
|
||||
else
|
||||
if (!outputs.empty()) {
|
||||
return DerivedPath::Built { path, OutputsSpec::Names { outputs } };
|
||||
} else if (path.isDerivation()) {
|
||||
assert(outputs.empty());
|
||||
return DerivedPath::Built { path, OutputsSpec::All { } };
|
||||
} else {
|
||||
return DerivedPath::Opaque { path };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -41,7 +45,18 @@ std::variant<StorePathWithOutputs, StorePath> StorePathWithOutputs::tryFromDeriv
|
|||
return StorePathWithOutputs { bo.path };
|
||||
},
|
||||
[&](const DerivedPath::Built & bfd) -> std::variant<StorePathWithOutputs, StorePath> {
|
||||
return StorePathWithOutputs { bfd.drvPath, bfd.outputs };
|
||||
return StorePathWithOutputs {
|
||||
.path = bfd.drvPath,
|
||||
// Use legacy encoding of wildcard as empty set
|
||||
.outputs = std::visit(overloaded {
|
||||
[&](const OutputsSpec::All &) -> StringSet {
|
||||
return {};
|
||||
},
|
||||
[&](const OutputsSpec::Names & outputs) {
|
||||
return static_cast<StringSet>(outputs);
|
||||
},
|
||||
}, bfd.outputs.raw()),
|
||||
};
|
||||
},
|
||||
}, p.raw());
|
||||
}
|
||||
|
@ -52,8 +67,8 @@ std::pair<std::string_view, StringSet> parsePathWithOutputs(std::string_view s)
|
|||
size_t n = s.find("!");
|
||||
return n == s.npos
|
||||
? std::make_pair(s, std::set<std::string>())
|
||||
: std::make_pair(((std::string_view) s).substr(0, n),
|
||||
tokenizeString<std::set<std::string>>(((std::string_view) s).substr(n + 1), ","));
|
||||
: std::make_pair(s.substr(0, n),
|
||||
tokenizeString<std::set<std::string>>(s.substr(n + 1), ","));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#include "path.hh"
|
||||
#include "derived-path.hh"
|
||||
#include "nlohmann/json_fwd.hpp"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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);
|
||||
|
@ -64,7 +65,6 @@ public:
|
|||
|
||||
typedef std::set<StorePath> StorePathSet;
|
||||
typedef std::vector<StorePath> StorePaths;
|
||||
typedef std::map<std::string, StorePath> OutputPathMap;
|
||||
|
||||
typedef std::map<StorePath, std::optional<ContentAddress>> StorePathCAMap;
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
class Store;
|
||||
|
||||
struct DrvOutput {
|
||||
// The hash modulo of the derivation
|
||||
Hash drvHash;
|
||||
|
|
|
@ -867,8 +867,8 @@ std::vector<BuildResult> RemoteStore::buildPathsWithResults(
|
|||
OutputPathMap outputs;
|
||||
auto drv = evalStore->readDerivation(bfd.drvPath);
|
||||
const auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive
|
||||
const auto drvOutputs = drv.outputsAndOptPaths(*this);
|
||||
for (auto & output : bfd.outputs) {
|
||||
auto built = resolveDerivedPath(*this, bfd, &*evalStore);
|
||||
for (auto & [output, outputPath] : built) {
|
||||
auto outputHash = get(outputHashes, output);
|
||||
if (!outputHash)
|
||||
throw Error(
|
||||
|
@ -882,16 +882,11 @@ std::vector<BuildResult> RemoteStore::buildPathsWithResults(
|
|||
throw MissingRealisation(outputId);
|
||||
res.builtOutputs.emplace(realisation->id, *realisation);
|
||||
} else {
|
||||
// If ca-derivations isn't enabled, assume that
|
||||
// the output path is statically known.
|
||||
const auto drvOutput = get(drvOutputs, output);
|
||||
assert(drvOutput);
|
||||
assert(drvOutput->second);
|
||||
res.builtOutputs.emplace(
|
||||
outputId,
|
||||
Realisation {
|
||||
.id = outputId,
|
||||
.outPath = *drvOutput->second,
|
||||
.outPath = outputPath,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -915,7 +910,12 @@ BuildResult RemoteStore::buildDerivation(const StorePath & drvPath, const BasicD
|
|||
writeDerivation(conn->to, *this, drv);
|
||||
conn->to << buildMode;
|
||||
conn.processStderr();
|
||||
BuildResult res { .path = DerivedPath::Built { .drvPath = drvPath } };
|
||||
BuildResult res {
|
||||
.path = DerivedPath::Built {
|
||||
.drvPath = drvPath,
|
||||
.outputs = OutputsSpec::All { },
|
||||
},
|
||||
};
|
||||
res.status = (BuildResult::Status) readInt(conn->from);
|
||||
conn->from >> res.errorMsg;
|
||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 29) {
|
||||
|
|
|
@ -53,8 +53,8 @@ public:
|
|||
{ return false; }
|
||||
|
||||
// FIXME extend daemon protocol, move implementation to RemoteStore
|
||||
std::optional<std::string> getBuildLog(const StorePath & path) override
|
||||
{ unsupported("getBuildLog"); }
|
||||
std::optional<std::string> getBuildLogExact(const StorePath & path) override
|
||||
{ unsupported("getBuildLogExact"); }
|
||||
|
||||
private:
|
||||
|
||||
|
|
|
@ -1214,79 +1214,6 @@ std::string showPaths(const PathSet & paths)
|
|||
}
|
||||
|
||||
|
||||
std::string ValidPathInfo::fingerprint(const Store & store) const
|
||||
{
|
||||
if (narSize == 0)
|
||||
throw Error("cannot calculate fingerprint of path '%s' because its size is not known",
|
||||
store.printStorePath(path));
|
||||
return
|
||||
"1;" + store.printStorePath(path) + ";"
|
||||
+ narHash.to_string(Base32, true) + ";"
|
||||
+ std::to_string(narSize) + ";"
|
||||
+ concatStringsSep(",", store.printStorePathSet(references));
|
||||
}
|
||||
|
||||
|
||||
void ValidPathInfo::sign(const Store & store, const SecretKey & secretKey)
|
||||
{
|
||||
sigs.insert(secretKey.signDetached(fingerprint(store)));
|
||||
}
|
||||
|
||||
bool ValidPathInfo::isContentAddressed(const Store & store) const
|
||||
{
|
||||
if (! ca) return false;
|
||||
|
||||
auto caPath = std::visit(overloaded {
|
||||
[&](const TextHash & th) {
|
||||
return store.makeTextPath(path.name(), th.hash, references);
|
||||
},
|
||||
[&](const FixedOutputHash & fsh) {
|
||||
auto refs = references;
|
||||
bool hasSelfReference = false;
|
||||
if (refs.count(path)) {
|
||||
hasSelfReference = true;
|
||||
refs.erase(path);
|
||||
}
|
||||
return store.makeFixedOutputPath(fsh.method, fsh.hash, path.name(), refs, hasSelfReference);
|
||||
}
|
||||
}, *ca);
|
||||
|
||||
bool res = caPath == path;
|
||||
|
||||
if (!res)
|
||||
printError("warning: path '%s' claims to be content-addressed but isn't", store.printStorePath(path));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
size_t ValidPathInfo::checkSignatures(const Store & store, const PublicKeys & publicKeys) const
|
||||
{
|
||||
if (isContentAddressed(store)) return maxSigs;
|
||||
|
||||
size_t good = 0;
|
||||
for (auto & sig : sigs)
|
||||
if (checkSignature(store, publicKeys, sig))
|
||||
good++;
|
||||
return good;
|
||||
}
|
||||
|
||||
|
||||
bool ValidPathInfo::checkSignature(const Store & store, const PublicKeys & publicKeys, const std::string & sig) const
|
||||
{
|
||||
return verifyDetached(fingerprint(store), sig, publicKeys);
|
||||
}
|
||||
|
||||
|
||||
Strings ValidPathInfo::shortRefs() const
|
||||
{
|
||||
Strings refs;
|
||||
for (auto & r : references)
|
||||
refs.push_back(std::string(r.to_string()));
|
||||
return refs;
|
||||
}
|
||||
|
||||
|
||||
Derivation Store::derivationFromPath(const StorePath & drvPath)
|
||||
{
|
||||
ensurePath(drvPath);
|
||||
|
@ -1305,6 +1232,34 @@ Derivation readDerivationCommon(Store& store, const StorePath& drvPath, bool req
|
|||
}
|
||||
}
|
||||
|
||||
std::optional<StorePath> Store::getBuildDerivationPath(const StorePath & path)
|
||||
{
|
||||
|
||||
if (!path.isDerivation()) {
|
||||
try {
|
||||
auto info = queryPathInfo(path);
|
||||
if (!info->deriver) return std::nullopt;
|
||||
return *info->deriver;
|
||||
} catch (InvalidPath &) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
if (!settings.isExperimentalFeatureEnabled(Xp::CaDerivations) || !isValidPath(path))
|
||||
return path;
|
||||
|
||||
auto drv = readDerivation(path);
|
||||
if (!drv.type().hasKnownOutputPaths()) {
|
||||
// The build log is actually attached to the corresponding
|
||||
// resolved derivation, so we need to get it first
|
||||
auto resolvedDrv = drv.tryResolve(*this);
|
||||
if (resolvedDrv)
|
||||
return writeDerivation(*this, *resolvedDrv, NoRepair, true);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
Derivation Store::readDerivation(const StorePath & drvPath)
|
||||
{ return readDerivationCommon(*this, drvPath, true); }
|
||||
|
||||
|
|
|
@ -71,6 +71,9 @@ class NarInfoDiskCache;
|
|||
class Store;
|
||||
|
||||
|
||||
typedef std::map<std::string, StorePath> OutputPathMap;
|
||||
|
||||
|
||||
enum CheckSigsFlag : bool { NoCheckSigs = false, CheckSigs = true };
|
||||
enum SubstituteFlag : bool { NoSubstitute = false, Substitute = true };
|
||||
enum AllowInvalidFlag : bool { DisallowInvalid = false, AllowInvalid = true };
|
||||
|
@ -120,6 +123,8 @@ public:
|
|||
|
||||
typedef std::map<std::string, std::string> Params;
|
||||
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
struct PathInfoCacheValue {
|
||||
|
@ -620,6 +625,13 @@ public:
|
|||
*/
|
||||
StorePathSet exportReferences(const StorePathSet & storePaths, const StorePathSet & inputPaths);
|
||||
|
||||
/**
|
||||
* Given a store path, return the realisation actually used in the realisation of this path:
|
||||
* - If the path is a content-addressed derivation, try to resolve it
|
||||
* - Otherwise, find one of its derivers
|
||||
*/
|
||||
std::optional<StorePath> getBuildDerivationPath(const StorePath &);
|
||||
|
||||
/* Hack to allow long-running processes like hydra-queue-runner to
|
||||
occasionally flush their path info cache. */
|
||||
void clearPathInfoCache()
|
||||
|
@ -721,6 +733,11 @@ void copyClosure(
|
|||
void removeTempRoots();
|
||||
|
||||
|
||||
/* Resolve the derived path completely, failing if any derivation output
|
||||
is unknown. */
|
||||
OutputPathMap resolveDerivedPath(Store &, const DerivedPath::Built &, Store * evalStore = nullptr);
|
||||
|
||||
|
||||
/* Return a Store object to access the Nix store denoted by
|
||||
‘uri’ (slight misnomer...). Supported values are:
|
||||
|
||||
|
|
23
src/libstore/tests/libstoretests.hh
Normal file
23
src/libstore/tests/libstoretests.hh
Normal 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 */
|
|
@ -12,4 +12,4 @@ libstore-tests_CXXFLAGS += -I src/libstore -I src/libutil
|
|||
|
||||
libstore-tests_LIBS = libstore libutil
|
||||
|
||||
libstore-tests_LDFLAGS := $(GTEST_LIBS)
|
||||
libstore-tests_LDFLAGS := -lrapidcheck $(GTEST_LIBS)
|
||||
|
|
|
@ -1,46 +1,201 @@
|
|||
#include "outputs-spec.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
TEST(parseOutputsSpec, basic)
|
||||
{
|
||||
{
|
||||
auto [prefix, outputsSpec] = parseOutputsSpec("foo");
|
||||
ASSERT_EQ(prefix, "foo");
|
||||
ASSERT_TRUE(std::get_if<DefaultOutputs>(&outputsSpec));
|
||||
#ifndef NDEBUG
|
||||
TEST(OutputsSpec, no_empty_names) {
|
||||
ASSERT_DEATH(OutputsSpec::Names { std::set<std::string> { } }, "");
|
||||
}
|
||||
#endif
|
||||
|
||||
#define TEST_DONT_PARSE(NAME, STR) \
|
||||
TEST(OutputsSpec, bad_ ## NAME) { \
|
||||
std::optional OutputsSpecOpt = \
|
||||
OutputsSpec::parseOpt(STR); \
|
||||
ASSERT_FALSE(OutputsSpecOpt); \
|
||||
}
|
||||
|
||||
{
|
||||
auto [prefix, outputsSpec] = parseOutputsSpec("foo^*");
|
||||
ASSERT_EQ(prefix, "foo");
|
||||
ASSERT_TRUE(std::get_if<AllOutputs>(&outputsSpec));
|
||||
}
|
||||
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,*")
|
||||
|
||||
{
|
||||
auto [prefix, outputsSpec] = parseOutputsSpec("foo^out");
|
||||
ASSERT_EQ(prefix, "foo");
|
||||
ASSERT_TRUE(std::get<OutputNames>(outputsSpec) == OutputNames({"out"}));
|
||||
}
|
||||
#undef TEST_DONT_PARSE
|
||||
|
||||
{
|
||||
auto [prefix, outputsSpec] = parseOutputsSpec("foo^out,bin");
|
||||
ASSERT_EQ(prefix, "foo");
|
||||
ASSERT_TRUE(std::get<OutputNames>(outputsSpec) == OutputNames({"out", "bin"}));
|
||||
}
|
||||
|
||||
{
|
||||
auto [prefix, outputsSpec] = parseOutputsSpec("foo^bar^out,bin");
|
||||
ASSERT_EQ(prefix, "foo^bar");
|
||||
ASSERT_TRUE(std::get<OutputNames>(outputsSpec) == OutputNames({"out", "bin"}));
|
||||
}
|
||||
|
||||
{
|
||||
auto [prefix, outputsSpec] = parseOutputsSpec("foo^&*()");
|
||||
ASSERT_EQ(prefix, "foo^&*()");
|
||||
ASSERT_TRUE(std::get_if<DefaultOutputs>(&outputsSpec));
|
||||
}
|
||||
TEST(OutputsSpec, all) {
|
||||
std::string_view str = "*";
|
||||
OutputsSpec expected = OutputsSpec::All { };
|
||||
ASSERT_EQ(OutputsSpec::parse(str), expected);
|
||||
ASSERT_EQ(expected.to_string(), str);
|
||||
}
|
||||
|
||||
TEST(OutputsSpec, names_out) {
|
||||
std::string_view str = "out";
|
||||
OutputsSpec expected = OutputsSpec::Names { "out" };
|
||||
ASSERT_EQ(OutputsSpec::parse(str), expected);
|
||||
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);
|
||||
// N.B. This normalization is OK.
|
||||
ASSERT_EQ(expected.to_string(), "bin,out");
|
||||
}
|
||||
|
||||
#define TEST_SUBSET(X, THIS, THAT) \
|
||||
X((OutputsSpec { THIS }).isSubsetOf(THAT));
|
||||
|
||||
TEST(OutputsSpec, subsets_all_all) {
|
||||
TEST_SUBSET(ASSERT_TRUE, OutputsSpec::All { }, OutputsSpec::All { });
|
||||
}
|
||||
|
||||
TEST(OutputsSpec, subsets_names_all) {
|
||||
TEST_SUBSET(ASSERT_TRUE, OutputsSpec::Names { "a" }, OutputsSpec::All { });
|
||||
}
|
||||
|
||||
TEST(OutputsSpec, subsets_names_names_eq) {
|
||||
TEST_SUBSET(ASSERT_TRUE, OutputsSpec::Names { "a" }, OutputsSpec::Names { "a" });
|
||||
}
|
||||
|
||||
TEST(OutputsSpec, subsets_names_names_noneq) {
|
||||
TEST_SUBSET(ASSERT_TRUE, OutputsSpec::Names { "a" }, (OutputsSpec::Names { "a", "b" }));
|
||||
}
|
||||
|
||||
TEST(OutputsSpec, not_subsets_all_names) {
|
||||
TEST_SUBSET(ASSERT_FALSE, OutputsSpec::All { }, OutputsSpec::Names { "a" });
|
||||
}
|
||||
|
||||
TEST(OutputsSpec, not_subsets_names_names) {
|
||||
TEST_SUBSET(ASSERT_FALSE, (OutputsSpec::Names { "a", "b" }), (OutputsSpec::Names { "a" }));
|
||||
}
|
||||
|
||||
#undef TEST_SUBSET
|
||||
|
||||
#define TEST_UNION(RES, THIS, THAT) \
|
||||
ASSERT_EQ(OutputsSpec { RES }, (OutputsSpec { THIS }).union_(THAT));
|
||||
|
||||
TEST(OutputsSpec, union_all_all) {
|
||||
TEST_UNION(OutputsSpec::All { }, OutputsSpec::All { }, OutputsSpec::All { });
|
||||
}
|
||||
|
||||
TEST(OutputsSpec, union_all_names) {
|
||||
TEST_UNION(OutputsSpec::All { }, OutputsSpec::All { }, OutputsSpec::Names { "a" });
|
||||
}
|
||||
|
||||
TEST(OutputsSpec, union_names_all) {
|
||||
TEST_UNION(OutputsSpec::All { }, OutputsSpec::Names { "a" }, OutputsSpec::All { });
|
||||
}
|
||||
|
||||
TEST(OutputsSpec, union_names_names) {
|
||||
TEST_UNION((OutputsSpec::Names { "a", "b" }), OutputsSpec::Names { "a" }, OutputsSpec::Names { "b" });
|
||||
}
|
||||
|
||||
#undef TEST_UNION
|
||||
|
||||
#define TEST_DONT_PARSE(NAME, STR) \
|
||||
TEST(ExtendedOutputsSpec, bad_ ## NAME) { \
|
||||
std::optional extendedOutputsSpecOpt = \
|
||||
ExtendedOutputsSpec::parseOpt(STR); \
|
||||
ASSERT_FALSE(extendedOutputsSpecOpt); \
|
||||
}
|
||||
|
||||
TEST_DONT_PARSE(carot_empty, "^")
|
||||
TEST_DONT_PARSE(prefix_carot_empty, "foo^")
|
||||
TEST_DONT_PARSE(garbage, "^&*()")
|
||||
TEST_DONT_PARSE(double_star, "^**")
|
||||
TEST_DONT_PARSE(star_first, "^*,foo")
|
||||
TEST_DONT_PARSE(star_second, "^foo,*")
|
||||
|
||||
#undef TEST_DONT_PARSE
|
||||
|
||||
TEST(ExtendedOutputsSpec, defeault) {
|
||||
std::string_view str = "foo";
|
||||
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(str);
|
||||
ASSERT_EQ(prefix, "foo");
|
||||
ExtendedOutputsSpec expected = ExtendedOutputsSpec::Default { };
|
||||
ASSERT_EQ(extendedOutputsSpec, expected);
|
||||
ASSERT_EQ(std::string { prefix } + expected.to_string(), str);
|
||||
}
|
||||
|
||||
TEST(ExtendedOutputsSpec, all) {
|
||||
std::string_view str = "foo^*";
|
||||
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(str);
|
||||
ASSERT_EQ(prefix, "foo");
|
||||
ExtendedOutputsSpec expected = OutputsSpec::All { };
|
||||
ASSERT_EQ(extendedOutputsSpec, expected);
|
||||
ASSERT_EQ(std::string { prefix } + expected.to_string(), str);
|
||||
}
|
||||
|
||||
TEST(ExtendedOutputsSpec, out) {
|
||||
std::string_view str = "foo^out";
|
||||
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(str);
|
||||
ASSERT_EQ(prefix, "foo");
|
||||
ExtendedOutputsSpec expected = OutputsSpec::Names { "out" };
|
||||
ASSERT_EQ(extendedOutputsSpec, expected);
|
||||
ASSERT_EQ(std::string { prefix } + expected.to_string(), str);
|
||||
}
|
||||
|
||||
TEST(ExtendedOutputsSpec, out_bin) {
|
||||
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse("foo^out,bin");
|
||||
ASSERT_EQ(prefix, "foo");
|
||||
ExtendedOutputsSpec expected = OutputsSpec::Names { "out", "bin" };
|
||||
ASSERT_EQ(extendedOutputsSpec, expected);
|
||||
ASSERT_EQ(std::string { prefix } + expected.to_string(), "foo^bin,out");
|
||||
}
|
||||
|
||||
TEST(ExtendedOutputsSpec, many_carrot) {
|
||||
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse("foo^bar^out,bin");
|
||||
ASSERT_EQ(prefix, "foo^bar");
|
||||
ExtendedOutputsSpec expected = OutputsSpec::Names { "out", "bin" };
|
||||
ASSERT_EQ(extendedOutputsSpec, expected);
|
||||
ASSERT_EQ(std::string { prefix } + expected.to_string(), "foo^bar^bin,out");
|
||||
}
|
||||
|
||||
|
||||
#define TEST_JSON(TYPE, NAME, STR, VAL) \
|
||||
\
|
||||
TEST(TYPE, NAME ## _to_json) { \
|
||||
using nlohmann::literals::operator "" _json; \
|
||||
ASSERT_EQ( \
|
||||
STR ## _json, \
|
||||
((nlohmann::json) TYPE { VAL })); \
|
||||
} \
|
||||
\
|
||||
TEST(TYPE, NAME ## _from_json) { \
|
||||
using nlohmann::literals::operator "" _json; \
|
||||
ASSERT_EQ( \
|
||||
TYPE { VAL }, \
|
||||
(STR ## _json).get<TYPE>()); \
|
||||
}
|
||||
|
||||
TEST_JSON(OutputsSpec, all, R"(["*"])", OutputsSpec::All { })
|
||||
TEST_JSON(OutputsSpec, name, R"(["a"])", OutputsSpec::Names { "a" })
|
||||
TEST_JSON(OutputsSpec, names, R"(["a","b"])", (OutputsSpec::Names { "a", "b" }))
|
||||
|
||||
TEST_JSON(ExtendedOutputsSpec, def, R"(null)", ExtendedOutputsSpec::Default { })
|
||||
TEST_JSON(ExtendedOutputsSpec, all, R"(["*"])", ExtendedOutputsSpec::Explicit { OutputsSpec::All { } })
|
||||
TEST_JSON(ExtendedOutputsSpec, name, R"(["a"])", ExtendedOutputsSpec::Explicit { OutputsSpec::Names { "a" } })
|
||||
TEST_JSON(ExtendedOutputsSpec, names, R"(["a","b"])", (ExtendedOutputsSpec::Explicit { OutputsSpec::Names { "a", "b" } }))
|
||||
|
||||
#undef TEST_JSON
|
||||
|
||||
}
|
||||
|
|
144
src/libstore/tests/path.cc
Normal file
144
src/libstore/tests/path.cc
Normal file
|
@ -0,0 +1,144 @@
|
|||
#include <regex>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
#include <rapidcheck/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
|
||||
|
||||
// For rapidcheck
|
||||
void showValue(const StorePath & p, std::ostream & os) {
|
||||
os << p.to_string();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace rc {
|
||||
using namespace nix;
|
||||
|
||||
template<>
|
||||
struct Arbitrary<StorePath> {
|
||||
static Gen<StorePath> arbitrary();
|
||||
};
|
||||
|
||||
Gen<StorePath> Arbitrary<StorePath>::arbitrary()
|
||||
{
|
||||
auto len = *gen::inRange<size_t>(1, StorePath::MaxPathLen);
|
||||
|
||||
std::string pre { HASH_PART "-" };
|
||||
pre.reserve(pre.size() + len);
|
||||
|
||||
for (size_t c = 0; c < len; ++c) {
|
||||
switch (auto i = *gen::inRange<uint8_t>(0, 10 + 2 * 26 + 6)) {
|
||||
case 0 ... 9:
|
||||
pre += '0' + i;
|
||||
case 10 ... 35:
|
||||
pre += 'A' + (i - 10);
|
||||
break;
|
||||
case 36 ... 61:
|
||||
pre += 'a' + (i - 36);
|
||||
break;
|
||||
case 62:
|
||||
pre += '+';
|
||||
break;
|
||||
case 63:
|
||||
pre += '-';
|
||||
break;
|
||||
case 64:
|
||||
pre += '.';
|
||||
break;
|
||||
case 65:
|
||||
pre += '_';
|
||||
break;
|
||||
case 66:
|
||||
pre += '?';
|
||||
break;
|
||||
case 67:
|
||||
pre += '=';
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
return gen::just(StorePath { pre });
|
||||
}
|
||||
|
||||
} // namespace rc
|
||||
|
||||
namespace nix {
|
||||
|
||||
RC_GTEST_FIXTURE_PROP(
|
||||
StorePathTest,
|
||||
prop_regex_accept,
|
||||
(const StorePath & p))
|
||||
{
|
||||
RC_ASSERT(std::regex_match(std::string { p.name() }, nameRegex));
|
||||
}
|
||||
|
||||
RC_GTEST_FIXTURE_PROP(
|
||||
StorePathTest,
|
||||
prop_round_rip,
|
||||
(const StorePath & p))
|
||||
{
|
||||
RC_ASSERT(p == store->parseStorePath(store->printStorePath(p)));
|
||||
}
|
||||
|
||||
}
|
|
@ -74,6 +74,8 @@ struct AbstractPos
|
|||
virtual void print(std::ostream & out) const = 0;
|
||||
|
||||
std::optional<LinesOfCode> getCodeLines() const;
|
||||
|
||||
virtual ~AbstractPos() = default;
|
||||
};
|
||||
|
||||
std::ostream & operator << (std::ostream & str, const AbstractPos & pos);
|
||||
|
|
14
src/libutil/json-impls.hh
Normal file
14
src/libutil/json-impls.hh
Normal file
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include "nlohmann/json_fwd.hpp"
|
||||
|
||||
// Following https://github.com/nlohmann/json#how-can-i-use-get-for-non-default-constructiblenon-copyable-types
|
||||
#define JSON_IMPL(TYPE) \
|
||||
namespace nlohmann { \
|
||||
using namespace nix; \
|
||||
template <> \
|
||||
struct adl_serializer<TYPE> { \
|
||||
static TYPE from_json(const json & json); \
|
||||
static void to_json(json & json, TYPE t); \
|
||||
}; \
|
||||
}
|
|
@ -22,27 +22,38 @@ public:
|
|||
{
|
||||
thread = std::thread([fd]() {
|
||||
while (true) {
|
||||
/* Wait indefinitely until a POLLHUP occurs. */
|
||||
struct pollfd fds[1];
|
||||
fds[0].fd = fd;
|
||||
/* This shouldn't be necessary, but macOS doesn't seem to
|
||||
like a zeroed out events field.
|
||||
See rdar://37537852.
|
||||
*/
|
||||
fds[0].events = POLLHUP;
|
||||
auto count = poll(fds, 1, -1);
|
||||
if (count == -1) abort(); // can't happen
|
||||
/* This shouldn't happen, but can on macOS due to a bug.
|
||||
See rdar://37550628.
|
||||
/* Wait indefinitely until a POLLHUP occurs. */
|
||||
struct pollfd fds[1];
|
||||
fds[0].fd = fd;
|
||||
/* Polling for no specific events (i.e. just waiting
|
||||
for an error/hangup) doesn't work on macOS
|
||||
anymore. So wait for read events and ignore
|
||||
them. */
|
||||
fds[0].events =
|
||||
#ifdef __APPLE__
|
||||
POLLRDNORM
|
||||
#else
|
||||
0
|
||||
#endif
|
||||
;
|
||||
auto count = poll(fds, 1, -1);
|
||||
if (count == -1) abort(); // can't happen
|
||||
/* This shouldn't happen, but can on macOS due to a bug.
|
||||
See rdar://37550628.
|
||||
|
||||
This may eventually need a delay or further
|
||||
coordination with the main thread if spinning proves
|
||||
too harmful.
|
||||
*/
|
||||
if (count == 0) continue;
|
||||
assert(fds[0].revents & POLLHUP);
|
||||
triggerInterrupt();
|
||||
break;
|
||||
This may eventually need a delay or further
|
||||
coordination with the main thread if spinning proves
|
||||
too harmful.
|
||||
*/
|
||||
if (count == 0) continue;
|
||||
if (fds[0].revents & POLLHUP) {
|
||||
triggerInterrupt();
|
||||
break;
|
||||
}
|
||||
/* This will only happen on macOS. We sleep a bit to
|
||||
avoid waking up too often if the client is sending
|
||||
input. */
|
||||
sleep(1);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
30
src/libutil/regex-combinators.hh
Normal file
30
src/libutil/regex-combinators.hh
Normal 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));
|
||||
}
|
||||
|
||||
}
|
|
@ -402,7 +402,7 @@ static void main_nix_build(int argc, char * * argv)
|
|||
auto bashDrv = drv->requireDrvPath();
|
||||
pathsToBuild.push_back(DerivedPath::Built {
|
||||
.drvPath = bashDrv,
|
||||
.outputs = {"out"},
|
||||
.outputs = OutputsSpec::Names {"out"},
|
||||
});
|
||||
pathsToCopy.insert(bashDrv);
|
||||
shellDrv = bashDrv;
|
||||
|
@ -426,7 +426,7 @@ static void main_nix_build(int argc, char * * argv)
|
|||
{
|
||||
pathsToBuild.push_back(DerivedPath::Built {
|
||||
.drvPath = inputDrv,
|
||||
.outputs = inputOutputs
|
||||
.outputs = OutputsSpec::Names { inputOutputs },
|
||||
});
|
||||
pathsToCopy.insert(inputDrv);
|
||||
}
|
||||
|
@ -596,7 +596,7 @@ static void main_nix_build(int argc, char * * argv)
|
|||
if (outputName == "")
|
||||
throw Error("derivation '%s' lacks an 'outputName' attribute", store->printStorePath(drvPath));
|
||||
|
||||
pathsToBuild.push_back(DerivedPath::Built{drvPath, {outputName}});
|
||||
pathsToBuild.push_back(DerivedPath::Built{drvPath, OutputsSpec::Names{outputName}});
|
||||
pathsToBuildOrdered.push_back({drvPath, {outputName}});
|
||||
drvsToCopy.insert(drvPath);
|
||||
|
||||
|
|
|
@ -480,9 +480,14 @@ static void printMissing(EvalState & state, DrvInfos & elems)
|
|||
std::vector<DerivedPath> targets;
|
||||
for (auto & i : elems)
|
||||
if (auto drvPath = i.queryDrvPath())
|
||||
targets.push_back(DerivedPath::Built{*drvPath});
|
||||
targets.push_back(DerivedPath::Built{
|
||||
.drvPath = *drvPath,
|
||||
.outputs = OutputsSpec::All { },
|
||||
});
|
||||
else
|
||||
targets.push_back(DerivedPath::Opaque{i.queryOutPath()});
|
||||
targets.push_back(DerivedPath::Opaque{
|
||||
.path = i.queryOutPath(),
|
||||
});
|
||||
|
||||
printMissing(state.store, targets);
|
||||
}
|
||||
|
@ -753,8 +758,13 @@ static void opSet(Globals & globals, Strings opFlags, Strings opArgs)
|
|||
auto drvPath = drv.queryDrvPath();
|
||||
std::vector<DerivedPath> paths {
|
||||
drvPath
|
||||
? (DerivedPath) (DerivedPath::Built { *drvPath })
|
||||
: (DerivedPath) (DerivedPath::Opaque { drv.queryOutPath() }),
|
||||
? (DerivedPath) (DerivedPath::Built {
|
||||
.drvPath = *drvPath,
|
||||
.outputs = OutputsSpec::All { },
|
||||
})
|
||||
: (DerivedPath) (DerivedPath::Opaque {
|
||||
.path = drv.queryOutPath(),
|
||||
}),
|
||||
};
|
||||
printMissing(globals.state->store, paths);
|
||||
if (globals.dryRun) return;
|
||||
|
|
|
@ -86,13 +86,13 @@ UnresolvedApp Installable::toApp(EvalState & state)
|
|||
/* We want all outputs of the drv */
|
||||
return DerivedPath::Built {
|
||||
.drvPath = d.drvPath,
|
||||
.outputs = {},
|
||||
.outputs = OutputsSpec::All {},
|
||||
};
|
||||
},
|
||||
[&](const NixStringContextElem::Built & b) -> DerivedPath {
|
||||
return DerivedPath::Built {
|
||||
.drvPath = b.drvPath,
|
||||
.outputs = { b.output },
|
||||
.outputs = OutputsSpec::Names { b.output },
|
||||
};
|
||||
},
|
||||
[&](const NixStringContextElem::Opaque & o) -> DerivedPath {
|
||||
|
@ -127,7 +127,7 @@ UnresolvedApp Installable::toApp(EvalState & state)
|
|||
return UnresolvedApp { App {
|
||||
.context = { DerivedPath::Built {
|
||||
.drvPath = drvPath,
|
||||
.outputs = {outputName},
|
||||
.outputs = OutputsSpec::Names { outputName },
|
||||
} },
|
||||
.program = program,
|
||||
}};
|
||||
|
|
|
@ -75,10 +75,10 @@ struct CmdBundle : InstallableCommand
|
|||
|
||||
auto val = installable->toValue(*evalState).first;
|
||||
|
||||
auto [bundlerFlakeRef, bundlerName, outputsSpec] = parseFlakeRefWithFragmentAndOutputsSpec(bundler, absPath("."));
|
||||
auto [bundlerFlakeRef, bundlerName, extendedOutputsSpec] = parseFlakeRefWithFragmentAndExtendedOutputsSpec(bundler, absPath("."));
|
||||
const flake::LockFlags lockFlags{ .writeLockFile = false };
|
||||
InstallableFlake bundler{this,
|
||||
evalState, std::move(bundlerFlakeRef), bundlerName, outputsSpec,
|
||||
evalState, std::move(bundlerFlakeRef), bundlerName, extendedOutputsSpec,
|
||||
{"bundlers." + settings.thisSystem.get() + ".default",
|
||||
"defaultBundler." + settings.thisSystem.get()
|
||||
},
|
||||
|
@ -105,7 +105,12 @@ struct CmdBundle : InstallableCommand
|
|||
|
||||
auto outPath = evalState->coerceToStorePath(attr2->pos, *attr2->value, context2, "");
|
||||
|
||||
store->buildPaths({ DerivedPath::Built { drvPath } });
|
||||
store->buildPaths({
|
||||
DerivedPath::Built {
|
||||
.drvPath = drvPath,
|
||||
.outputs = OutputsSpec::All { },
|
||||
},
|
||||
});
|
||||
|
||||
auto outPathS = store->printStorePath(outPath);
|
||||
|
||||
|
|
|
@ -232,7 +232,12 @@ static StorePath getDerivationEnvironment(ref<Store> store, ref<Store> evalStore
|
|||
auto shellDrvPath = writeDerivation(*evalStore, drv);
|
||||
|
||||
/* Build the derivation. */
|
||||
store->buildPaths({DerivedPath::Built{shellDrvPath}}, bmNormal, evalStore);
|
||||
store->buildPaths(
|
||||
{ DerivedPath::Built {
|
||||
.drvPath = shellDrvPath,
|
||||
.outputs = OutputsSpec::All { },
|
||||
}},
|
||||
bmNormal, evalStore);
|
||||
|
||||
for (auto & [_0, optPath] : evalStore->queryPartialDerivationOutputMap(shellDrvPath)) {
|
||||
assert(optPath);
|
||||
|
|
|
@ -509,8 +509,12 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
auto drvPath = checkDerivation(
|
||||
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
|
||||
*attr2.value, attr2.pos);
|
||||
if (drvPath && attr_name == settings.thisSystem.get())
|
||||
drvPaths.push_back(DerivedPath::Built{*drvPath});
|
||||
if (drvPath && attr_name == settings.thisSystem.get()) {
|
||||
drvPaths.push_back(DerivedPath::Built {
|
||||
.drvPath = *drvPath,
|
||||
.outputs = OutputsSpec::All { },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -647,6 +651,19 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
}
|
||||
}
|
||||
|
||||
else if (
|
||||
name == "lib"
|
||||
|| name == "darwinConfigurations"
|
||||
|| name == "darwinModules"
|
||||
|| name == "flakeModule"
|
||||
|| name == "flakeModules"
|
||||
|| name == "herculesCI"
|
||||
|| name == "homeConfigurations"
|
||||
|| name == "nixopsConfigurations"
|
||||
)
|
||||
// Known but unchecked community attribute
|
||||
;
|
||||
|
||||
else
|
||||
warn("unknown flake output '%s'", name);
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ struct ProfileElementSource
|
|||
// FIXME: record original attrpath.
|
||||
FlakeRef resolvedRef;
|
||||
std::string attrPath;
|
||||
OutputsSpec outputs;
|
||||
ExtendedOutputsSpec outputs;
|
||||
|
||||
bool operator < (const ProfileElementSource & other) const
|
||||
{
|
||||
|
@ -44,7 +44,7 @@ struct ProfileElement
|
|||
std::string describe() const
|
||||
{
|
||||
if (source)
|
||||
return fmt("%s#%s%s", source->originalRef, source->attrPath, printOutputsSpec(source->outputs));
|
||||
return fmt("%s#%s%s", source->originalRef, source->attrPath, source->outputs.to_string());
|
||||
StringSet names;
|
||||
for (auto & path : storePaths)
|
||||
names.insert(DrvName(path.name()).name);
|
||||
|
@ -126,7 +126,7 @@ struct ProfileManifest
|
|||
parseFlakeRef(e[sOriginalUrl]),
|
||||
parseFlakeRef(e[sUrl]),
|
||||
e["attrPath"],
|
||||
e["outputs"].get<OutputsSpec>()
|
||||
e["outputs"].get<ExtendedOutputsSpec>()
|
||||
};
|
||||
}
|
||||
elements.emplace_back(std::move(element));
|
||||
|
@ -308,12 +308,12 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
|
|||
|
||||
auto & [res, info] = builtPaths[installable.get()];
|
||||
|
||||
if (info.originalRef && info.resolvedRef && info.attrPath && info.outputsSpec) {
|
||||
if (info.originalRef && info.resolvedRef && info.attrPath && info.extendedOutputsSpec) {
|
||||
element.source = ProfileElementSource {
|
||||
.originalRef = *info.originalRef,
|
||||
.resolvedRef = *info.resolvedRef,
|
||||
.attrPath = *info.attrPath,
|
||||
.outputs = *info.outputsSpec,
|
||||
.outputs = *info.extendedOutputsSpec,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -497,7 +497,7 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
|
|||
.originalRef = installable->flakeRef,
|
||||
.resolvedRef = *info.resolvedRef,
|
||||
.attrPath = *info.attrPath,
|
||||
.outputs = installable->outputsSpec,
|
||||
.outputs = installable->extendedOutputsSpec,
|
||||
};
|
||||
|
||||
installables.push_back(installable);
|
||||
|
@ -553,8 +553,8 @@ struct CmdProfileList : virtual EvalCommand, virtual StoreCommand, MixDefaultPro
|
|||
for (size_t i = 0; i < manifest.elements.size(); ++i) {
|
||||
auto & element(manifest.elements[i]);
|
||||
logger->cout("%d %s %s %s", i,
|
||||
element.source ? element.source->originalRef.to_string() + "#" + element.source->attrPath + printOutputsSpec(element.source->outputs) : "-",
|
||||
element.source ? element.source->resolvedRef.to_string() + "#" + element.source->attrPath + printOutputsSpec(element.source->outputs) : "-",
|
||||
element.source ? element.source->originalRef.to_string() + "#" + element.source->attrPath + element.source->outputs.to_string() : "-",
|
||||
element.source ? element.source->resolvedRef.to_string() + "#" + element.source->attrPath + element.source->outputs.to_string() : "-",
|
||||
concatStringsSep(" ", store->printStorePathSet(element.storePaths)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,15 +9,44 @@ using namespace nix;
|
|||
|
||||
struct CmdShowConfig : Command, MixJSON
|
||||
{
|
||||
std::optional<std::string> name;
|
||||
|
||||
CmdShowConfig() {
|
||||
expectArgs({
|
||||
.label = {"name"},
|
||||
.optional = true,
|
||||
.handler = {&name},
|
||||
});
|
||||
}
|
||||
|
||||
std::string description() override
|
||||
{
|
||||
return "show the Nix configuration";
|
||||
return "show the Nix configuration or the value of a specific setting";
|
||||
}
|
||||
|
||||
Category category() override { return catUtility; }
|
||||
|
||||
void run() override
|
||||
{
|
||||
if (name) {
|
||||
if (json) {
|
||||
throw UsageError("'--json' is not supported when specifying a setting name");
|
||||
}
|
||||
|
||||
std::map<std::string, Config::SettingInfo> settings;
|
||||
globalConfig.getSettings(settings);
|
||||
auto setting = settings.find(*name);
|
||||
|
||||
if (setting == settings.end()) {
|
||||
throw Error("could not find setting '%1%'", *name);
|
||||
} else {
|
||||
const auto & value = setting->second.value;
|
||||
logger->cout("%s", value);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (json) {
|
||||
// FIXME: use appropriate JSON types (bool, ints, etc).
|
||||
logger->cout("%s", globalConfig.toJSON().dump());
|
||||
|
|
|
@ -1,53 +1,6 @@
|
|||
{ busybox }:
|
||||
|
||||
with import ./config.nix;
|
||||
|
||||
let
|
||||
|
||||
mkDerivation = args:
|
||||
derivation ({
|
||||
inherit system;
|
||||
builder = busybox;
|
||||
args = ["sh" "-e" args.builder or (builtins.toFile "builder-${args.name}.sh" "if [ -e .attrs.sh ]; then source .attrs.sh; fi; eval \"$buildCommand\"")];
|
||||
outputHashMode = "recursive";
|
||||
outputHashAlgo = "sha256";
|
||||
__contentAddressed = true;
|
||||
} // removeAttrs args ["builder" "meta"])
|
||||
// { meta = args.meta or {}; };
|
||||
|
||||
input1 = mkDerivation {
|
||||
shell = busybox;
|
||||
name = "build-remote-input-1";
|
||||
buildCommand = "echo FOO > $out";
|
||||
requiredSystemFeatures = ["foo"];
|
||||
};
|
||||
|
||||
input2 = mkDerivation {
|
||||
shell = busybox;
|
||||
name = "build-remote-input-2";
|
||||
buildCommand = "echo BAR > $out";
|
||||
requiredSystemFeatures = ["bar"];
|
||||
};
|
||||
|
||||
input3 = mkDerivation {
|
||||
shell = busybox;
|
||||
name = "build-remote-input-3";
|
||||
buildCommand = ''
|
||||
read x < ${input2}
|
||||
echo $x BAZ > $out
|
||||
'';
|
||||
requiredSystemFeatures = ["baz"];
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
mkDerivation {
|
||||
shell = busybox;
|
||||
name = "build-remote";
|
||||
buildCommand =
|
||||
''
|
||||
read x < ${input1}
|
||||
read y < ${input3}
|
||||
echo "$x $y" > $out
|
||||
'';
|
||||
}
|
||||
import ./build-hook.nix {
|
||||
inherit busybox;
|
||||
contentAddressed = true;
|
||||
}
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
{ busybox }:
|
||||
{ busybox, contentAddressed ? false }:
|
||||
|
||||
with import ./config.nix;
|
||||
|
||||
let
|
||||
|
||||
caArgs = if contentAddressed then {
|
||||
outputHashMode = "recursive";
|
||||
outputHashAlgo = "sha256";
|
||||
__contentAddressed = true;
|
||||
} else {};
|
||||
|
||||
mkDerivation = args:
|
||||
derivation ({
|
||||
inherit system;
|
||||
builder = busybox;
|
||||
args = ["sh" "-e" args.builder or (builtins.toFile "builder-${args.name}.sh" "if [ -e .attrs.sh ]; then source .attrs.sh; fi; eval \"$buildCommand\"")];
|
||||
} // removeAttrs args ["builder" "meta" "passthru"])
|
||||
} // removeAttrs args ["builder" "meta" "passthru"]
|
||||
// caArgs)
|
||||
// { meta = args.meta or {}; passthru = args.passthru or {}; };
|
||||
|
||||
input1 = mkDerivation {
|
||||
|
|
|
@ -63,12 +63,9 @@ nix path-info --store $TEST_ROOT/machine3 --all \
|
|||
| grep builder-build-remote-input-3.sh
|
||||
|
||||
|
||||
# Temporarily disabled because of https://github.com/NixOS/nix/issues/6209
|
||||
if [[ -z "$CONTENT_ADDRESSED" ]]; then
|
||||
for i in input1 input3; do
|
||||
nix log --store $TEST_ROOT/machine0 --file "$file" --arg busybox $busybox passthru."$i" | grep hi-$i
|
||||
done
|
||||
fi
|
||||
for i in input1 input3; do
|
||||
nix log --store $TEST_ROOT/machine0 --file "$file" --arg busybox $busybox passthru."$i" | grep hi-$i
|
||||
done
|
||||
|
||||
# Behavior of keep-failed
|
||||
out="$(nix-build 2>&1 failing.nix \
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -51,3 +51,8 @@ exp_features=$(nix show-config | grep '^experimental-features' | cut -d '=' -f 2
|
|||
[[ $prev != $exp_cores ]]
|
||||
[[ $exp_cores == "4242" ]]
|
||||
[[ $exp_features == "flakes nix-command" ]]
|
||||
|
||||
# Test that it's possible to retrieve a single setting's value
|
||||
val=$(nix show-config | grep '^warn-dirty' | cut -d '=' -f 2 | xargs)
|
||||
val2=$(nix show-config warn-dirty)
|
||||
[[ $val == $val2 ]]
|
||||
|
|
|
@ -4,7 +4,7 @@ clearStore
|
|||
clearProfiles
|
||||
|
||||
checkRef() {
|
||||
nix-store -q --references $TEST_ROOT/result | grep -q "$1" || fail "missing reference $1"
|
||||
nix-store -q --references $TEST_ROOT/result | grep -q "$1"'$' || fail "missing reference $1"
|
||||
}
|
||||
|
||||
# Test the export of the runtime dependency graph.
|
||||
|
|
|
@ -1 +1 @@
|
|||
true
|
||||
[ true true true true true true ]
|
||||
|
|
|
@ -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))
|
||||
]
|
||||
|
|
|
@ -1 +1 @@
|
|||
{ bar = "regular"; foo = "directory"; }
|
||||
{ bar = "regular"; foo = "directory"; ldir = "symlink"; linked = "symlink"; }
|
||||
|
|
1
tests/lang/eval-okay-readFileType.exp
Normal file
1
tests/lang/eval-okay-readFileType.exp
Normal file
|
@ -0,0 +1 @@
|
|||
{ bar = "regular"; foo = "directory"; ldir = "symlink"; linked = "symlink"; }
|
6
tests/lang/eval-okay-readFileType.nix
Normal file
6
tests/lang/eval-okay-readFileType.nix
Normal 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
1
tests/lang/readDir/ldir
Symbolic link
|
@ -0,0 +1 @@
|
|||
foo
|
1
tests/lang/readDir/linked
Symbolic link
1
tests/lang/readDir/linked
Symbolic link
|
@ -0,0 +1 @@
|
|||
foo/git-hates-directories
|
|
@ -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$"];
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue