mirror of
https://github.com/privatevoid-net/nix-super.git
synced 2024-11-24 14:56:15 +02:00
Merge remote-tracking branch 'nixos/master'
This commit is contained in:
commit
1c35324a97
93 changed files with 1957 additions and 1199 deletions
2
.github/workflows/backport.yml
vendored
2
.github/workflows/backport.yml
vendored
|
@ -21,7 +21,7 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Create backport PRs
|
- name: Create backport PRs
|
||||||
# should be kept in sync with `version`
|
# should be kept in sync with `version`
|
||||||
uses: zeebe-io/backport-action@v0.0.9
|
uses: zeebe-io/backport-action@v1.0.1
|
||||||
with:
|
with:
|
||||||
# Config README: https://github.com/zeebe-io/backport-action#backport-action
|
# Config README: https://github.com/zeebe-io/backport-action#backport-action
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
|
@ -41,8 +41,6 @@ AC_DEFINE_UNQUOTED(SYSTEM, ["$system"], [platform identifier ('cpu-os')])
|
||||||
test "$localstatedir" = '${prefix}/var' && localstatedir=/nix/var
|
test "$localstatedir" = '${prefix}/var' && localstatedir=/nix/var
|
||||||
|
|
||||||
|
|
||||||
CFLAGS=
|
|
||||||
CXXFLAGS=
|
|
||||||
AC_PROG_CC
|
AC_PROG_CC
|
||||||
AC_PROG_CXX
|
AC_PROG_CXX
|
||||||
AC_PROG_CPP
|
AC_PROG_CPP
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
- [Nix Language](language/index.md)
|
- [Nix Language](language/index.md)
|
||||||
- [Data Types](language/values.md)
|
- [Data Types](language/values.md)
|
||||||
- [Language Constructs](language/constructs.md)
|
- [Language Constructs](language/constructs.md)
|
||||||
|
- [String interpolation](language/string-interpolation.md)
|
||||||
- [Operators](language/operators.md)
|
- [Operators](language/operators.md)
|
||||||
- [Derivations](language/derivations.md)
|
- [Derivations](language/derivations.md)
|
||||||
- [Advanced Attributes](language/advanced-attributes.md)
|
- [Advanced Attributes](language/advanced-attributes.md)
|
||||||
|
@ -59,6 +60,7 @@
|
||||||
@manpages@
|
@manpages@
|
||||||
- [Files](command-ref/files.md)
|
- [Files](command-ref/files.md)
|
||||||
- [nix.conf](command-ref/conf-file.md)
|
- [nix.conf](command-ref/conf-file.md)
|
||||||
|
- [Architecture](architecture/architecture.md)
|
||||||
- [Glossary](glossary.md)
|
- [Glossary](glossary.md)
|
||||||
- [Contributing](contributing/contributing.md)
|
- [Contributing](contributing/contributing.md)
|
||||||
- [Hacking](contributing/hacking.md)
|
- [Hacking](contributing/hacking.md)
|
||||||
|
|
115
doc/manual/src/architecture/architecture.md
Normal file
115
doc/manual/src/architecture/architecture.md
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
# Architecture
|
||||||
|
|
||||||
|
This chapter describes how Nix works.
|
||||||
|
It should help users understand why Nix behaves as it does, and it should help developers understand how to modify Nix and how to write similar tools.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Nix consists of [hierarchical layers].
|
||||||
|
|
||||||
|
[hierarchical layers]: https://en.m.wikipedia.org/wiki/Multitier_architecture#Layers
|
||||||
|
|
||||||
|
The following [concept map] shows its main components (rectangles), the objects they operate on (rounded rectangles), and their interactions (connecting phrases):
|
||||||
|
|
||||||
|
[concept map]: https://en.m.wikipedia.org/wiki/Concept_map
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
.----------------.
|
||||||
|
| Nix expression |----------.
|
||||||
|
'----------------' |
|
||||||
|
| passed to
|
||||||
|
| |
|
||||||
|
+----------|-------------------|--------------------------------+
|
||||||
|
| Nix | V |
|
||||||
|
| | +-------------------------+ |
|
||||||
|
| | | commmand line interface |------. |
|
||||||
|
| | +-------------------------+ | |
|
||||||
|
| | | | |
|
||||||
|
| evaluated by calls manages |
|
||||||
|
| | | | |
|
||||||
|
| | V | |
|
||||||
|
| | +--------------------+ | |
|
||||||
|
| '-------->| language evaluator | | |
|
||||||
|
| +--------------------+ | |
|
||||||
|
| | | |
|
||||||
|
| produces | |
|
||||||
|
| | V |
|
||||||
|
| +----------------------------|------------------------------+ |
|
||||||
|
| | store | | |
|
||||||
|
| | referenced by V builds | |
|
||||||
|
| | .-------------. .------------. .--------------. | |
|
||||||
|
| | | build input |----->| build plan |----->| build result | | |
|
||||||
|
| | '-------------' '------------' '--------------' | |
|
||||||
|
| +-------------------------------------------------|---------+ |
|
||||||
|
+---------------------------------------------------|-----------+
|
||||||
|
|
|
||||||
|
represented as
|
||||||
|
|
|
||||||
|
V
|
||||||
|
.---------------.
|
||||||
|
| file |
|
||||||
|
'---------------'
|
||||||
|
```
|
||||||
|
|
||||||
|
At the top is the [command line interface](../command-ref/command-ref.md) that drives the underlying layers.
|
||||||
|
|
||||||
|
The [Nix language](../language/index.md) evaluator transforms Nix expressions into self-contained *build plans*, which are used to derive *build results* from referenced *build inputs*.
|
||||||
|
|
||||||
|
The command line interface and Nix expressions are what users deal with most.
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
> The Nix language itself does not have a notion of *packages* or *configurations*.
|
||||||
|
> As far as we are concerned here, the inputs and results of a build plan are just data.
|
||||||
|
|
||||||
|
Underlying the command line interface and the Nix language evaluator is the [Nix store](../glossary.md#gloss-store), a mechanism to keep track of build plans, data, and references between them.
|
||||||
|
It can also execute build plans to produce new data, which are made available to the operating system as files.
|
||||||
|
|
||||||
|
A build plan itself is a series of *build tasks*, together with their build inputs.
|
||||||
|
|
||||||
|
> **Important**
|
||||||
|
> A build task in Nix is called [derivation](../glossary#gloss-derivation).
|
||||||
|
|
||||||
|
Each build task has a special build input executed as *build instructions* in order to perform the build.
|
||||||
|
The result of a build task can be input to another build task.
|
||||||
|
|
||||||
|
The following [data flow diagram] shows a build plan for illustration.
|
||||||
|
Build inputs used as instructions to a build task are marked accordingly:
|
||||||
|
|
||||||
|
[data flow diagram]: https://en.m.wikipedia.org/wiki/Data-flow_diagram
|
||||||
|
|
||||||
|
```
|
||||||
|
+--------------------------------------------------------------------+
|
||||||
|
| build plan |
|
||||||
|
| |
|
||||||
|
| .-------------. |
|
||||||
|
| | build input |---------. |
|
||||||
|
| '-------------' | |
|
||||||
|
| instructions |
|
||||||
|
| | |
|
||||||
|
| v |
|
||||||
|
| .-------------. .----------. |
|
||||||
|
| | build input |-->( build task )-------. |
|
||||||
|
| '-------------' '----------' | |
|
||||||
|
| instructions |
|
||||||
|
| | |
|
||||||
|
| v |
|
||||||
|
| .-------------. .----------. .--------------. |
|
||||||
|
| | build input |---------. ( build task )--->| build result | |
|
||||||
|
| '-------------' | '----------' '--------------' |
|
||||||
|
| instructions ^ |
|
||||||
|
| | | |
|
||||||
|
| v | |
|
||||||
|
| .-------------. .----------. | |
|
||||||
|
| | build input |-->( build task )-------' |
|
||||||
|
| '-------------' '----------' |
|
||||||
|
| ^ |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| .-------------. | |
|
||||||
|
| | build input |---------' |
|
||||||
|
| '-------------' |
|
||||||
|
| |
|
||||||
|
+--------------------------------------------------------------------+
|
||||||
|
```
|
||||||
|
|
|
@ -37,10 +37,12 @@ directory containing at least a file named `default.nix`.
|
||||||
|
|
||||||
`nix-build` is essentially a wrapper around
|
`nix-build` is essentially a wrapper around
|
||||||
[`nix-instantiate`](nix-instantiate.md) (to translate a high-level Nix
|
[`nix-instantiate`](nix-instantiate.md) (to translate a high-level Nix
|
||||||
expression to a low-level store derivation) and [`nix-store
|
expression to a low-level [store derivation]) and [`nix-store
|
||||||
--realise`](nix-store.md#operation---realise) (to build the store
|
--realise`](nix-store.md#operation---realise) (to build the store
|
||||||
derivation).
|
derivation).
|
||||||
|
|
||||||
|
[store derivation]: ../glossary.md#gloss-store-derivation
|
||||||
|
|
||||||
> **Warning**
|
> **Warning**
|
||||||
>
|
>
|
||||||
> The result of the build is automatically registered as a root of the
|
> The result of the build is automatically registered as a root of the
|
||||||
|
|
|
@ -47,7 +47,9 @@ authentication, you can avoid typing the passphrase with `ssh-agent`.
|
||||||
Enable compression of the SSH connection.
|
Enable compression of the SSH connection.
|
||||||
|
|
||||||
- `--include-outputs`\
|
- `--include-outputs`\
|
||||||
Also copy the outputs of store derivations included in the closure.
|
Also copy the outputs of [store derivation]s included in the closure.
|
||||||
|
|
||||||
|
[store derivation]: ../../glossary.md#gloss-store-derivation
|
||||||
|
|
||||||
- `--use-substitutes` / `-s`\
|
- `--use-substitutes` / `-s`\
|
||||||
Attempt to download missing paths on the target machine using Nix’s
|
Attempt to download missing paths on the target machine using Nix’s
|
||||||
|
|
|
@ -205,10 +205,12 @@ a number of possible ways:
|
||||||
unambiguous way, which is necessary if there are multiple
|
unambiguous way, which is necessary if there are multiple
|
||||||
derivations with the same name.
|
derivations with the same name.
|
||||||
|
|
||||||
- If *args* are store derivations, then these are
|
- If *args* are [store derivation]s, then these are
|
||||||
[realised](nix-store.md#operation---realise), and the resulting output paths
|
[realised](nix-store.md#operation---realise), and the resulting output paths
|
||||||
are installed.
|
are installed.
|
||||||
|
|
||||||
|
[store derivation]: ../glossary.md#gloss-store-derivation
|
||||||
|
|
||||||
- If *args* are store paths that are not store derivations, then these
|
- If *args* are store paths that are not store derivations, then these
|
||||||
are [realised](nix-store.md#operation---realise) and installed.
|
are [realised](nix-store.md#operation---realise) and installed.
|
||||||
|
|
||||||
|
@ -280,7 +282,7 @@ To copy the store path with symbolic name `gcc` from another profile:
|
||||||
$ nix-env -i --from-profile /nix/var/nix/profiles/foo gcc
|
$ nix-env -i --from-profile /nix/var/nix/profiles/foo gcc
|
||||||
```
|
```
|
||||||
|
|
||||||
To install a specific store derivation (typically created by
|
To install a specific [store derivation] (typically created by
|
||||||
`nix-instantiate`):
|
`nix-instantiate`):
|
||||||
|
|
||||||
```console
|
```console
|
||||||
|
@ -665,7 +667,7 @@ derivation is shown unless `--no-name` is specified.
|
||||||
Print the `system` attribute of the derivation.
|
Print the `system` attribute of the derivation.
|
||||||
|
|
||||||
- `--drv-path`\
|
- `--drv-path`\
|
||||||
Print the path of the store derivation.
|
Print the path of the [store derivation].
|
||||||
|
|
||||||
- `--out-path`\
|
- `--out-path`\
|
||||||
Print the output path of the derivation.
|
Print the output path of the derivation.
|
||||||
|
|
|
@ -17,13 +17,14 @@
|
||||||
|
|
||||||
# Description
|
# Description
|
||||||
|
|
||||||
The command `nix-instantiate` generates [store
|
The command `nix-instantiate` produces [store derivation]s from (high-level) Nix expressions.
|
||||||
derivations](../glossary.md) from (high-level) Nix expressions. It
|
It evaluates the Nix expressions in each of *files* (which defaults to
|
||||||
evaluates the Nix expressions in each of *files* (which defaults to
|
|
||||||
*./default.nix*). Each top-level expression should evaluate to a
|
*./default.nix*). Each top-level expression should evaluate to a
|
||||||
derivation, a list of derivations, or a set of derivations. The paths
|
derivation, a list of derivations, or a set of derivations. The paths
|
||||||
of the resulting store derivations are printed on standard output.
|
of the resulting store derivations are printed on standard output.
|
||||||
|
|
||||||
|
[store derivation]: ../glossary.md#gloss-store-derivation
|
||||||
|
|
||||||
If *files* is the character `-`, then a Nix expression will be read from
|
If *files* is the character `-`, then a Nix expression will be read from
|
||||||
standard input.
|
standard input.
|
||||||
|
|
||||||
|
@ -79,8 +80,7 @@ standard input.
|
||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
|
|
||||||
Instantiating store derivations from a Nix expression, and building them
|
Instantiate [store derivation]s from a Nix expression, and build them using `nix-store`:
|
||||||
using `nix-store`:
|
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ nix-instantiate test.nix (instantiate)
|
$ nix-instantiate test.nix (instantiate)
|
||||||
|
|
|
@ -137,8 +137,10 @@ or.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
This operation is typically used to build store derivations produced by
|
This operation is typically used to build [store derivation]s produced by
|
||||||
[`nix-instantiate`](nix-instantiate.md):
|
[`nix-instantiate`](./nix-instantiate.md):
|
||||||
|
|
||||||
|
[store derivation]: ../glossary.md#gloss-store-derivation
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ nix-store -r $(nix-instantiate ./test.nix)
|
$ nix-store -r $(nix-instantiate ./test.nix)
|
||||||
|
@ -298,7 +300,7 @@ symlink.
|
||||||
## Common query options
|
## Common query options
|
||||||
|
|
||||||
- `--use-output`; `-u`\
|
- `--use-output`; `-u`\
|
||||||
For each argument to the query that is a store derivation, apply the
|
For each argument to the query that is a [store derivation], apply the
|
||||||
query to the output path of the derivation instead.
|
query to the output path of the derivation instead.
|
||||||
|
|
||||||
- `--force-realise`; `-f`\
|
- `--force-realise`; `-f`\
|
||||||
|
@ -318,7 +320,7 @@ symlink.
|
||||||
This query has one option:
|
This query has one option:
|
||||||
|
|
||||||
- `--include-outputs`
|
- `--include-outputs`
|
||||||
Also include the existing output paths of store derivations,
|
Also include the existing output paths of [store derivation]s,
|
||||||
and their closures.
|
and their closures.
|
||||||
|
|
||||||
This query can be used to implement various kinds of deployment. A
|
This query can be used to implement various kinds of deployment. A
|
||||||
|
@ -372,12 +374,12 @@ symlink.
|
||||||
Prints the references graph of the store paths *paths* in the
|
Prints the references graph of the store paths *paths* in the
|
||||||
[GraphML](http://graphml.graphdrawing.org/) file format. This can be
|
[GraphML](http://graphml.graphdrawing.org/) file format. This can be
|
||||||
used to visualise dependency graphs. To obtain a build-time
|
used to visualise dependency graphs. To obtain a build-time
|
||||||
dependency graph, apply this to a store derivation. To obtain a
|
dependency graph, apply this to a [store derivation]. To obtain a
|
||||||
runtime dependency graph, apply it to an output path.
|
runtime dependency graph, apply it to an output path.
|
||||||
|
|
||||||
- `--binding` *name*; `-b` *name*\
|
- `--binding` *name*; `-b` *name*\
|
||||||
Prints the value of the attribute *name* (i.e., environment
|
Prints the value of the attribute *name* (i.e., environment
|
||||||
variable) of the store derivations *paths*. It is an error for a
|
variable) of the [store derivation]s *paths*. It is an error for a
|
||||||
derivation to not have the specified attribute.
|
derivation to not have the specified attribute.
|
||||||
|
|
||||||
- `--hash`\
|
- `--hash`\
|
||||||
|
|
|
@ -99,8 +99,79 @@ You can run the whole testsuite with `make check`, or the tests for a specific c
|
||||||
### Functional tests
|
### Functional tests
|
||||||
|
|
||||||
The functional tests reside under the `tests` directory and are listed in `tests/local.mk`.
|
The functional tests reside under the `tests` directory and are listed in `tests/local.mk`.
|
||||||
The whole testsuite can be run with `make install && make installcheck`.
|
Each test is a bash script.
|
||||||
Individual tests can be run with `make tests/{testName}.sh.test`.
|
|
||||||
|
The whole test suite can be run with:
|
||||||
|
|
||||||
|
```shell-session
|
||||||
|
$ make install && make installcheck
|
||||||
|
ran test tests/foo.sh... [PASS]
|
||||||
|
ran test tests/bar.sh... [PASS]
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Individual tests can be run with `make`:
|
||||||
|
|
||||||
|
```shell-session
|
||||||
|
$ make tests/${testName}.sh.test
|
||||||
|
ran test tests/${testName}.sh... [PASS]
|
||||||
|
```
|
||||||
|
|
||||||
|
or without `make`:
|
||||||
|
|
||||||
|
```shell-session
|
||||||
|
$ ./mk/run-test.sh tests/${testName}.sh
|
||||||
|
ran test tests/${testName}.sh... [PASS]
|
||||||
|
```
|
||||||
|
|
||||||
|
To see the complete output, one can also run:
|
||||||
|
|
||||||
|
```shell-session
|
||||||
|
$ ./mk/debug-test.sh tests/${testName}.sh
|
||||||
|
+ foo
|
||||||
|
output from foo
|
||||||
|
+ bar
|
||||||
|
output from bar
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
The test script will then be traced with `set -x` and the output displayed as it happens, regardless of whether the test succeeds or fails.
|
||||||
|
|
||||||
|
#### Debugging failing functional tests
|
||||||
|
|
||||||
|
When a functional test fails, it usually does so somewhere in the middle of the script.
|
||||||
|
|
||||||
|
To figure out what's wrong, it is convenient to run the test regularly up to the failing `nix` command, and then run that command with a debugger like GDB.
|
||||||
|
|
||||||
|
For example, if the script looks like:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
foo
|
||||||
|
nix blah blub
|
||||||
|
bar
|
||||||
|
```
|
||||||
|
edit it like so:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
foo
|
||||||
|
-nix blah blub
|
||||||
|
+gdb --args nix blah blub
|
||||||
|
bar
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, running the test with `./mk/debug-test.sh` will drop you into GDB once the script reaches that point:
|
||||||
|
|
||||||
|
```shell-session
|
||||||
|
$ ./mk/debug-test.sh tests/${testName}.sh
|
||||||
|
...
|
||||||
|
+ gdb blash blub
|
||||||
|
GNU gdb (GDB) 12.1
|
||||||
|
...
|
||||||
|
(gdb)
|
||||||
|
```
|
||||||
|
|
||||||
|
One can debug the Nix invocation in all the usual ways.
|
||||||
|
For example, enter `run` to start the Nix invocation.
|
||||||
|
|
||||||
### Integration tests
|
### Integration tests
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,18 @@
|
||||||
translated into low-level *store derivations* (implicitly by
|
translated into low-level *store derivations* (implicitly by
|
||||||
`nix-env` and `nix-build`, or explicitly by `nix-instantiate`).
|
`nix-env` and `nix-build`, or explicitly by `nix-instantiate`).
|
||||||
|
|
||||||
|
[derivation]: #gloss-derivation
|
||||||
|
|
||||||
|
- [store derivation]{#gloss-store-derivation}\
|
||||||
|
A [derivation] represented as a `.drv` file in the [store].
|
||||||
|
It has a [store path], like any [store object].
|
||||||
|
|
||||||
|
Example: `/nix/store/g946hcz4c8mdvq2g8vxx42z51qb71rvp-git-2.38.1.drv`
|
||||||
|
|
||||||
|
See [`nix show-derivation`](./command-ref/new-cli/nix3-show-derivation.md) (experimental) for displaying the contents of store derivations.
|
||||||
|
|
||||||
|
[store derivation]: #gloss-store-derivation
|
||||||
|
|
||||||
- [content-addressed derivation]{#gloss-content-addressed-derivation}\
|
- [content-addressed derivation]{#gloss-content-addressed-derivation}\
|
||||||
A derivation which has the
|
A derivation which has the
|
||||||
[`__contentAddressed`](./language/advanced-attributes.md#adv-attr-__contentAddressed)
|
[`__contentAddressed`](./language/advanced-attributes.md#adv-attr-__contentAddressed)
|
||||||
|
@ -34,6 +46,8 @@
|
||||||
directory on another machine, accessed via `ssh` or
|
directory on another machine, accessed via `ssh` or
|
||||||
served by the `nix-serve` Perl script.
|
served by the `nix-serve` Perl script.
|
||||||
|
|
||||||
|
[store]: #gloss-store
|
||||||
|
|
||||||
- [chroot store]{#gloss-chroot-store}\
|
- [chroot store]{#gloss-chroot-store}\
|
||||||
A local store whose canonical path is anything other than `/nix/store`.
|
A local store whose canonical path is anything other than `/nix/store`.
|
||||||
|
|
||||||
|
@ -46,9 +60,13 @@
|
||||||
cache](https://cache.nixos.org).
|
cache](https://cache.nixos.org).
|
||||||
|
|
||||||
- [store path]{#gloss-store-path}\
|
- [store path]{#gloss-store-path}\
|
||||||
The location in the file system of a store object, i.e., an
|
The location of a [store object] in the file system, i.e., an
|
||||||
immediate child of the Nix store directory.
|
immediate child of the Nix store directory.
|
||||||
|
|
||||||
|
Example: `/nix/store/a040m110amc4h71lds2jmr8qrkj2jhxd-git-2.38.1`
|
||||||
|
|
||||||
|
[store path]: #gloss-store-path
|
||||||
|
|
||||||
- [store object]{#gloss-store-object}\
|
- [store object]{#gloss-store-object}\
|
||||||
A file that is an immediate child of the Nix store directory. These
|
A file that is an immediate child of the Nix store directory. These
|
||||||
can be regular files, but also entire directory trees. Store objects
|
can be regular files, but also entire directory trees. Store objects
|
||||||
|
@ -56,6 +74,8 @@
|
||||||
derivation outputs (objects produced by running a build task), or
|
derivation outputs (objects produced by running a build task), or
|
||||||
derivations (files describing a build task).
|
derivations (files describing a build task).
|
||||||
|
|
||||||
|
[store object]: #gloss-store-object
|
||||||
|
|
||||||
- [input-addressed store object]{#gloss-input-addressed-store-object}\
|
- [input-addressed store object]{#gloss-input-addressed-store-object}\
|
||||||
A store object produced by building a
|
A store object produced by building a
|
||||||
non-[content-addressed](#gloss-content-addressed-derivation),
|
non-[content-addressed](#gloss-content-addressed-derivation),
|
||||||
|
@ -124,7 +144,9 @@
|
||||||
references `R` then `R` is also in the closure of `P`.
|
references `R` then `R` is also in the closure of `P`.
|
||||||
|
|
||||||
- [output path]{#gloss-output-path}\
|
- [output path]{#gloss-output-path}\
|
||||||
A store path produced by a derivation.
|
A [store path] produced by a [derivation].
|
||||||
|
|
||||||
|
[output path]: #gloss-output-path
|
||||||
|
|
||||||
- [deriver]{#gloss-deriver}\
|
- [deriver]{#gloss-deriver}\
|
||||||
The deriver of an *output path* is the store
|
The deriver of an *output path* is the store
|
||||||
|
@ -156,3 +178,12 @@
|
||||||
|
|
||||||
- [`ε`]{#gloss-epsilon}\
|
- [`ε`]{#gloss-epsilon}\
|
||||||
The epsilon symbol. In the context of a package, this means the version is empty. More precisely, the derivation does not have a version attribute.
|
The epsilon symbol. In the context of a package, this means the version is empty. More precisely, the derivation does not have a version attribute.
|
||||||
|
|
||||||
|
- [string interpolation]{#gloss-string-interpolation}\
|
||||||
|
Expanding expressions enclosed in `${ }` within a [string], [path], or [attribute name].
|
||||||
|
|
||||||
|
See [String interpolation](./language/string-interpolation.md) for details.
|
||||||
|
|
||||||
|
[string]: ./language/values.md#type-string
|
||||||
|
[path]: ./language/values.md#type-path
|
||||||
|
[attribute name]: ./language/values.md#attribute-set
|
||||||
|
|
|
@ -120,10 +120,10 @@ sudo rm -rf /nix /etc/nix /etc/profile/nix.sh ~root/.nix-profile ~root/.nix-defe
|
||||||
Remove build users and their group:
|
Remove build users and their group:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
for i in $(seq 30001 30032); do
|
for i in $(seq 1 32); do
|
||||||
sudo userdel $i
|
sudo userdel nixbld$i
|
||||||
done
|
done
|
||||||
sudo groupdel 30000
|
sudo groupdel nixbld
|
||||||
```
|
```
|
||||||
|
|
||||||
There may also be references to Nix in
|
There may also be references to Nix in
|
||||||
|
|
|
@ -1,28 +1,167 @@
|
||||||
# Operators
|
# Operators
|
||||||
|
|
||||||
The table below lists the operators in the Nix language, in
|
| Name | Syntax | Associativity | Precedence |
|
||||||
order of precedence (from strongest to weakest binding).
|
|----------------------------------------|--------------------------------------------|---------------|------------|
|
||||||
|
| [Attribute selection] | *attrset* `.` *attrpath* \[ `or` *expr* \] | none | 1 |
|
||||||
|
| Function application | *func* *expr* | left | 2 |
|
||||||
|
| [Arithmetic negation][arithmetic] | `-` *number* | none | 3 |
|
||||||
|
| [Has attribute] | *attrset* `?` *attrpath* | none | 4 |
|
||||||
|
| List concatenation | *list* `++` *list* | right | 5 |
|
||||||
|
| [Multiplication][arithmetic] | *number* `*` *number* | left | 6 |
|
||||||
|
| [Division][arithmetic] | *number* `/` *number* | left | 6 |
|
||||||
|
| [Subtraction][arithmetic] | *number* `-` *number* | left | 7 |
|
||||||
|
| [Addition][arithmetic] | *number* `+` *number* | left | 7 |
|
||||||
|
| [String concatenation] | *string* `+` *string* | left | 7 |
|
||||||
|
| [Path concatenation] | *path* `+` *path* | left | 7 |
|
||||||
|
| [Path and string concatenation] | *path* `+` *string* | left | 7 |
|
||||||
|
| [String and path concatenation] | *string* `+` *path* | left | 7 |
|
||||||
|
| Logical negation (`NOT`) | `!` *bool* | none | 8 |
|
||||||
|
| [Update] | *attrset* `//` *attrset* | right | 9 |
|
||||||
|
| [Less than][Comparison] | *expr* `<` *expr* | none | 10 |
|
||||||
|
| [Less than or equal to][Comparison] | *expr* `<=` *expr* | none | 10 |
|
||||||
|
| [Greater than][Comparison] | *expr* `>` *expr* | none | 10 |
|
||||||
|
| [Greater than or equal to][Comparison] | *expr* `>=` *expr* | none | 10 |
|
||||||
|
| [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 implication] | *bool* `->` *bool* | none | 14 |
|
||||||
|
|
||||||
|
[string]: ./values.md#type-string
|
||||||
|
[path]: ./values.md#type-path
|
||||||
|
[number]: ./values.md#type-number
|
||||||
|
[list]: ./values.md#list
|
||||||
|
[attribute set]: ./values.md#attribute-set
|
||||||
|
|
||||||
|
## Attribute selection
|
||||||
|
|
||||||
|
Select the attribute denoted by attribute path *attrpath* from [attribute set] *attrset*.
|
||||||
|
If the attribute doesn’t exist, return *value* if provided, otherwise abort evaluation.
|
||||||
|
|
||||||
|
<!-- FIXME: the following should to into its own language syntax section, but that needs more work to fit in well -->
|
||||||
|
|
||||||
|
An attribute path is a dot-separated list of attribute names.
|
||||||
|
An attribute name can be an identifier or a string.
|
||||||
|
|
||||||
|
> *attrpath* = *name* [ `.` *name* ]...
|
||||||
|
> *name* = *identifier* | *string*
|
||||||
|
> *identifier* ~ `[a-zA-Z_][a-zA-Z0-9_'-]*`
|
||||||
|
|
||||||
|
[Attribute selection]: #attribute-selection
|
||||||
|
|
||||||
|
## Has attribute
|
||||||
|
|
||||||
|
> *attrset* `?` *attrpath*
|
||||||
|
|
||||||
|
Test whether [attribute set] *attrset* contains the attribute denoted by *attrpath*.
|
||||||
|
The result is a [Boolean] value.
|
||||||
|
|
||||||
|
[Boolean]: ./values.md#type-boolean
|
||||||
|
|
||||||
|
[Has attribute]: #has-attribute
|
||||||
|
|
||||||
|
## Arithmetic
|
||||||
|
|
||||||
|
Numbers are type-compatible:
|
||||||
|
Pure integer operations will always return integers, whereas any operation involving at least one floating point number return a floating point number.
|
||||||
|
|
||||||
|
See also [Comparison] and [Equality].
|
||||||
|
|
||||||
|
The `+` operator is overloaded to also work on strings and paths.
|
||||||
|
|
||||||
|
[arithmetic]: #arithmetic
|
||||||
|
|
||||||
|
## String concatenation
|
||||||
|
|
||||||
|
> *string* `+` *string*
|
||||||
|
|
||||||
|
Concatenate two [string]s and merge their string contexts.
|
||||||
|
|
||||||
|
[String concatenation]: #string-concatenation
|
||||||
|
|
||||||
|
## Path concatenation
|
||||||
|
|
||||||
|
> *path* `+` *path*
|
||||||
|
|
||||||
|
Concatenate two [path]s.
|
||||||
|
The result is a path.
|
||||||
|
|
||||||
|
[Path concatenation]: #path-concatenation
|
||||||
|
|
||||||
|
## Path and string concatenation
|
||||||
|
|
||||||
|
> *path* + *string*
|
||||||
|
|
||||||
|
Concatenate *[path]* with *[string]*.
|
||||||
|
The result is a path.
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> The string must not have a string context that refers to a [store path].
|
||||||
|
|
||||||
|
[Path and string concatenation]: #path-and-string-concatenation
|
||||||
|
|
||||||
|
## String and path concatenation
|
||||||
|
|
||||||
|
> *string* + *path*
|
||||||
|
|
||||||
|
Concatenate *[string]* with *[path]*.
|
||||||
|
The result is a string.
|
||||||
|
|
||||||
|
> **Important**
|
||||||
|
>
|
||||||
|
> The file or directory at *path* must exist and is copied to the [store].
|
||||||
|
> The path appears in the result as the corresponding [store path].
|
||||||
|
|
||||||
|
[store path]: ../glossary.md#gloss-store-path
|
||||||
|
[store]: ../glossary.md#gloss-store
|
||||||
|
|
||||||
|
[Path and string concatenation]: #path-and-string-concatenation
|
||||||
|
|
||||||
|
## Update
|
||||||
|
|
||||||
|
> *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.
|
||||||
|
|
||||||
|
[Update]: #update
|
||||||
|
|
||||||
|
## Comparison
|
||||||
|
|
||||||
|
Comparison is
|
||||||
|
|
||||||
|
- [arithmetic] for [number]s
|
||||||
|
- lexicographic for [string]s and [path]s
|
||||||
|
- item-wise lexicographic for [list]s:
|
||||||
|
elements at the same index in both lists are compared according to their type and skipped if they are equal.
|
||||||
|
|
||||||
|
All comparison operators are implemented in terms of `<`, and the following equivalencies hold:
|
||||||
|
|
||||||
|
| comparison | implementation |
|
||||||
|
|--------------|-----------------------|
|
||||||
|
| *a* `<=` *b* | `! (` *b* `<` *a* `)` |
|
||||||
|
| *a* `>` *b* | *b* `<` *a* |
|
||||||
|
| *a* `>=` *b* | `! (` *a* `<` *b* `)` |
|
||||||
|
|
||||||
|
[Comparison]: #comparison-operators
|
||||||
|
|
||||||
|
## Equality
|
||||||
|
|
||||||
|
- [Attribute sets][attribute set] and [list]s are compared recursively, and therefore are fully evaluated.
|
||||||
|
- Comparison of [function]s always returns `false`.
|
||||||
|
- Numbers are type-compatible, see [arithmetic] operators.
|
||||||
|
- Floating point numbers only differ up to a limited precision.
|
||||||
|
|
||||||
|
[function]: ./constructs.md#functions
|
||||||
|
|
||||||
|
[Equality]: #equality
|
||||||
|
|
||||||
|
## Logical implication
|
||||||
|
|
||||||
|
Equivalent to `!`*b1* `||` *b2*.
|
||||||
|
|
||||||
|
[Logical implication]: #logical-implication
|
||||||
|
|
||||||
| Name | Syntax | Associativity | Description | Precedence |
|
|
||||||
| ------------------------ | ----------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
|
|
||||||
| Select | *e* `.` *attrpath* \[ `or` *def* \] | none | Select attribute denoted by the attribute path *attrpath* from set *e*. (An attribute path is a dot-separated list of attribute names.) If the attribute doesn’t exist, return *def* if provided, otherwise abort evaluation. | 1 |
|
|
||||||
| Application | *e1* *e2* | left | Call function *e1* with argument *e2*. | 2 |
|
|
||||||
| Arithmetic Negation | `-` *e* | none | Arithmetic negation. | 3 |
|
|
||||||
| Has Attribute | *e* `?` *attrpath* | none | Test whether set *e* contains the attribute denoted by *attrpath*; return `true` or `false`. | 4 |
|
|
||||||
| List Concatenation | *e1* `++` *e2* | right | List concatenation. | 5 |
|
|
||||||
| Multiplication | *e1* `*` *e2*, | left | Arithmetic multiplication. | 6 |
|
|
||||||
| Division | *e1* `/` *e2* | left | Arithmetic division. | 6 |
|
|
||||||
| Addition | *e1* `+` *e2* | left | Arithmetic addition. | 7 |
|
|
||||||
| Subtraction | *e1* `-` *e2* | left | Arithmetic subtraction. | 7 |
|
|
||||||
| String Concatenation | *string1* `+` *string2* | left | String concatenation. | 7 |
|
|
||||||
| Not | `!` *e* | none | Boolean negation. | 8 |
|
|
||||||
| Update | *e1* `//` *e2* | right | Return a set consisting of the attributes in *e1* and *e2* (with the latter taking precedence over the former in case of equally named attributes). | 9 |
|
|
||||||
| Less Than | *e1* `<` *e2*, | none | Arithmetic/lexicographic comparison. | 10 |
|
|
||||||
| Less Than or Equal To | *e1* `<=` *e2* | none | Arithmetic/lexicographic comparison. | 10 |
|
|
||||||
| Greater Than | *e1* `>` *e2* | none | Arithmetic/lexicographic comparison. | 10 |
|
|
||||||
| Greater Than or Equal To | *e1* `>=` *e2* | none | Arithmetic/lexicographic comparison. | 10 |
|
|
||||||
| Equality | *e1* `==` *e2* | none | Equality. | 11 |
|
|
||||||
| Inequality | *e1* `!=` *e2* | none | Inequality. | 11 |
|
|
||||||
| Logical AND | *e1* `&&` *e2* | left | Logical AND. | 12 |
|
|
||||||
| Logical OR | *e1* <code>||</code> *e2* | left | Logical OR. | 13 |
|
|
||||||
| Logical Implication | *e1* `->` *e2* | none | Logical implication (equivalent to <code>!e1 || e2</code>). | 14 |
|
|
||||||
|
|
82
doc/manual/src/language/string-interpolation.md
Normal file
82
doc/manual/src/language/string-interpolation.md
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
# String interpolation
|
||||||
|
|
||||||
|
String interpolation is a language feature where a [string], [path], or [attribute name] can contain expressions enclosed in `${ }` (dollar-sign with curly brackets).
|
||||||
|
|
||||||
|
Such a string is an *interpolated string*, and an expression inside is an *interpolated expression*.
|
||||||
|
|
||||||
|
Interpolated expressions must evaluate to one of the following:
|
||||||
|
|
||||||
|
- a [string]
|
||||||
|
- a [path]
|
||||||
|
- a [derivation]
|
||||||
|
|
||||||
|
[string]: ./values.md#type-string
|
||||||
|
[path]: ./values.md#type-path
|
||||||
|
[attribute name]: ./values.md#attribute-set
|
||||||
|
[derivation]: ../glossary.md#gloss-derivation
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### String
|
||||||
|
|
||||||
|
Rather than writing
|
||||||
|
|
||||||
|
```nix
|
||||||
|
"--with-freetype2-library=" + freetype + "/lib"
|
||||||
|
```
|
||||||
|
|
||||||
|
(where `freetype` is a [derivation]), you can instead write
|
||||||
|
|
||||||
|
```nix
|
||||||
|
"--with-freetype2-library=${freetype}/lib"
|
||||||
|
```
|
||||||
|
|
||||||
|
The latter is automatically translated to the former.
|
||||||
|
|
||||||
|
A more complicated example (from the Nix expression for [Qt](http://www.trolltech.com/products/qt)):
|
||||||
|
|
||||||
|
```nix
|
||||||
|
configureFlags = "
|
||||||
|
-system-zlib -system-libpng -system-libjpeg
|
||||||
|
${if openglSupport then "-dlopen-opengl
|
||||||
|
-L${mesa}/lib -I${mesa}/include
|
||||||
|
-L${libXmu}/lib -I${libXmu}/include" else ""}
|
||||||
|
${if threadSupport then "-thread" else "-no-thread"}
|
||||||
|
";
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that Nix expressions and strings can be arbitrarily nested;
|
||||||
|
in this case the outer string contains various interpolated expressions that themselves contain strings (e.g., `"-thread"`), some of which in turn contain interpolated expressions (e.g., `${mesa}`).
|
||||||
|
|
||||||
|
### Path
|
||||||
|
|
||||||
|
Rather than writing
|
||||||
|
|
||||||
|
```nix
|
||||||
|
./. + "/" + foo + "-" + bar + ".nix"
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```nix
|
||||||
|
./. + "/${foo}-${bar}.nix"
|
||||||
|
```
|
||||||
|
|
||||||
|
you can instead write
|
||||||
|
|
||||||
|
```nix
|
||||||
|
./${foo}-${bar}.nix
|
||||||
|
```
|
||||||
|
|
||||||
|
### Attribute name
|
||||||
|
|
||||||
|
Attribute names can be created dynamically with string interpolation:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
let name = "foo"; in
|
||||||
|
{
|
||||||
|
${name} = "bar";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
{ foo = "bar"; }
|
|
@ -13,41 +13,9 @@
|
||||||
returns and tabs can be written as `\n`, `\r` and `\t`,
|
returns and tabs can be written as `\n`, `\r` and `\t`,
|
||||||
respectively.
|
respectively.
|
||||||
|
|
||||||
You can include the result of an expression into a string by
|
You can include the results of other expressions into a string by enclosing them in `${ }`, a feature known as [string interpolation].
|
||||||
enclosing it in `${...}`, a feature known as *antiquotation*. The
|
|
||||||
enclosed expression must evaluate to something that can be coerced
|
|
||||||
into a string (meaning that it must be a string, a path, or a
|
|
||||||
derivation). For instance, rather than writing
|
|
||||||
|
|
||||||
```nix
|
[string interpolation]: ./string-interpolation.md
|
||||||
"--with-freetype2-library=" + freetype + "/lib"
|
|
||||||
```
|
|
||||||
|
|
||||||
(where `freetype` is a derivation), you can instead write the more
|
|
||||||
natural
|
|
||||||
|
|
||||||
```nix
|
|
||||||
"--with-freetype2-library=${freetype}/lib"
|
|
||||||
```
|
|
||||||
|
|
||||||
The latter is automatically translated to the former. A more
|
|
||||||
complicated example (from the Nix expression for
|
|
||||||
[Qt](http://www.trolltech.com/products/qt)):
|
|
||||||
|
|
||||||
```nix
|
|
||||||
configureFlags = "
|
|
||||||
-system-zlib -system-libpng -system-libjpeg
|
|
||||||
${if openglSupport then "-dlopen-opengl
|
|
||||||
-L${mesa}/lib -I${mesa}/include
|
|
||||||
-L${libXmu}/lib -I${libXmu}/include" else ""}
|
|
||||||
${if threadSupport then "-thread" else "-no-thread"}
|
|
||||||
";
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that Nix expressions and strings can be arbitrarily nested; in
|
|
||||||
this case the outer string contains various antiquotations that
|
|
||||||
themselves contain strings (e.g., `"-thread"`), some of which in
|
|
||||||
turn contain expressions (e.g., `${mesa}`).
|
|
||||||
|
|
||||||
The second way to write string literals is as an *indented string*,
|
The second way to write string literals is as an *indented string*,
|
||||||
which is enclosed between pairs of *double single-quotes*, like so:
|
which is enclosed between pairs of *double single-quotes*, like so:
|
||||||
|
@ -75,7 +43,7 @@
|
||||||
Note that the whitespace and newline following the opening `''` is
|
Note that the whitespace and newline following the opening `''` is
|
||||||
ignored if there is no non-whitespace text on the initial line.
|
ignored if there is no non-whitespace text on the initial line.
|
||||||
|
|
||||||
Antiquotation (`${expr}`) is supported in indented strings.
|
Indented strings support [string interpolation].
|
||||||
|
|
||||||
Since `${` and `''` have special meaning in indented strings, you
|
Since `${` and `''` have special meaning in indented strings, you
|
||||||
need a way to quote them. `$` can be escaped by prefixing it with
|
need a way to quote them. `$` can be escaped by prefixing it with
|
||||||
|
@ -117,9 +85,10 @@
|
||||||
Numbers, which can be *integers* (like `123`) or *floating point*
|
Numbers, which can be *integers* (like `123`) or *floating point*
|
||||||
(like `123.43` or `.27e13`).
|
(like `123.43` or `.27e13`).
|
||||||
|
|
||||||
Numbers are type-compatible: pure integer operations will always
|
See [arithmetic] and [comparison] operators for semantics.
|
||||||
return integers, whereas any operation involving at least one
|
|
||||||
floating point number will have a floating point number as a result.
|
[arithmetic]: ./operators.md#arithmetic
|
||||||
|
[comparison]: ./operators.md#comparison
|
||||||
|
|
||||||
- <a id="type-path" href="#type-path">Path</a>
|
- <a id="type-path" href="#type-path">Path</a>
|
||||||
|
|
||||||
|
@ -143,26 +112,23 @@
|
||||||
environment variable `NIX_PATH` will be searched for the given file
|
environment variable `NIX_PATH` will be searched for the given file
|
||||||
or directory name.
|
or directory name.
|
||||||
|
|
||||||
Antiquotation is supported in any paths except those in angle brackets.
|
When an [interpolated string][string interpolation] evaluates to a path, the path is first copied into the Nix store and the resulting string is the [store path] of the newly created [store object].
|
||||||
`./${foo}-${bar}.nix` is a more convenient way of writing
|
|
||||||
`./. + "/" + foo + "-" + bar + ".nix"` or `./. + "/${foo}-${bar}.nix"`. At
|
|
||||||
least one slash must appear *before* any antiquotations for this to be
|
|
||||||
recognized as a path. `a.${foo}/b.${bar}` is a syntactically valid division
|
|
||||||
operation. `./a.${foo}/b.${bar}` is a path.
|
|
||||||
|
|
||||||
When a path appears in an antiquotation, and is thus coerced into a string,
|
[store path]: ../glossary.md#gloss-store-path
|
||||||
the path is first copied into the Nix store and the resulting string is
|
[store object]: ../glossary.md#gloss-store-object
|
||||||
the Nix store path. For instance `"${./foo.txt}" will cause `foo.txt` in
|
|
||||||
the current directory to be copied into the Nix store and result in the
|
|
||||||
string `"/nix/store/<HASH>-foo.txt"`.
|
|
||||||
|
|
||||||
Note that the Nix language assumes that all input files will remain
|
For instance, evaluating `"${./foo.txt}"` will cause `foo.txt` in the current directory to be copied into the Nix store and result in the string `"/nix/store/<hash>-foo.txt"`.
|
||||||
_unchanged_ during the course of the Nix expression evaluation.
|
|
||||||
If you for example antiquote a file path during a `nix repl` session, and
|
Note that the Nix language assumes that all input files will remain _unchanged_ while evaluating a Nix expression.
|
||||||
then later in the same session, after having changed the file contents,
|
For example, assume you used a file path in an interpolated string during a `nix repl` session.
|
||||||
evaluate the antiquotation with the file path again, then Nix will still
|
Later in the same session, after having changed the file contents, evaluating the interpolated string with the file path again might not return a new store path, since Nix might not re-read the file contents.
|
||||||
return the first store path. It will _not_ reread the file contents to
|
|
||||||
produce a different Nix store path.
|
Paths themselves, except those in angle brackets (`< >`), support [string interpolation].
|
||||||
|
|
||||||
|
At least one slash (`/`) must appear *before* any interpolated expression for the result to be recognized as a path.
|
||||||
|
|
||||||
|
`a.${foo}/b.${bar}` is a syntactically valid division operation.
|
||||||
|
`./a.${foo}/b.${bar}` is a path.
|
||||||
|
|
||||||
- <a id="type-boolean" href="#type-boolean">Boolean</a>
|
- <a id="type-boolean" href="#type-boolean">Boolean</a>
|
||||||
|
|
||||||
|
@ -235,23 +201,33 @@ will evaluate to `"Xyzzy"` because there is no `c` attribute in the set.
|
||||||
You can use arbitrary double-quoted strings as attribute names:
|
You can use arbitrary double-quoted strings as attribute names:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
{ "foo ${bar}" = 123; "nix-1.0" = 456; }."foo ${bar}"
|
{ "$!@#?" = 123; }."$!@#?"
|
||||||
```
|
```
|
||||||
|
|
||||||
This will evaluate to `123` (Assuming `bar` is antiquotable). In the
|
|
||||||
case where an attribute name is just a single antiquotation, the quotes
|
|
||||||
can be dropped:
|
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
{ foo = 123; }.${bar} or 456
|
let bar = "bar";
|
||||||
|
{ "foo ${bar}" = 123; }."foo ${bar}"
|
||||||
```
|
```
|
||||||
|
|
||||||
This will evaluate to `123` if `bar` evaluates to `"foo"` when coerced
|
Both will evaluate to `123`.
|
||||||
to a string and `456` otherwise (again assuming `bar` is antiquotable).
|
|
||||||
|
Attribute names support [string interpolation]:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
let bar = "foo"; in
|
||||||
|
{ foo = 123; }.${bar}
|
||||||
|
```
|
||||||
|
|
||||||
|
```nix
|
||||||
|
let bar = "foo"; in
|
||||||
|
{ ${bar} = 123; }.foo
|
||||||
|
```
|
||||||
|
|
||||||
|
Both will evaluate to `123`.
|
||||||
|
|
||||||
In the special case where an attribute name inside of a set declaration
|
In the special case where an attribute name inside of a set declaration
|
||||||
evaluates to `null` (which is normally an error, as `null` is not
|
evaluates to `null` (which is normally an error, as `null` cannot be coerced to
|
||||||
antiquotable), that attribute is simply not added to the set:
|
a string), that attribute is simply not added to the set:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
{ ${if foo then "bar" else null} = true; }
|
{ ${if foo then "bar" else null} = true; }
|
||||||
|
|
|
@ -15,13 +15,9 @@
|
||||||
# NIX_PATH=nixpkgs=flake:nixpkgs nix-build '<nixpkgs>' -A hello
|
# NIX_PATH=nixpkgs=flake:nixpkgs nix-build '<nixpkgs>' -A hello
|
||||||
```
|
```
|
||||||
|
|
||||||
* Allow explicitly selecting outputs in a store derivation installable, just like we can do with other sorts of installables.
|
* Instead of "antiquotation", the more common term [string interpolation](../language/string-interpolation.md) is now used consistently.
|
||||||
For example,
|
Historical release notes were not changed.
|
||||||
```shell-session
|
|
||||||
$ nix-build /nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^dev`
|
* Error traces have been reworked to provide detailed explanations and more
|
||||||
```
|
accurate error locations. A short excerpt of the trace is now shown by
|
||||||
now works just as
|
default when an error occurs.
|
||||||
```shell-session
|
|
||||||
$ nix-build glibc^dev`
|
|
||||||
```
|
|
||||||
does already.
|
|
||||||
|
|
11
mk/common-test.sh
Normal file
11
mk/common-test.sh
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
TESTS_ENVIRONMENT=("TEST_NAME=${test%.*}" 'NIX_REMOTE=')
|
||||||
|
|
||||||
|
: ${BASH:=/usr/bin/env bash}
|
||||||
|
|
||||||
|
init_test () {
|
||||||
|
cd tests && env "${TESTS_ENVIRONMENT[@]}" $BASH -e init.sh 2>/dev/null > /dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test_proper () {
|
||||||
|
cd $(dirname $test) && env "${TESTS_ENVIRONMENT[@]}" $BASH -e $(basename $test)
|
||||||
|
}
|
11
mk/debug-test.sh
Executable file
11
mk/debug-test.sh
Executable file
|
@ -0,0 +1,11 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
test=$1
|
||||||
|
|
||||||
|
dir="$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
source "$dir/common-test.sh"
|
||||||
|
|
||||||
|
(init_test)
|
||||||
|
run_test_proper
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
|
@ -7,7 +7,12 @@ green=""
|
||||||
yellow=""
|
yellow=""
|
||||||
normal=""
|
normal=""
|
||||||
|
|
||||||
post_run_msg="ran test $1..."
|
test=$1
|
||||||
|
|
||||||
|
dir="$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
source "$dir/common-test.sh"
|
||||||
|
|
||||||
|
post_run_msg="ran test $test..."
|
||||||
if [ -t 1 ]; then
|
if [ -t 1 ]; then
|
||||||
red="[31;1m"
|
red="[31;1m"
|
||||||
green="[32;1m"
|
green="[32;1m"
|
||||||
|
@ -16,12 +21,12 @@ if [ -t 1 ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
run_test () {
|
run_test () {
|
||||||
(cd tests && env ${TESTS_ENVIRONMENT} init.sh 2>/dev/null > /dev/null)
|
(init_test 2>/dev/null > /dev/null)
|
||||||
log="$(cd $(dirname $1) && env ${TESTS_ENVIRONMENT} $(basename $1) 2>&1)"
|
log="$(run_test_proper 2>&1)"
|
||||||
status=$?
|
status=$?
|
||||||
}
|
}
|
||||||
|
|
||||||
run_test "$1"
|
run_test
|
||||||
|
|
||||||
# Hack: Retry the test if it fails with “unexpected EOF reading a line” as these
|
# Hack: Retry the test if it fails with “unexpected EOF reading a line” as these
|
||||||
# appear randomly without anyone knowing why.
|
# appear randomly without anyone knowing why.
|
||||||
|
@ -32,7 +37,7 @@ if [[ $status -ne 0 && $status -ne 99 && \
|
||||||
]]; then
|
]]; then
|
||||||
echo "$post_run_msg [${yellow}FAIL$normal] (possibly flaky, so will be retried)"
|
echo "$post_run_msg [${yellow}FAIL$normal] (possibly flaky, so will be retried)"
|
||||||
echo "$log" | sed 's/^/ /'
|
echo "$log" | sed 's/^/ /'
|
||||||
run_test "$1"
|
run_test
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ $status -eq 0 ]; then
|
if [ $status -eq 0 ]; then
|
|
@ -8,7 +8,11 @@ define run-install-test
|
||||||
|
|
||||||
.PHONY: $1.test
|
.PHONY: $1.test
|
||||||
$1.test: $1 $(test-deps)
|
$1.test: $1 $(test-deps)
|
||||||
@env TEST_NAME=$(basename $1) TESTS_ENVIRONMENT="$(tests-environment)" mk/run_test.sh $1 < /dev/null
|
@env BASH=$(bash) $(bash) mk/run-test.sh $1 < /dev/null
|
||||||
|
|
||||||
|
.PHONY: $1.test-debug
|
||||||
|
$1.test-debug: $1 $(test-deps)
|
||||||
|
@env BASH=$(bash) $(bash) mk/debug-test.sh $1 < /dev/null
|
||||||
|
|
||||||
endef
|
endef
|
||||||
|
|
||||||
|
|
|
@ -575,7 +575,7 @@ EOF
|
||||||
# to extract _just_ the user's note, instead it is prefixed with
|
# to extract _just_ the user's note, instead it is prefixed with
|
||||||
# some plist junk. This was causing the user note to always be set,
|
# some plist junk. This was causing the user note to always be set,
|
||||||
# even if there was no reason for it.
|
# even if there was no reason for it.
|
||||||
if ! poly_user_note_get "$username" | grep -q "Nix build user $coreid"; then
|
if poly_user_note_get "$username" | grep -q "Nix build user $coreid"; then
|
||||||
row " Note" "Nix build user $coreid"
|
row " Note" "Nix build user $coreid"
|
||||||
else
|
else
|
||||||
poly_user_note_set "$username" "Nix build user $coreid"
|
poly_user_note_set "$username" "Nix build user $coreid"
|
||||||
|
|
|
@ -183,7 +183,7 @@ SourceExprCommand::SourceExprCommand(bool supportReadOnlyMode)
|
||||||
|
|
||||||
addFlag({
|
addFlag({
|
||||||
.longName = "derivation",
|
.longName = "derivation",
|
||||||
.description = "Operate on the store derivation rather than its outputs.",
|
.description = "Operate on the [store derivation](../../glossary.md#gloss-store-derivation) rather than its outputs.",
|
||||||
.category = installablesCategory,
|
.category = installablesCategory,
|
||||||
.handler = {&operateOn, OperateOn::Derivation},
|
.handler = {&operateOn, OperateOn::Derivation},
|
||||||
});
|
});
|
||||||
|
@ -667,7 +667,7 @@ ref<eval_cache::EvalCache> openEvalCache(
|
||||||
auto vFlake = state.allocValue();
|
auto vFlake = state.allocValue();
|
||||||
flake::callFlake(state, *lockedFlake, *vFlake);
|
flake::callFlake(state, *lockedFlake, *vFlake);
|
||||||
|
|
||||||
state.forceAttrs(*vFlake, noPos);
|
state.forceAttrs(*vFlake, noPos, "while parsing cached flake data");
|
||||||
|
|
||||||
auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs"));
|
auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs"));
|
||||||
assert(aOutputs);
|
assert(aOutputs);
|
||||||
|
@ -1128,10 +1128,7 @@ std::vector<std::pair<std::shared_ptr<Installable>, BuiltPathWithResult>> Instal
|
||||||
DrvOutput outputId { *outputHash, output };
|
DrvOutput outputId { *outputHash, output };
|
||||||
auto realisation = store->queryRealisation(outputId);
|
auto realisation = store->queryRealisation(outputId);
|
||||||
if (!realisation)
|
if (!realisation)
|
||||||
throw Error(
|
throw MissingRealisation(outputId);
|
||||||
"cannot operate on an output of the "
|
|
||||||
"unbuilt derivation '%s'",
|
|
||||||
outputId.to_string());
|
|
||||||
outputs.insert_or_assign(output, realisation->outPath);
|
outputs.insert_or_assign(output, realisation->outPath);
|
||||||
} else {
|
} else {
|
||||||
// If ca-derivations isn't enabled, assume that
|
// If ca-derivations isn't enabled, assume that
|
||||||
|
|
|
@ -397,7 +397,7 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
|
||||||
Expr * e = parseString(expr);
|
Expr * e = parseString(expr);
|
||||||
Value v;
|
Value v;
|
||||||
e->eval(*state, *env, v);
|
e->eval(*state, *env, v);
|
||||||
state->forceAttrs(v, noPos);
|
state->forceAttrs(v, noPos, "nevermind, it is ignored anyway");
|
||||||
|
|
||||||
for (auto & i : *v.attrs) {
|
for (auto & i : *v.attrs) {
|
||||||
std::string_view name = state->symbols[i.name];
|
std::string_view name = state->symbols[i.name];
|
||||||
|
@ -590,7 +590,7 @@ bool NixRepl::processLine(std::string line)
|
||||||
const auto [path, line] = [&] () -> std::pair<Path, uint32_t> {
|
const auto [path, line] = [&] () -> std::pair<Path, uint32_t> {
|
||||||
if (v.type() == nPath || v.type() == nString) {
|
if (v.type() == nPath || v.type() == nString) {
|
||||||
PathSet context;
|
PathSet context;
|
||||||
auto path = state->coerceToPath(noPos, v, context);
|
auto path = state->coerceToPath(noPos, v, context, "while evaluating the filename to edit");
|
||||||
return {path, 0};
|
return {path, 0};
|
||||||
} else if (v.isLambda()) {
|
} else if (v.isLambda()) {
|
||||||
auto pos = state->positions[v.lambda.fun->pos];
|
auto pos = state->positions[v.lambda.fun->pos];
|
||||||
|
@ -834,7 +834,7 @@ void NixRepl::loadFiles()
|
||||||
|
|
||||||
void NixRepl::addAttrsToScope(Value & attrs)
|
void NixRepl::addAttrsToScope(Value & attrs)
|
||||||
{
|
{
|
||||||
state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); });
|
state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); }, "while evaluating an attribute set to be merged in the global scope");
|
||||||
if (displ + attrs.attrs->size() >= envSize)
|
if (displ + attrs.attrs->size() >= envSize)
|
||||||
throw Error("environment full; cannot add more variables");
|
throw Error("environment full; cannot add more variables");
|
||||||
|
|
||||||
|
@ -939,7 +939,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
|
||||||
Bindings::iterator i = v.attrs->find(state->sDrvPath);
|
Bindings::iterator i = v.attrs->find(state->sDrvPath);
|
||||||
PathSet context;
|
PathSet context;
|
||||||
if (i != v.attrs->end())
|
if (i != v.attrs->end())
|
||||||
str << state->store->printStorePath(state->coerceToStorePath(i->pos, *i->value, context));
|
str << state->store->printStorePath(state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the drvPath of a derivation"));
|
||||||
else
|
else
|
||||||
str << "???";
|
str << "???";
|
||||||
str << "»";
|
str << "»";
|
||||||
|
|
|
@ -118,7 +118,7 @@ std::pair<std::string, uint32_t> findPackageFilename(EvalState & state, Value &
|
||||||
|
|
||||||
// FIXME: is it possible to extract the Pos object instead of doing this
|
// FIXME: is it possible to extract the Pos object instead of doing this
|
||||||
// toString + parsing?
|
// toString + parsing?
|
||||||
auto pos = state.forceString(*v2);
|
auto pos = state.forceString(*v2, noPos, "while evaluating the 'meta.position' attribute of a derivation");
|
||||||
|
|
||||||
auto colon = pos.rfind(':');
|
auto colon = pos.rfind(':');
|
||||||
if (colon == std::string::npos)
|
if (colon == std::string::npos)
|
||||||
|
|
|
@ -385,7 +385,7 @@ Value & AttrCursor::getValue()
|
||||||
if (!_value) {
|
if (!_value) {
|
||||||
if (parent) {
|
if (parent) {
|
||||||
auto & vParent = parent->first->getValue();
|
auto & vParent = parent->first->getValue();
|
||||||
root->state.forceAttrs(vParent, noPos);
|
root->state.forceAttrs(vParent, noPos, "while searching for an attribute");
|
||||||
auto attr = vParent.attrs->get(parent->second);
|
auto attr = vParent.attrs->get(parent->second);
|
||||||
if (!attr)
|
if (!attr)
|
||||||
throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr());
|
throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr());
|
||||||
|
@ -571,14 +571,14 @@ std::string AttrCursor::getString()
|
||||||
debug("using cached string attribute '%s'", getAttrPathStr());
|
debug("using cached string attribute '%s'", getAttrPathStr());
|
||||||
return s->first;
|
return s->first;
|
||||||
} else
|
} else
|
||||||
root->state.debugThrowLastTrace(TypeError("'%s' is not a string", getAttrPathStr()));
|
root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow<TypeError>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto & v = forceValue();
|
auto & v = forceValue();
|
||||||
|
|
||||||
if (v.type() != nString && v.type() != nPath)
|
if (v.type() != nString && v.type() != nPath)
|
||||||
root->state.debugThrowLastTrace(TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type())));
|
root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
|
||||||
|
|
||||||
return v.type() == nString ? v.string.s : v.path;
|
return v.type() == nString ? v.string.s : v.path;
|
||||||
}
|
}
|
||||||
|
@ -602,7 +602,7 @@ string_t AttrCursor::getStringWithContext()
|
||||||
return *s;
|
return *s;
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
root->state.debugThrowLastTrace(TypeError("'%s' is not a string", getAttrPathStr()));
|
root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow<TypeError>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -613,7 +613,7 @@ string_t AttrCursor::getStringWithContext()
|
||||||
else if (v.type() == nPath)
|
else if (v.type() == nPath)
|
||||||
return {v.path, {}};
|
return {v.path, {}};
|
||||||
else
|
else
|
||||||
root->state.debugThrowLastTrace(TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type())));
|
root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AttrCursor::getBool()
|
bool AttrCursor::getBool()
|
||||||
|
@ -626,14 +626,14 @@ bool AttrCursor::getBool()
|
||||||
debug("using cached Boolean attribute '%s'", getAttrPathStr());
|
debug("using cached Boolean attribute '%s'", getAttrPathStr());
|
||||||
return *b;
|
return *b;
|
||||||
} else
|
} else
|
||||||
root->state.debugThrowLastTrace(TypeError("'%s' is not a Boolean", getAttrPathStr()));
|
root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow<TypeError>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto & v = forceValue();
|
auto & v = forceValue();
|
||||||
|
|
||||||
if (v.type() != nBool)
|
if (v.type() != nBool)
|
||||||
root->state.debugThrowLastTrace(TypeError("'%s' is not a Boolean", getAttrPathStr()));
|
root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow<TypeError>();
|
||||||
|
|
||||||
return v.boolean;
|
return v.boolean;
|
||||||
}
|
}
|
||||||
|
@ -685,7 +685,7 @@ std::vector<std::string> AttrCursor::getListOfStrings()
|
||||||
std::vector<std::string> res;
|
std::vector<std::string> res;
|
||||||
|
|
||||||
for (auto & elem : v.listItems())
|
for (auto & elem : v.listItems())
|
||||||
res.push_back(std::string(root->state.forceStringNoCtx(*elem)));
|
res.push_back(std::string(root->state.forceStringNoCtx(*elem, noPos, "while evaluating an attribute for caching")));
|
||||||
|
|
||||||
if (root->db)
|
if (root->db)
|
||||||
cachedValue = {root->db->setListOfStrings(getKey(), res), res};
|
cachedValue = {root->db->setListOfStrings(getKey(), res), res};
|
||||||
|
@ -703,14 +703,14 @@ std::vector<Symbol> AttrCursor::getAttrs()
|
||||||
debug("using cached attrset attribute '%s'", getAttrPathStr());
|
debug("using cached attrset attribute '%s'", getAttrPathStr());
|
||||||
return *attrs;
|
return *attrs;
|
||||||
} else
|
} else
|
||||||
root->state.debugThrowLastTrace(TypeError("'%s' is not an attribute set", getAttrPathStr()));
|
root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow<TypeError>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto & v = forceValue();
|
auto & v = forceValue();
|
||||||
|
|
||||||
if (v.type() != nAttrs)
|
if (v.type() != nAttrs)
|
||||||
root->state.debugThrowLastTrace(TypeError("'%s' is not an attribute set", getAttrPathStr()));
|
root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow<TypeError>();
|
||||||
|
|
||||||
std::vector<Symbol> attrs;
|
std::vector<Symbol> attrs;
|
||||||
for (auto & attr : *getValue().attrs)
|
for (auto & attr : *getValue().attrs)
|
||||||
|
|
|
@ -103,33 +103,36 @@ void EvalState::forceValue(Value & v, Callable getPos)
|
||||||
else if (v.isApp())
|
else if (v.isApp())
|
||||||
callFunction(*v.app.left, *v.app.right, v, noPos);
|
callFunction(*v.app.left, *v.app.right, v, noPos);
|
||||||
else if (v.isBlackhole())
|
else if (v.isBlackhole())
|
||||||
throwEvalError(getPos(), "infinite recursion encountered");
|
error("infinite recursion encountered").atPos(getPos()).template debugThrow<EvalError>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[[gnu::always_inline]]
|
[[gnu::always_inline]]
|
||||||
inline void EvalState::forceAttrs(Value & v, const PosIdx pos)
|
inline void EvalState::forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx)
|
||||||
{
|
{
|
||||||
forceAttrs(v, [&]() { return pos; });
|
forceAttrs(v, [&]() { return pos; }, errorCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
template <typename Callable>
|
template <typename Callable>
|
||||||
[[gnu::always_inline]]
|
[[gnu::always_inline]]
|
||||||
inline void EvalState::forceAttrs(Value & v, Callable getPos)
|
inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view errorCtx)
|
||||||
{
|
{
|
||||||
forceValue(v, getPos);
|
forceValue(v, noPos);
|
||||||
if (v.type() != nAttrs)
|
if (v.type() != nAttrs) {
|
||||||
throwTypeError(getPos(), "value is %1% while a set was expected", v);
|
PosIdx pos = getPos();
|
||||||
|
error("value is %1% while a set was expected", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[[gnu::always_inline]]
|
[[gnu::always_inline]]
|
||||||
inline void EvalState::forceList(Value & v, const PosIdx pos)
|
inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view errorCtx)
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
forceValue(v, noPos);
|
||||||
if (!v.isList())
|
if (!v.isList()) {
|
||||||
throwTypeError(pos, "value is %1% while a list was expected", v);
|
error("value is %1% while a list was expected", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -67,22 +67,19 @@ static char * dupString(const char * s)
|
||||||
|
|
||||||
// When there's no need to write to the string, we can optimize away empty
|
// When there's no need to write to the string, we can optimize away empty
|
||||||
// string allocations.
|
// string allocations.
|
||||||
// This function handles makeImmutableStringWithLen(null, 0) by returning the
|
// This function handles makeImmutableString(std::string_view()) by returning
|
||||||
// empty string.
|
// the empty string.
|
||||||
static const char * makeImmutableStringWithLen(const char * s, size_t size)
|
static const char * makeImmutableString(std::string_view s)
|
||||||
{
|
{
|
||||||
|
const size_t size = s.size();
|
||||||
if (size == 0)
|
if (size == 0)
|
||||||
return "";
|
return "";
|
||||||
auto t = allocString(size + 1);
|
auto t = allocString(size + 1);
|
||||||
memcpy(t, s, size);
|
memcpy(t, s.data(), size);
|
||||||
t[size] = 0;
|
t[size] = '\0';
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline const char * makeImmutableString(std::string_view s) {
|
|
||||||
return makeImmutableStringWithLen(s.data(), s.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
RootValue allocRootValue(Value * v)
|
RootValue allocRootValue(Value * v)
|
||||||
{
|
{
|
||||||
|
@ -321,7 +318,7 @@ static Symbol getName(const AttrName & name, EvalState & state, Env & env)
|
||||||
} else {
|
} else {
|
||||||
Value nameValue;
|
Value nameValue;
|
||||||
name.expr->eval(state, env, nameValue);
|
name.expr->eval(state, env, nameValue);
|
||||||
state.forceStringNoCtx(nameValue);
|
state.forceStringNoCtx(nameValue, noPos, "while evaluating an attribute name");
|
||||||
return state.symbols.create(nameValue.string.s);
|
return state.symbols.create(nameValue.string.s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -417,6 +414,44 @@ static Strings parseNixPath(const std::string & s)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ErrorBuilder & ErrorBuilder::atPos(PosIdx pos)
|
||||||
|
{
|
||||||
|
info.errPos = state.positions[pos];
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorBuilder & ErrorBuilder::withTrace(PosIdx pos, const std::string_view text)
|
||||||
|
{
|
||||||
|
info.traces.push_front(Trace{ .pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = false });
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorBuilder & ErrorBuilder::withFrameTrace(PosIdx pos, const std::string_view text)
|
||||||
|
{
|
||||||
|
info.traces.push_front(Trace{ .pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = true });
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorBuilder & ErrorBuilder::withSuggestions(Suggestions & s)
|
||||||
|
{
|
||||||
|
info.suggestions = s;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorBuilder & ErrorBuilder::withFrame(const Env & env, const Expr & expr)
|
||||||
|
{
|
||||||
|
// NOTE: This is abusing side-effects.
|
||||||
|
// TODO: check compatibility with nested debugger calls.
|
||||||
|
state.debugTraces.push_front(DebugTrace {
|
||||||
|
.pos = nullptr,
|
||||||
|
.expr = expr,
|
||||||
|
.env = env,
|
||||||
|
.hint = hintformat("Fake frame for debugging purposes"),
|
||||||
|
.isError = true
|
||||||
|
});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
EvalState::EvalState(
|
EvalState::EvalState(
|
||||||
const Strings & _searchPath,
|
const Strings & _searchPath,
|
||||||
|
@ -649,25 +684,7 @@ void EvalState::addConstant(const std::string & name, Value * v)
|
||||||
Value * EvalState::addPrimOp(const std::string & name,
|
Value * EvalState::addPrimOp(const std::string & name,
|
||||||
size_t arity, PrimOpFun primOp)
|
size_t arity, PrimOpFun primOp)
|
||||||
{
|
{
|
||||||
auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name;
|
return addPrimOp(PrimOp { .fun = primOp, .arity = arity, .name = name });
|
||||||
auto sym = symbols.create(name2);
|
|
||||||
|
|
||||||
/* Hack to make constants lazy: turn them into a application of
|
|
||||||
the primop to a dummy value. */
|
|
||||||
if (arity == 0) {
|
|
||||||
auto vPrimOp = allocValue();
|
|
||||||
vPrimOp->mkPrimOp(new PrimOp { .fun = primOp, .arity = 1, .name = name2 });
|
|
||||||
Value v;
|
|
||||||
v.mkApp(vPrimOp, vPrimOp);
|
|
||||||
return addConstant(name, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value * v = allocValue();
|
|
||||||
v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = name2 });
|
|
||||||
staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
|
|
||||||
baseEnv.values[baseEnvDispl++] = v;
|
|
||||||
baseEnv.values[0]->attrs->push_back(Attr(sym, v));
|
|
||||||
return v;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -845,176 +862,14 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr &
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Every "format" object (even temporary) takes up a few hundred bytes
|
|
||||||
of stack space, which is a real killer in the recursive
|
|
||||||
evaluator. So here are some helper functions for throwing
|
|
||||||
exceptions. */
|
|
||||||
void EvalState::throwEvalError(const PosIdx pos, const char * s, Env & env, Expr & expr)
|
|
||||||
{
|
|
||||||
debugThrow(EvalError({
|
|
||||||
.msg = hintfmt(s),
|
|
||||||
.errPos = positions[pos]
|
|
||||||
}), env, expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::throwEvalError(const PosIdx pos, const char * s)
|
|
||||||
{
|
|
||||||
debugThrowLastTrace(EvalError({
|
|
||||||
.msg = hintfmt(s),
|
|
||||||
.errPos = positions[pos]
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::throwEvalError(const char * s, const std::string & s2)
|
|
||||||
{
|
|
||||||
debugThrowLastTrace(EvalError(s, s2));
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s,
|
|
||||||
const std::string & s2, Env & env, Expr & expr)
|
|
||||||
{
|
|
||||||
debugThrow(EvalError(ErrorInfo{
|
|
||||||
.msg = hintfmt(s, s2),
|
|
||||||
.errPos = positions[pos],
|
|
||||||
.suggestions = suggestions,
|
|
||||||
}), env, expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2)
|
|
||||||
{
|
|
||||||
debugThrowLastTrace(EvalError({
|
|
||||||
.msg = hintfmt(s, s2),
|
|
||||||
.errPos = positions[pos]
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2, Env & env, Expr & expr)
|
|
||||||
{
|
|
||||||
debugThrow(EvalError({
|
|
||||||
.msg = hintfmt(s, s2),
|
|
||||||
.errPos = positions[pos]
|
|
||||||
}), env, expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::throwEvalError(const char * s, const std::string & s2,
|
|
||||||
const std::string & s3)
|
|
||||||
{
|
|
||||||
debugThrowLastTrace(EvalError({
|
|
||||||
.msg = hintfmt(s, s2, s3),
|
|
||||||
.errPos = positions[noPos]
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2,
|
|
||||||
const std::string & s3)
|
|
||||||
{
|
|
||||||
debugThrowLastTrace(EvalError({
|
|
||||||
.msg = hintfmt(s, s2, s3),
|
|
||||||
.errPos = positions[pos]
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2,
|
|
||||||
const std::string & s3, Env & env, Expr & expr)
|
|
||||||
{
|
|
||||||
debugThrow(EvalError({
|
|
||||||
.msg = hintfmt(s, s2, s3),
|
|
||||||
.errPos = positions[pos]
|
|
||||||
}), env, expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2, Env & env, Expr & expr)
|
|
||||||
{
|
|
||||||
// p1 is where the error occurred; p2 is a position mentioned in the message.
|
|
||||||
debugThrow(EvalError({
|
|
||||||
.msg = hintfmt(s, symbols[sym], positions[p2]),
|
|
||||||
.errPos = positions[p1]
|
|
||||||
}), env, expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::throwTypeError(const PosIdx pos, const char * s, const Value & v)
|
|
||||||
{
|
|
||||||
debugThrowLastTrace(TypeError({
|
|
||||||
.msg = hintfmt(s, showType(v)),
|
|
||||||
.errPos = positions[pos]
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::throwTypeError(const PosIdx pos, const char * s, const Value & v, Env & env, Expr & expr)
|
|
||||||
{
|
|
||||||
debugThrow(TypeError({
|
|
||||||
.msg = hintfmt(s, showType(v)),
|
|
||||||
.errPos = positions[pos]
|
|
||||||
}), env, expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::throwTypeError(const PosIdx pos, const char * s)
|
|
||||||
{
|
|
||||||
debugThrowLastTrace(TypeError({
|
|
||||||
.msg = hintfmt(s),
|
|
||||||
.errPos = positions[pos]
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun,
|
|
||||||
const Symbol s2, Env & env, Expr &expr)
|
|
||||||
{
|
|
||||||
debugThrow(TypeError({
|
|
||||||
.msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]),
|
|
||||||
.errPos = positions[pos]
|
|
||||||
}), env, expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s,
|
|
||||||
const ExprLambda & fun, const Symbol s2, Env & env, Expr &expr)
|
|
||||||
{
|
|
||||||
debugThrow(TypeError(ErrorInfo {
|
|
||||||
.msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]),
|
|
||||||
.errPos = positions[pos],
|
|
||||||
.suggestions = suggestions,
|
|
||||||
}), env, expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::throwTypeError(const char * s, const Value & v, Env & env, Expr &expr)
|
|
||||||
{
|
|
||||||
debugThrow(TypeError({
|
|
||||||
.msg = hintfmt(s, showType(v)),
|
|
||||||
.errPos = positions[expr.getPos()],
|
|
||||||
}), env, expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::throwAssertionError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr)
|
|
||||||
{
|
|
||||||
debugThrow(AssertionError({
|
|
||||||
.msg = hintfmt(s, s1),
|
|
||||||
.errPos = positions[pos]
|
|
||||||
}), env, expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr)
|
|
||||||
{
|
|
||||||
debugThrow(UndefinedVarError({
|
|
||||||
.msg = hintfmt(s, s1),
|
|
||||||
.errPos = positions[pos]
|
|
||||||
}), env, expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr)
|
|
||||||
{
|
|
||||||
debugThrow(MissingArgumentError({
|
|
||||||
.msg = hintfmt(s, s1),
|
|
||||||
.errPos = positions[pos]
|
|
||||||
}), env, expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) const
|
void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) const
|
||||||
{
|
{
|
||||||
e.addTrace(nullptr, s, s2);
|
e.addTrace(nullptr, s, s2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const
|
void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2, bool frame) const
|
||||||
{
|
{
|
||||||
e.addTrace(positions[pos], s, s2);
|
e.addTrace(positions[pos], hintfmt(s, s2), frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::unique_ptr<DebugTraceStacker> makeDebugTraceStacker(
|
static std::unique_ptr<DebugTraceStacker> makeDebugTraceStacker(
|
||||||
|
@ -1091,7 +946,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
|
||||||
if (env->type == Env::HasWithExpr) {
|
if (env->type == Env::HasWithExpr) {
|
||||||
if (noEval) return 0;
|
if (noEval) return 0;
|
||||||
Value * v = allocValue();
|
Value * v = allocValue();
|
||||||
evalAttrs(*env->up, (Expr *) env->values[0], *v);
|
evalAttrs(*env->up, (Expr *) env->values[0], *v, noPos, "<borked>");
|
||||||
env->values[0] = v;
|
env->values[0] = v;
|
||||||
env->type = Env::HasWithAttrs;
|
env->type = Env::HasWithAttrs;
|
||||||
}
|
}
|
||||||
|
@ -1101,7 +956,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
|
||||||
return j->value;
|
return j->value;
|
||||||
}
|
}
|
||||||
if (!env->prevWith)
|
if (!env->prevWith)
|
||||||
throwUndefinedVarError(var.pos, "undefined variable '%1%'", symbols[var.name], *env, const_cast<ExprVar&>(var));
|
error("undefined variable '%1%'", symbols[var.name]).atPos(var.pos).withFrame(*env, var).debugThrow<UndefinedVarError>();
|
||||||
for (size_t l = env->prevWith; l; --l, env = env->up) ;
|
for (size_t l = env->prevWith; l; --l, env = env->up) ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1251,7 +1106,7 @@ void EvalState::cacheFile(
|
||||||
// computation.
|
// computation.
|
||||||
if (mustBeTrivial &&
|
if (mustBeTrivial &&
|
||||||
!(dynamic_cast<ExprAttrs *>(e)))
|
!(dynamic_cast<ExprAttrs *>(e)))
|
||||||
throw EvalError("file '%s' must be an attribute set", path);
|
error("file '%s' must be an attribute set", path).debugThrow<EvalError>();
|
||||||
eval(e, v);
|
eval(e, v);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath);
|
addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath);
|
||||||
|
@ -1269,31 +1124,31 @@ void EvalState::eval(Expr * e, Value & v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
inline bool EvalState::evalBool(Env & env, Expr * e)
|
inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos, std::string_view errorCtx)
|
||||||
{
|
{
|
||||||
Value v;
|
try {
|
||||||
e->eval(*this, env, v);
|
Value v;
|
||||||
if (v.type() != nBool)
|
e->eval(*this, env, v);
|
||||||
throwTypeError(noPos, "value is %1% while a Boolean was expected", v, env, *e);
|
if (v.type() != nBool)
|
||||||
return v.boolean;
|
error("value is %1% while a Boolean was expected", showType(v)).withFrame(env, *e).debugThrow<TypeError>();
|
||||||
|
return v.boolean;
|
||||||
|
} catch (Error & e) {
|
||||||
|
e.addTrace(positions[pos], errorCtx);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos)
|
inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const PosIdx pos, std::string_view errorCtx)
|
||||||
{
|
{
|
||||||
Value v;
|
try {
|
||||||
e->eval(*this, env, v);
|
e->eval(*this, env, v);
|
||||||
if (v.type() != nBool)
|
if (v.type() != nAttrs)
|
||||||
throwTypeError(pos, "value is %1% while a Boolean was expected", v, env, *e);
|
error("value is %1% while a set was expected", showType(v)).withFrame(env, *e).debugThrow<TypeError>();
|
||||||
return v.boolean;
|
} catch (Error & e) {
|
||||||
}
|
e.addTrace(positions[pos], errorCtx);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v)
|
|
||||||
{
|
|
||||||
e->eval(*this, env, v);
|
|
||||||
if (v.type() != nAttrs)
|
|
||||||
throwTypeError(noPos, "value is %1% while a set was expected", v, env, *e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1366,7 +1221,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
|
||||||
Hence we need __overrides.) */
|
Hence we need __overrides.) */
|
||||||
if (hasOverrides) {
|
if (hasOverrides) {
|
||||||
Value * vOverrides = (*v.attrs)[overrides->second.displ].value;
|
Value * vOverrides = (*v.attrs)[overrides->second.displ].value;
|
||||||
state.forceAttrs(*vOverrides, [&]() { return vOverrides->determinePos(noPos); });
|
state.forceAttrs(*vOverrides, [&]() { return vOverrides->determinePos(noPos); }, "while evaluating the `__overrides` attribute");
|
||||||
Bindings * newBnds = state.allocBindings(v.attrs->capacity() + vOverrides->attrs->size());
|
Bindings * newBnds = state.allocBindings(v.attrs->capacity() + vOverrides->attrs->size());
|
||||||
for (auto & i : *v.attrs)
|
for (auto & i : *v.attrs)
|
||||||
newBnds->push_back(i);
|
newBnds->push_back(i);
|
||||||
|
@ -1394,11 +1249,11 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
|
||||||
state.forceValue(nameVal, i.pos);
|
state.forceValue(nameVal, i.pos);
|
||||||
if (nameVal.type() == nNull)
|
if (nameVal.type() == nNull)
|
||||||
continue;
|
continue;
|
||||||
state.forceStringNoCtx(nameVal);
|
state.forceStringNoCtx(nameVal, i.pos, "while evaluating the name of a dynamic attribute");
|
||||||
auto nameSym = state.symbols.create(nameVal.string.s);
|
auto nameSym = state.symbols.create(nameVal.string.s);
|
||||||
Bindings::iterator j = v.attrs->find(nameSym);
|
Bindings::iterator j = v.attrs->find(nameSym);
|
||||||
if (j != v.attrs->end())
|
if (j != v.attrs->end())
|
||||||
state.throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, j->pos, env, *this);
|
state.error("dynamic attribute '%1%' already defined at %2%", state.symbols[nameSym], state.positions[j->pos]).atPos(i.pos).withFrame(env, *this).debugThrow<EvalError>();
|
||||||
|
|
||||||
i.valueExpr->setName(nameSym);
|
i.valueExpr->setName(nameSym);
|
||||||
/* Keep sorted order so find can catch duplicates */
|
/* Keep sorted order so find can catch duplicates */
|
||||||
|
@ -1495,15 +1350,14 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
state.forceAttrs(*vAttrs, pos);
|
state.forceAttrs(*vAttrs, pos, "while selecting an attribute");
|
||||||
if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) {
|
if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) {
|
||||||
std::set<std::string> allAttrNames;
|
std::set<std::string> allAttrNames;
|
||||||
for (auto & attr : *vAttrs->attrs)
|
for (auto & attr : *vAttrs->attrs)
|
||||||
allAttrNames.insert(state.symbols[attr.name]);
|
allAttrNames.insert(state.symbols[attr.name]);
|
||||||
state.throwEvalError(
|
auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]);
|
||||||
pos,
|
state.error("attribute '%1%' missing", state.symbols[name])
|
||||||
Suggestions::bestMatches(allAttrNames, state.symbols[name]),
|
.atPos(pos).withSuggestions(suggestions).withFrame(env, *this).debugThrow<EvalError>();
|
||||||
"attribute '%1%' missing", state.symbols[name], env, *this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vAttrs = j->value;
|
vAttrs = j->value;
|
||||||
|
@ -1598,7 +1452,12 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
if (!lambda.hasFormals())
|
if (!lambda.hasFormals())
|
||||||
env2.values[displ++] = args[0];
|
env2.values[displ++] = args[0];
|
||||||
else {
|
else {
|
||||||
forceAttrs(*args[0], pos);
|
try {
|
||||||
|
forceAttrs(*args[0], lambda.pos, "while evaluating the value passed for the lambda argument");
|
||||||
|
} catch (Error & e) {
|
||||||
|
if (pos) e.addTrace(positions[pos], "from call site");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
if (lambda.arg)
|
if (lambda.arg)
|
||||||
env2.values[displ++] = args[0];
|
env2.values[displ++] = args[0];
|
||||||
|
@ -1610,8 +1469,15 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
for (auto & i : lambda.formals->formals) {
|
for (auto & i : lambda.formals->formals) {
|
||||||
auto j = args[0]->attrs->get(i.name);
|
auto j = args[0]->attrs->get(i.name);
|
||||||
if (!j) {
|
if (!j) {
|
||||||
if (!i.def) throwTypeError(pos, "%1% called without required argument '%2%'",
|
if (!i.def) {
|
||||||
lambda, i.name, *fun.lambda.env, lambda);
|
error("function '%1%' called without required argument '%2%'",
|
||||||
|
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
|
||||||
|
symbols[i.name])
|
||||||
|
.atPos(lambda.pos)
|
||||||
|
.withTrace(pos, "from call site")
|
||||||
|
.withFrame(*fun.lambda.env, lambda)
|
||||||
|
.debugThrow<TypeError>();
|
||||||
|
}
|
||||||
env2.values[displ++] = i.def->maybeThunk(*this, env2);
|
env2.values[displ++] = i.def->maybeThunk(*this, env2);
|
||||||
} else {
|
} else {
|
||||||
attrsUsed++;
|
attrsUsed++;
|
||||||
|
@ -1629,11 +1495,15 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
std::set<std::string> formalNames;
|
std::set<std::string> formalNames;
|
||||||
for (auto & formal : lambda.formals->formals)
|
for (auto & formal : lambda.formals->formals)
|
||||||
formalNames.insert(symbols[formal.name]);
|
formalNames.insert(symbols[formal.name]);
|
||||||
throwTypeError(
|
auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]);
|
||||||
pos,
|
error("function '%1%' called with unexpected argument '%2%'",
|
||||||
Suggestions::bestMatches(formalNames, symbols[i.name]),
|
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
|
||||||
"%1% called with unexpected argument '%2%'",
|
symbols[i.name])
|
||||||
lambda, i.name, *fun.lambda.env, lambda);
|
.atPos(lambda.pos)
|
||||||
|
.withTrace(pos, "from call site")
|
||||||
|
.withSuggestions(suggestions)
|
||||||
|
.withFrame(*fun.lambda.env, lambda)
|
||||||
|
.debugThrow<TypeError>();
|
||||||
}
|
}
|
||||||
abort(); // can't happen
|
abort(); // can't happen
|
||||||
}
|
}
|
||||||
|
@ -1656,11 +1526,15 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
lambda.body->eval(*this, env2, vCur);
|
lambda.body->eval(*this, env2, vCur);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
if (loggerSettings.showTrace.get()) {
|
if (loggerSettings.showTrace.get()) {
|
||||||
addErrorTrace(e, lambda.pos, "while calling %s",
|
addErrorTrace(
|
||||||
(lambda.name
|
e,
|
||||||
? concatStrings("'", symbols[lambda.name], "'")
|
lambda.pos,
|
||||||
: "anonymous lambda"));
|
"while calling %s",
|
||||||
addErrorTrace(e, pos, "while evaluating call site%s", "");
|
lambda.name
|
||||||
|
? concatStrings("'", symbols[lambda.name], "'")
|
||||||
|
: "anonymous lambda",
|
||||||
|
true);
|
||||||
|
if (pos) addErrorTrace(e, pos, "from call site%s", "", true);
|
||||||
}
|
}
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
@ -1679,9 +1553,17 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
/* We have all the arguments, so call the primop. */
|
/* We have all the arguments, so call the primop. */
|
||||||
|
auto name = vCur.primOp->name;
|
||||||
|
|
||||||
nrPrimOpCalls++;
|
nrPrimOpCalls++;
|
||||||
if (countCalls) primOpCalls[vCur.primOp->name]++;
|
if (countCalls) primOpCalls[name]++;
|
||||||
vCur.primOp->fun(*this, pos, args, vCur);
|
|
||||||
|
try {
|
||||||
|
vCur.primOp->fun(*this, noPos, args, vCur);
|
||||||
|
} catch (Error & e) {
|
||||||
|
addErrorTrace(e, pos, "while calling the '%1%' builtin", name);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
nrArgs -= argsLeft;
|
nrArgs -= argsLeft;
|
||||||
args += argsLeft;
|
args += argsLeft;
|
||||||
|
@ -1716,9 +1598,20 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
for (size_t i = 0; i < argsLeft; ++i)
|
for (size_t i = 0; i < argsLeft; ++i)
|
||||||
vArgs[argsDone + i] = args[i];
|
vArgs[argsDone + i] = args[i];
|
||||||
|
|
||||||
|
auto name = primOp->primOp->name;
|
||||||
nrPrimOpCalls++;
|
nrPrimOpCalls++;
|
||||||
if (countCalls) primOpCalls[primOp->primOp->name]++;
|
if (countCalls) primOpCalls[name]++;
|
||||||
primOp->primOp->fun(*this, pos, vArgs, vCur);
|
|
||||||
|
try {
|
||||||
|
// TODO:
|
||||||
|
// 1. Unify this and above code. Heavily redundant.
|
||||||
|
// 2. Create a fake env (arg1, arg2, etc.) and a fake expr (arg1: arg2: etc: builtins.name arg1 arg2 etc)
|
||||||
|
// so the debugger allows to inspect the wrong parameters passed to the builtin.
|
||||||
|
primOp->primOp->fun(*this, noPos, vArgs, vCur);
|
||||||
|
} catch (Error & e) {
|
||||||
|
addErrorTrace(e, pos, "while calling the '%1%' builtin", name);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
nrArgs -= argsLeft;
|
nrArgs -= argsLeft;
|
||||||
args += argsLeft;
|
args += argsLeft;
|
||||||
|
@ -1731,14 +1624,18 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
heap-allocate a copy and use that instead. */
|
heap-allocate a copy and use that instead. */
|
||||||
Value * args2[] = {allocValue(), args[0]};
|
Value * args2[] = {allocValue(), args[0]};
|
||||||
*args2[0] = vCur;
|
*args2[0] = vCur;
|
||||||
/* !!! Should we use the attr pos here? */
|
try {
|
||||||
callFunction(*functor->value, 2, args2, vCur, pos);
|
callFunction(*functor->value, 2, args2, vCur, functor->pos);
|
||||||
|
} catch (Error & e) {
|
||||||
|
e.addTrace(positions[pos], "while calling a functor (an attribute set with a '__functor' attribute)");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
nrArgs--;
|
nrArgs--;
|
||||||
args++;
|
args++;
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
throwTypeError(pos, "attempt to call something which is not a function but %1%", vCur);
|
error("attempt to call something which is not a function but %1%", showType(vCur)).atPos(pos).debugThrow<TypeError>();
|
||||||
}
|
}
|
||||||
|
|
||||||
vRes = vCur;
|
vRes = vCur;
|
||||||
|
@ -1802,13 +1699,12 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
|
||||||
if (j != args.end()) {
|
if (j != args.end()) {
|
||||||
attrs.insert(*j);
|
attrs.insert(*j);
|
||||||
} else if (!i.def) {
|
} else if (!i.def) {
|
||||||
throwMissingArgumentError(i.pos, R"(cannot evaluate a function that has an argument without a value ('%1%')
|
error(R"(cannot evaluate a function that has an argument without a value ('%1%')
|
||||||
|
|
||||||
Nix attempted to evaluate a function as a top level expression; in
|
Nix attempted to evaluate a function as a top level expression; in
|
||||||
this case it must have its arguments supplied either by default
|
this case it must have its arguments supplied either by default
|
||||||
values, or passed explicitly with '--arg' or '--argstr'. See
|
values, or passed explicitly with '--arg' or '--argstr'. See
|
||||||
https://nixos.org/manual/nix/stable/language/constructs.html#functions.)", symbols[i.name],
|
https://nixos.org/manual/nix/stable/language/constructs.html#functions.)", symbols[i.name])
|
||||||
*fun.lambda.env, *fun.lambda.fun);
|
.atPos(i.pos).withFrame(*fun.lambda.env, *fun.lambda.fun).debugThrow<MissingArgumentError>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1831,16 +1727,17 @@ void ExprWith::eval(EvalState & state, Env & env, Value & v)
|
||||||
|
|
||||||
void ExprIf::eval(EvalState & state, Env & env, Value & v)
|
void ExprIf::eval(EvalState & state, Env & env, Value & v)
|
||||||
{
|
{
|
||||||
(state.evalBool(env, cond, pos) ? then : else_)->eval(state, env, v);
|
// We cheat in the parser, and pass the position of the condition as the position of the if itself.
|
||||||
|
(state.evalBool(env, cond, pos, "while evaluating a branch condition") ? then : else_)->eval(state, env, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void ExprAssert::eval(EvalState & state, Env & env, Value & v)
|
void ExprAssert::eval(EvalState & state, Env & env, Value & v)
|
||||||
{
|
{
|
||||||
if (!state.evalBool(env, cond, pos)) {
|
if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) {
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
cond->show(state.symbols, out);
|
cond->show(state.symbols, out);
|
||||||
state.throwAssertionError(pos, "assertion '%1%' failed", out.str(), env, *this);
|
state.error("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow<AssertionError>();
|
||||||
}
|
}
|
||||||
body->eval(state, env, v);
|
body->eval(state, env, v);
|
||||||
}
|
}
|
||||||
|
@ -1848,7 +1745,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v)
|
||||||
|
|
||||||
void ExprOpNot::eval(EvalState & state, Env & env, Value & v)
|
void ExprOpNot::eval(EvalState & state, Env & env, Value & v)
|
||||||
{
|
{
|
||||||
v.mkBool(!state.evalBool(env, e));
|
v.mkBool(!state.evalBool(env, e, noPos, "in the argument of the not operator")); // XXX: FIXME: !
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1856,7 +1753,7 @@ void ExprOpEq::eval(EvalState & state, Env & env, Value & v)
|
||||||
{
|
{
|
||||||
Value v1; e1->eval(state, env, v1);
|
Value v1; e1->eval(state, env, v1);
|
||||||
Value v2; e2->eval(state, env, v2);
|
Value v2; e2->eval(state, env, v2);
|
||||||
v.mkBool(state.eqValues(v1, v2));
|
v.mkBool(state.eqValues(v1, v2, pos, "while testing two values for equality"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1864,33 +1761,33 @@ void ExprOpNEq::eval(EvalState & state, Env & env, Value & v)
|
||||||
{
|
{
|
||||||
Value v1; e1->eval(state, env, v1);
|
Value v1; e1->eval(state, env, v1);
|
||||||
Value v2; e2->eval(state, env, v2);
|
Value v2; e2->eval(state, env, v2);
|
||||||
v.mkBool(!state.eqValues(v1, v2));
|
v.mkBool(!state.eqValues(v1, v2, pos, "while testing two values for inequality"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void ExprOpAnd::eval(EvalState & state, Env & env, Value & v)
|
void ExprOpAnd::eval(EvalState & state, Env & env, Value & v)
|
||||||
{
|
{
|
||||||
v.mkBool(state.evalBool(env, e1, pos) && state.evalBool(env, e2, pos));
|
v.mkBool(state.evalBool(env, e1, pos, "in the left operand of the AND (&&) operator") && state.evalBool(env, e2, pos, "in the right operand of the AND (&&) operator"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void ExprOpOr::eval(EvalState & state, Env & env, Value & v)
|
void ExprOpOr::eval(EvalState & state, Env & env, Value & v)
|
||||||
{
|
{
|
||||||
v.mkBool(state.evalBool(env, e1, pos) || state.evalBool(env, e2, pos));
|
v.mkBool(state.evalBool(env, e1, pos, "in the left operand of the OR (||) operator") || state.evalBool(env, e2, pos, "in the right operand of the OR (||) operator"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void ExprOpImpl::eval(EvalState & state, Env & env, Value & v)
|
void ExprOpImpl::eval(EvalState & state, Env & env, Value & v)
|
||||||
{
|
{
|
||||||
v.mkBool(!state.evalBool(env, e1, pos) || state.evalBool(env, e2, pos));
|
v.mkBool(!state.evalBool(env, e1, pos, "in the left operand of the IMPL (->) operator") || state.evalBool(env, e2, pos, "in the right operand of the IMPL (->) operator"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v)
|
void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v)
|
||||||
{
|
{
|
||||||
Value v1, v2;
|
Value v1, v2;
|
||||||
state.evalAttrs(env, e1, v1);
|
state.evalAttrs(env, e1, v1, pos, "in the left operand of the update (//) operator");
|
||||||
state.evalAttrs(env, e2, v2);
|
state.evalAttrs(env, e2, v2, pos, "in the right operand of the update (//) operator");
|
||||||
|
|
||||||
state.nrOpUpdates++;
|
state.nrOpUpdates++;
|
||||||
|
|
||||||
|
@ -1929,18 +1826,18 @@ void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v)
|
||||||
Value v1; e1->eval(state, env, v1);
|
Value v1; e1->eval(state, env, v1);
|
||||||
Value v2; e2->eval(state, env, v2);
|
Value v2; e2->eval(state, env, v2);
|
||||||
Value * lists[2] = { &v1, &v2 };
|
Value * lists[2] = { &v1, &v2 };
|
||||||
state.concatLists(v, 2, lists, pos);
|
state.concatLists(v, 2, lists, pos, "while evaluating one of the elements to concatenate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos)
|
void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx)
|
||||||
{
|
{
|
||||||
nrListConcats++;
|
nrListConcats++;
|
||||||
|
|
||||||
Value * nonEmpty = 0;
|
Value * nonEmpty = 0;
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
for (size_t n = 0; n < nrLists; ++n) {
|
for (size_t n = 0; n < nrLists; ++n) {
|
||||||
forceList(*lists[n], pos);
|
forceList(*lists[n], pos, errorCtx);
|
||||||
auto l = lists[n]->listSize();
|
auto l = lists[n]->listSize();
|
||||||
len += l;
|
len += l;
|
||||||
if (l) nonEmpty = lists[n];
|
if (l) nonEmpty = lists[n];
|
||||||
|
@ -2017,20 +1914,20 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
||||||
nf = n;
|
nf = n;
|
||||||
nf += vTmp.fpoint;
|
nf += vTmp.fpoint;
|
||||||
} else
|
} else
|
||||||
state.throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp), env, *this);
|
state.error("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>();
|
||||||
} else if (firstType == nFloat) {
|
} else if (firstType == nFloat) {
|
||||||
if (vTmp.type() == nInt) {
|
if (vTmp.type() == nInt) {
|
||||||
nf += vTmp.integer;
|
nf += vTmp.integer;
|
||||||
} else if (vTmp.type() == nFloat) {
|
} else if (vTmp.type() == nFloat) {
|
||||||
nf += vTmp.fpoint;
|
nf += vTmp.fpoint;
|
||||||
} else
|
} else
|
||||||
state.throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp), env, *this);
|
state.error("cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>();
|
||||||
} else {
|
} else {
|
||||||
if (s.empty()) s.reserve(es->size());
|
if (s.empty()) s.reserve(es->size());
|
||||||
/* skip canonization of first path, which would only be not
|
/* skip canonization of first path, which would only be not
|
||||||
canonized in the first place if it's coming from a ./${foo} type
|
canonized in the first place if it's coming from a ./${foo} type
|
||||||
path */
|
path */
|
||||||
auto part = state.coerceToString(i_pos, vTmp, context, false, firstType == nString, !first);
|
auto part = state.coerceToString(i_pos, vTmp, context, false, firstType == nString, !first, "while evaluating a path segment");
|
||||||
sSize += part->size();
|
sSize += part->size();
|
||||||
s.emplace_back(std::move(part));
|
s.emplace_back(std::move(part));
|
||||||
}
|
}
|
||||||
|
@ -2044,7 +1941,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
||||||
v.mkFloat(nf);
|
v.mkFloat(nf);
|
||||||
else if (firstType == nPath) {
|
else if (firstType == nPath) {
|
||||||
if (!context.empty())
|
if (!context.empty())
|
||||||
state.throwEvalError(pos, "a string that refers to a store path cannot be appended to a path", env, *this);
|
state.error("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow<EvalError>();
|
||||||
v.mkPath(canonPath(str()));
|
v.mkPath(canonPath(str()));
|
||||||
} else
|
} else
|
||||||
v.mkStringMove(c_str(), context);
|
v.mkStringMove(c_str(), context);
|
||||||
|
@ -2094,33 +1991,47 @@ void EvalState::forceValueDeep(Value & v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
NixInt EvalState::forceInt(Value & v, const PosIdx pos)
|
NixInt EvalState::forceInt(Value & v, const PosIdx pos, std::string_view errorCtx)
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
try {
|
||||||
if (v.type() != nInt)
|
forceValue(v, pos);
|
||||||
throwTypeError(pos, "value is %1% while an integer was expected", v);
|
if (v.type() != nInt)
|
||||||
|
error("value is %1% while an integer was expected", showType(v)).debugThrow<TypeError>();
|
||||||
return v.integer;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
NixFloat EvalState::forceFloat(Value & v, const PosIdx pos)
|
|
||||||
{
|
|
||||||
forceValue(v, pos);
|
|
||||||
if (v.type() == nInt)
|
|
||||||
return v.integer;
|
return v.integer;
|
||||||
else if (v.type() != nFloat)
|
} catch (Error & e) {
|
||||||
throwTypeError(pos, "value is %1% while a float was expected", v);
|
e.addTrace(positions[pos], errorCtx);
|
||||||
return v.fpoint;
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool EvalState::forceBool(Value & v, const PosIdx pos)
|
NixFloat EvalState::forceFloat(Value & v, const PosIdx pos, std::string_view errorCtx)
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
try {
|
||||||
if (v.type() != nBool)
|
forceValue(v, pos);
|
||||||
throwTypeError(pos, "value is %1% while a Boolean was expected", v);
|
if (v.type() == nInt)
|
||||||
return v.boolean;
|
return v.integer;
|
||||||
|
else if (v.type() != nFloat)
|
||||||
|
error("value is %1% while a float was expected", showType(v)).debugThrow<TypeError>();
|
||||||
|
return v.fpoint;
|
||||||
|
} catch (Error & e) {
|
||||||
|
e.addTrace(positions[pos], errorCtx);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool EvalState::forceBool(Value & v, const PosIdx pos, std::string_view errorCtx)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
forceValue(v, pos);
|
||||||
|
if (v.type() != nBool)
|
||||||
|
error("value is %1% while a Boolean was expected", showType(v)).debugThrow<TypeError>();
|
||||||
|
return v.boolean;
|
||||||
|
} catch (Error & e) {
|
||||||
|
e.addTrace(positions[pos], errorCtx);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2130,21 +2041,30 @@ bool EvalState::isFunctor(Value & fun)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void EvalState::forceFunction(Value & v, const PosIdx pos)
|
void EvalState::forceFunction(Value & v, const PosIdx pos, std::string_view errorCtx)
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
try {
|
||||||
if (v.type() != nFunction && !isFunctor(v))
|
forceValue(v, pos);
|
||||||
throwTypeError(pos, "value is %1% while a function was expected", v);
|
if (v.type() != nFunction && !isFunctor(v))
|
||||||
|
error("value is %1% while a function was expected", showType(v)).debugThrow<TypeError>();
|
||||||
|
} catch (Error & e) {
|
||||||
|
e.addTrace(positions[pos], errorCtx);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::string_view EvalState::forceString(Value & v, const PosIdx pos)
|
std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string_view errorCtx)
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
try {
|
||||||
if (v.type() != nString) {
|
forceValue(v, pos);
|
||||||
throwTypeError(pos, "value is %1% while a string was expected", v);
|
if (v.type() != nString)
|
||||||
|
error("value is %1% while a string was expected", showType(v)).debugThrow<TypeError>();
|
||||||
|
return v.string.s;
|
||||||
|
} catch (Error & e) {
|
||||||
|
e.addTrace(positions[pos], errorCtx);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
return v.string.s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2188,24 +2108,19 @@ NixStringContext Value::getContext(const Store & store)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::string_view EvalState::forceString(Value & v, PathSet & context, const PosIdx pos)
|
std::string_view EvalState::forceString(Value & v, PathSet & context, const PosIdx pos, std::string_view errorCtx)
|
||||||
{
|
{
|
||||||
auto s = forceString(v, pos);
|
auto s = forceString(v, pos, errorCtx);
|
||||||
copyContext(v, context);
|
copyContext(v, context);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos)
|
std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx)
|
||||||
{
|
{
|
||||||
auto s = forceString(v, pos);
|
auto s = forceString(v, pos, errorCtx);
|
||||||
if (v.string.context) {
|
if (v.string.context) {
|
||||||
if (pos)
|
error("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0]).withTrace(pos, errorCtx).debugThrow<EvalError>();
|
||||||
throwEvalError(pos, "the string '%1%' is not allowed to refer to a store path (such as '%2%')",
|
|
||||||
v.string.s, v.string.context[0]);
|
|
||||||
else
|
|
||||||
throwEvalError("the string '%1%' is not allowed to refer to a store path (such as '%2%')",
|
|
||||||
v.string.s, v.string.context[0]);
|
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
@ -2229,14 +2144,15 @@ std::optional<std::string> EvalState::tryAttrsToString(const PosIdx pos, Value &
|
||||||
if (i != v.attrs->end()) {
|
if (i != v.attrs->end()) {
|
||||||
Value v1;
|
Value v1;
|
||||||
callFunction(*i->value, v, v1, pos);
|
callFunction(*i->value, v, v1, pos);
|
||||||
return coerceToString(pos, v1, context, coerceMore, copyToStore).toOwned();
|
return coerceToString(pos, v1, context, coerceMore, copyToStore,
|
||||||
|
"while evaluating the result of the `toString` attribute").toOwned();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet & context,
|
BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet & context,
|
||||||
bool coerceMore, bool copyToStore, bool canonicalizePath)
|
bool coerceMore, bool copyToStore, bool canonicalizePath, std::string_view errorCtx)
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
|
|
||||||
|
@ -2260,12 +2176,12 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
|
||||||
return std::move(*maybeString);
|
return std::move(*maybeString);
|
||||||
auto i = v.attrs->find(sOutPath);
|
auto i = v.attrs->find(sOutPath);
|
||||||
if (i == v.attrs->end())
|
if (i == v.attrs->end())
|
||||||
throwTypeError(pos, "cannot coerce a set to a string");
|
error("cannot coerce a set to a string", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
|
||||||
return coerceToString(pos, *i->value, context, coerceMore, copyToStore);
|
return coerceToString(pos, *i->value, context, coerceMore, copyToStore, canonicalizePath, errorCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v.type() == nExternal)
|
if (v.type() == nExternal)
|
||||||
return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore);
|
return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore, errorCtx);
|
||||||
|
|
||||||
if (coerceMore) {
|
if (coerceMore) {
|
||||||
/* Note that `false' is represented as an empty string for
|
/* Note that `false' is represented as an empty string for
|
||||||
|
@ -2279,7 +2195,13 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
|
||||||
if (v.isList()) {
|
if (v.isList()) {
|
||||||
std::string result;
|
std::string result;
|
||||||
for (auto [n, v2] : enumerate(v.listItems())) {
|
for (auto [n, v2] : enumerate(v.listItems())) {
|
||||||
result += *coerceToString(pos, *v2, context, coerceMore, copyToStore);
|
try {
|
||||||
|
result += *coerceToString(noPos, *v2, context, coerceMore, copyToStore, canonicalizePath,
|
||||||
|
"while evaluating one element of the list");
|
||||||
|
} catch (Error & e) {
|
||||||
|
e.addTrace(positions[pos], errorCtx);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
if (n < v.listSize() - 1
|
if (n < v.listSize() - 1
|
||||||
/* !!! not quite correct */
|
/* !!! not quite correct */
|
||||||
&& (!v2->isList() || v2->listSize() != 0))
|
&& (!v2->isList() || v2->listSize() != 0))
|
||||||
|
@ -2289,14 +2211,14 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throwTypeError(pos, "cannot coerce %1% to a string", v);
|
error("cannot coerce %1% to a string", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::string EvalState::copyPathToStore(PathSet & context, const Path & path)
|
std::string EvalState::copyPathToStore(PathSet & context, const Path & path)
|
||||||
{
|
{
|
||||||
if (nix::isDerivation(path))
|
if (nix::isDerivation(path))
|
||||||
throwEvalError("file names are not allowed to end in '%1%'", drvExtension);
|
error("file names are not allowed to end in '%1%'", drvExtension).debugThrow<EvalError>();
|
||||||
|
|
||||||
Path dstPath;
|
Path dstPath;
|
||||||
auto i = srcToStore.find(path);
|
auto i = srcToStore.find(path);
|
||||||
|
@ -2317,28 +2239,25 @@ std::string EvalState::copyPathToStore(PathSet & context, const Path & path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context)
|
Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
|
||||||
{
|
{
|
||||||
auto path = coerceToString(pos, v, context, false, false).toOwned();
|
auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned();
|
||||||
if (path == "" || path[0] != '/')
|
if (path == "" || path[0] != '/')
|
||||||
throwEvalError(pos, "string '%1%' doesn't represent an absolute path", path);
|
error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & context)
|
StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
|
||||||
{
|
{
|
||||||
auto path = coerceToString(pos, v, context, false, false).toOwned();
|
auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned();
|
||||||
if (auto storePath = store->maybeParseStorePath(path))
|
if (auto storePath = store->maybeParseStorePath(path))
|
||||||
return *storePath;
|
return *storePath;
|
||||||
throw EvalError({
|
error("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
|
||||||
.msg = hintfmt("path '%1%' is not in the Nix store", path),
|
|
||||||
.errPos = positions[pos]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool EvalState::eqValues(Value & v1, Value & v2)
|
bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_view errorCtx)
|
||||||
{
|
{
|
||||||
forceValue(v1, noPos);
|
forceValue(v1, noPos);
|
||||||
forceValue(v2, noPos);
|
forceValue(v2, noPos);
|
||||||
|
@ -2358,7 +2277,6 @@ bool EvalState::eqValues(Value & v1, Value & v2)
|
||||||
if (v1.type() != v2.type()) return false;
|
if (v1.type() != v2.type()) return false;
|
||||||
|
|
||||||
switch (v1.type()) {
|
switch (v1.type()) {
|
||||||
|
|
||||||
case nInt:
|
case nInt:
|
||||||
return v1.integer == v2.integer;
|
return v1.integer == v2.integer;
|
||||||
|
|
||||||
|
@ -2377,7 +2295,7 @@ bool EvalState::eqValues(Value & v1, Value & v2)
|
||||||
case nList:
|
case nList:
|
||||||
if (v1.listSize() != v2.listSize()) return false;
|
if (v1.listSize() != v2.listSize()) return false;
|
||||||
for (size_t n = 0; n < v1.listSize(); ++n)
|
for (size_t n = 0; n < v1.listSize(); ++n)
|
||||||
if (!eqValues(*v1.listElems()[n], *v2.listElems()[n])) return false;
|
if (!eqValues(*v1.listElems()[n], *v2.listElems()[n], pos, errorCtx)) return false;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case nAttrs: {
|
case nAttrs: {
|
||||||
|
@ -2387,7 +2305,7 @@ bool EvalState::eqValues(Value & v1, Value & v2)
|
||||||
Bindings::iterator i = v1.attrs->find(sOutPath);
|
Bindings::iterator i = v1.attrs->find(sOutPath);
|
||||||
Bindings::iterator j = v2.attrs->find(sOutPath);
|
Bindings::iterator j = v2.attrs->find(sOutPath);
|
||||||
if (i != v1.attrs->end() && j != v2.attrs->end())
|
if (i != v1.attrs->end() && j != v2.attrs->end())
|
||||||
return eqValues(*i->value, *j->value);
|
return eqValues(*i->value, *j->value, pos, errorCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v1.attrs->size() != v2.attrs->size()) return false;
|
if (v1.attrs->size() != v2.attrs->size()) return false;
|
||||||
|
@ -2395,7 +2313,7 @@ bool EvalState::eqValues(Value & v1, Value & v2)
|
||||||
/* Otherwise, compare the attributes one by one. */
|
/* Otherwise, compare the attributes one by one. */
|
||||||
Bindings::iterator i, j;
|
Bindings::iterator i, j;
|
||||||
for (i = v1.attrs->begin(), j = v2.attrs->begin(); i != v1.attrs->end(); ++i, ++j)
|
for (i = v1.attrs->begin(), j = v2.attrs->begin(); i != v1.attrs->end(); ++i, ++j)
|
||||||
if (i->name != j->name || !eqValues(*i->value, *j->value))
|
if (i->name != j->name || !eqValues(*i->value, *j->value, pos, errorCtx))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -2412,9 +2330,7 @@ bool EvalState::eqValues(Value & v1, Value & v2)
|
||||||
return v1.fpoint == v2.fpoint;
|
return v1.fpoint == v2.fpoint;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throwEvalError("cannot compare %1% with %2%",
|
error("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow<EvalError>();
|
||||||
showType(v1),
|
|
||||||
showType(v2));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2538,12 +2454,13 @@ void EvalState::printStats()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const
|
std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, std::string_view errorCtx) const
|
||||||
{
|
{
|
||||||
throw TypeError({
|
auto e = TypeError({
|
||||||
.msg = hintfmt("cannot coerce %1% to a string", showType()),
|
.msg = hintfmt("cannot coerce %1% to a string", showType())
|
||||||
.errPos = pos
|
|
||||||
});
|
});
|
||||||
|
e.addTrace(pos, errorCtx);
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,6 @@ void copyContext(const Value & v, PathSet & context);
|
||||||
typedef std::map<Path, StorePath> SrcToStore;
|
typedef std::map<Path, StorePath> SrcToStore;
|
||||||
|
|
||||||
|
|
||||||
std::ostream & printValue(const EvalState & state, std::ostream & str, const Value & v);
|
|
||||||
std::string printValue(const EvalState & state, const Value & v);
|
std::string printValue(const EvalState & state, const Value & v);
|
||||||
std::ostream & operator << (std::ostream & os, const ValueType t);
|
std::ostream & operator << (std::ostream & os, const ValueType t);
|
||||||
|
|
||||||
|
@ -87,6 +86,43 @@ struct DebugTrace {
|
||||||
|
|
||||||
void debugError(Error * e, Env & env, Expr & expr);
|
void debugError(Error * e, Env & env, Expr & expr);
|
||||||
|
|
||||||
|
class ErrorBuilder
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
EvalState & state;
|
||||||
|
ErrorInfo info;
|
||||||
|
|
||||||
|
ErrorBuilder(EvalState & s, ErrorInfo && i): state(s), info(i) { }
|
||||||
|
|
||||||
|
public:
|
||||||
|
template<typename... Args>
|
||||||
|
[[nodiscard, gnu::noinline]]
|
||||||
|
static ErrorBuilder * create(EvalState & s, const Args & ... args)
|
||||||
|
{
|
||||||
|
return new ErrorBuilder(s, ErrorInfo { .msg = hintfmt(args...) });
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard, gnu::noinline]]
|
||||||
|
ErrorBuilder & atPos(PosIdx pos);
|
||||||
|
|
||||||
|
[[nodiscard, gnu::noinline]]
|
||||||
|
ErrorBuilder & withTrace(PosIdx pos, const std::string_view text);
|
||||||
|
|
||||||
|
[[nodiscard, gnu::noinline]]
|
||||||
|
ErrorBuilder & withFrameTrace(PosIdx pos, const std::string_view text);
|
||||||
|
|
||||||
|
[[nodiscard, gnu::noinline]]
|
||||||
|
ErrorBuilder & withSuggestions(Suggestions & s);
|
||||||
|
|
||||||
|
[[nodiscard, gnu::noinline]]
|
||||||
|
ErrorBuilder & withFrame(const Env & e, const Expr & ex);
|
||||||
|
|
||||||
|
template<class ErrorType>
|
||||||
|
[[gnu::noinline, gnu::noreturn]]
|
||||||
|
void debugThrow();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
class EvalState : public std::enable_shared_from_this<EvalState>
|
class EvalState : public std::enable_shared_from_this<EvalState>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -146,29 +182,35 @@ public:
|
||||||
|
|
||||||
template<class E>
|
template<class E>
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
[[gnu::noinline, gnu::noreturn]]
|
||||||
void debugThrow(E && error, const Env & env, const Expr & expr)
|
void debugThrowLastTrace(E && error)
|
||||||
{
|
{
|
||||||
if (debugRepl)
|
debugThrow(error, nullptr, nullptr);
|
||||||
runDebugRepl(&error, env, expr);
|
|
||||||
|
|
||||||
throw std::move(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class E>
|
template<class E>
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
[[gnu::noinline, gnu::noreturn]]
|
||||||
void debugThrowLastTrace(E && e)
|
void debugThrow(E && error, const Env * env, const Expr * expr)
|
||||||
{
|
{
|
||||||
// Call this in the situation where Expr and Env are inaccessible.
|
if (debugRepl && ((env && expr) || !debugTraces.empty())) {
|
||||||
// The debugger will start in the last context that's in the
|
if (!env || !expr) {
|
||||||
// DebugTrace stack.
|
const DebugTrace & last = debugTraces.front();
|
||||||
if (debugRepl && !debugTraces.empty()) {
|
env = &last.env;
|
||||||
const DebugTrace & last = debugTraces.front();
|
expr = &last.expr;
|
||||||
runDebugRepl(&e, last.env, last.expr);
|
}
|
||||||
|
runDebugRepl(&error, *env, *expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw std::move(e);
|
throw std::move(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ErrorBuilder * errorBuilder;
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
[[nodiscard, gnu::noinline]]
|
||||||
|
ErrorBuilder & error(const Args & ... args) {
|
||||||
|
errorBuilder = ErrorBuilder::create(*this, args...);
|
||||||
|
return *errorBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SrcToStore srcToStore;
|
SrcToStore srcToStore;
|
||||||
|
@ -283,8 +325,8 @@ public:
|
||||||
/* Evaluation the expression, then verify that it has the expected
|
/* Evaluation the expression, then verify that it has the expected
|
||||||
type. */
|
type. */
|
||||||
inline bool evalBool(Env & env, Expr * e);
|
inline bool evalBool(Env & env, Expr * e);
|
||||||
inline bool evalBool(Env & env, Expr * e, const PosIdx pos);
|
inline bool evalBool(Env & env, Expr * e, const PosIdx pos, std::string_view errorCtx);
|
||||||
inline void evalAttrs(Env & env, Expr * e, Value & v);
|
inline void evalAttrs(Env & env, Expr * e, Value & v, const PosIdx pos, std::string_view errorCtx);
|
||||||
|
|
||||||
/* If `v' is a thunk, enter it and overwrite `v' with the result
|
/* If `v' is a thunk, enter it and overwrite `v' with the result
|
||||||
of the evaluation of the thunk. If `v' is a delayed function
|
of the evaluation of the thunk. If `v' is a delayed function
|
||||||
|
@ -300,89 +342,25 @@ public:
|
||||||
void forceValueDeep(Value & v);
|
void forceValueDeep(Value & v);
|
||||||
|
|
||||||
/* Force `v', and then verify that it has the expected type. */
|
/* Force `v', and then verify that it has the expected type. */
|
||||||
NixInt forceInt(Value & v, const PosIdx pos);
|
NixInt forceInt(Value & v, const PosIdx pos, std::string_view errorCtx);
|
||||||
NixFloat forceFloat(Value & v, const PosIdx pos);
|
NixFloat forceFloat(Value & v, const PosIdx pos, std::string_view errorCtx);
|
||||||
bool forceBool(Value & v, const PosIdx pos);
|
bool forceBool(Value & v, const PosIdx pos, std::string_view errorCtx);
|
||||||
|
|
||||||
void forceAttrs(Value & v, const PosIdx pos);
|
void forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx);
|
||||||
|
|
||||||
template <typename Callable>
|
template <typename Callable>
|
||||||
inline void forceAttrs(Value & v, Callable getPos);
|
inline void forceAttrs(Value & v, Callable getPos, std::string_view errorCtx);
|
||||||
|
|
||||||
inline void forceList(Value & v, const PosIdx pos);
|
inline void forceList(Value & v, const PosIdx pos, std::string_view errorCtx);
|
||||||
void forceFunction(Value & v, const PosIdx pos); // either lambda or primop
|
void forceFunction(Value & v, const PosIdx pos, std::string_view errorCtx); // either lambda or primop
|
||||||
std::string_view forceString(Value & v, const PosIdx pos = noPos);
|
std::string_view forceString(Value & v, const PosIdx pos, std::string_view errorCtx);
|
||||||
std::string_view forceString(Value & v, PathSet & context, const PosIdx pos = noPos);
|
std::string_view forceString(Value & v, PathSet & context, const PosIdx pos, std::string_view errorCtx);
|
||||||
std::string_view forceStringNoCtx(Value & v, const PosIdx pos = noPos);
|
std::string_view forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx);
|
||||||
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwEvalError(const PosIdx pos, const char * s);
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwEvalError(const PosIdx pos, const char * s,
|
|
||||||
Env & env, Expr & expr);
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwEvalError(const char * s, const std::string & s2);
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2);
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwEvalError(const char * s, const std::string & s2,
|
|
||||||
Env & env, Expr & expr);
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2,
|
|
||||||
Env & env, Expr & expr);
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwEvalError(const char * s, const std::string & s2, const std::string & s3,
|
|
||||||
Env & env, Expr & expr);
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3,
|
|
||||||
Env & env, Expr & expr);
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3);
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwEvalError(const char * s, const std::string & s2, const std::string & s3);
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, const std::string & s2,
|
|
||||||
Env & env, Expr & expr);
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2,
|
|
||||||
Env & env, Expr & expr);
|
|
||||||
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwTypeError(const PosIdx pos, const char * s, const Value & v);
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwTypeError(const PosIdx pos, const char * s, const Value & v,
|
|
||||||
Env & env, Expr & expr);
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwTypeError(const PosIdx pos, const char * s);
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwTypeError(const PosIdx pos, const char * s,
|
|
||||||
Env & env, Expr & expr);
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, const Symbol s2,
|
|
||||||
Env & env, Expr & expr);
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s, const ExprLambda & fun, const Symbol s2,
|
|
||||||
Env & env, Expr & expr);
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwTypeError(const char * s, const Value & v,
|
|
||||||
Env & env, Expr & expr);
|
|
||||||
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwAssertionError(const PosIdx pos, const char * s, const std::string & s1,
|
|
||||||
Env & env, Expr & expr);
|
|
||||||
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1,
|
|
||||||
Env & env, Expr & expr);
|
|
||||||
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1,
|
|
||||||
Env & env, Expr & expr);
|
|
||||||
|
|
||||||
[[gnu::noinline]]
|
[[gnu::noinline]]
|
||||||
void addErrorTrace(Error & e, const char * s, const std::string & s2) const;
|
void addErrorTrace(Error & e, const char * s, const std::string & s2) const;
|
||||||
[[gnu::noinline]]
|
[[gnu::noinline]]
|
||||||
void addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const;
|
void addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2, bool frame = false) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/* Return true iff the value `v' denotes a derivation (i.e. a
|
/* Return true iff the value `v' denotes a derivation (i.e. a
|
||||||
|
@ -398,17 +376,18 @@ public:
|
||||||
referenced paths are copied to the Nix store as a side effect. */
|
referenced paths are copied to the Nix store as a side effect. */
|
||||||
BackedStringView coerceToString(const PosIdx pos, Value & v, PathSet & context,
|
BackedStringView coerceToString(const PosIdx pos, Value & v, PathSet & context,
|
||||||
bool coerceMore = false, bool copyToStore = true,
|
bool coerceMore = false, bool copyToStore = true,
|
||||||
bool canonicalizePath = true);
|
bool canonicalizePath = true,
|
||||||
|
std::string_view errorCtx = "");
|
||||||
|
|
||||||
std::string copyPathToStore(PathSet & context, const Path & path);
|
std::string copyPathToStore(PathSet & context, const Path & path);
|
||||||
|
|
||||||
/* Path coercion. Converts strings, paths and derivations to a
|
/* Path coercion. Converts strings, paths and derivations to a
|
||||||
path. The result is guaranteed to be a canonicalised, absolute
|
path. The result is guaranteed to be a canonicalised, absolute
|
||||||
path. Nothing is copied to the store. */
|
path. Nothing is copied to the store. */
|
||||||
Path coerceToPath(const PosIdx pos, Value & v, PathSet & context);
|
Path coerceToPath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx);
|
||||||
|
|
||||||
/* Like coerceToPath, but the result must be a store path. */
|
/* Like coerceToPath, but the result must be a store path. */
|
||||||
StorePath coerceToStorePath(const PosIdx pos, Value & v, PathSet & context);
|
StorePath coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
@ -468,7 +447,7 @@ public:
|
||||||
|
|
||||||
/* Do a deep equality test between two values. That is, list
|
/* Do a deep equality test between two values. That is, list
|
||||||
elements and attributes are compared recursively. */
|
elements and attributes are compared recursively. */
|
||||||
bool eqValues(Value & v1, Value & v2);
|
bool eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_view errorCtx);
|
||||||
|
|
||||||
bool isFunctor(Value & fun);
|
bool isFunctor(Value & fun);
|
||||||
|
|
||||||
|
@ -503,7 +482,7 @@ public:
|
||||||
void mkThunk_(Value & v, Expr * expr);
|
void mkThunk_(Value & v, Expr * expr);
|
||||||
void mkPos(Value & v, PosIdx pos);
|
void mkPos(Value & v, PosIdx pos);
|
||||||
|
|
||||||
void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos);
|
void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx);
|
||||||
|
|
||||||
/* Print statistics. */
|
/* Print statistics. */
|
||||||
void printStats();
|
void printStats();
|
||||||
|
@ -670,6 +649,13 @@ extern EvalSettings evalSettings;
|
||||||
|
|
||||||
static const std::string corepkgsPrefix{"/__corepkgs__/"};
|
static const std::string corepkgsPrefix{"/__corepkgs__/"};
|
||||||
|
|
||||||
|
template<class ErrorType>
|
||||||
|
void ErrorBuilder::debugThrow()
|
||||||
|
{
|
||||||
|
// NOTE: We always use the -LastTrace version as we push the new trace in withFrame()
|
||||||
|
state.debugThrowLastTrace(ErrorType(info));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "eval-inline.hh"
|
#include "eval-inline.hh"
|
||||||
|
|
|
@ -259,28 +259,28 @@ static Flake getFlake(
|
||||||
if (setting.value->type() == nString)
|
if (setting.value->type() == nString)
|
||||||
flake.config.settings.emplace(
|
flake.config.settings.emplace(
|
||||||
state.symbols[setting.name],
|
state.symbols[setting.name],
|
||||||
std::string(state.forceStringNoCtx(*setting.value, setting.pos)));
|
std::string(state.forceStringNoCtx(*setting.value, setting.pos, "")));
|
||||||
else if (setting.value->type() == nPath) {
|
else if (setting.value->type() == nPath) {
|
||||||
PathSet emptyContext = {};
|
PathSet emptyContext = {};
|
||||||
flake.config.settings.emplace(
|
flake.config.settings.emplace(
|
||||||
state.symbols[setting.name],
|
state.symbols[setting.name],
|
||||||
state.coerceToString(setting.pos, *setting.value, emptyContext, false, true, true) .toOwned());
|
state.coerceToString(setting.pos, *setting.value, emptyContext, false, true, true, "") .toOwned());
|
||||||
}
|
}
|
||||||
else if (setting.value->type() == nInt)
|
else if (setting.value->type() == nInt)
|
||||||
flake.config.settings.emplace(
|
flake.config.settings.emplace(
|
||||||
state.symbols[setting.name],
|
state.symbols[setting.name],
|
||||||
state.forceInt(*setting.value, setting.pos));
|
state.forceInt(*setting.value, setting.pos, ""));
|
||||||
else if (setting.value->type() == nBool)
|
else if (setting.value->type() == nBool)
|
||||||
flake.config.settings.emplace(
|
flake.config.settings.emplace(
|
||||||
state.symbols[setting.name],
|
state.symbols[setting.name],
|
||||||
Explicit<bool> { state.forceBool(*setting.value, setting.pos) });
|
Explicit<bool> { state.forceBool(*setting.value, setting.pos, "") });
|
||||||
else if (setting.value->type() == nList) {
|
else if (setting.value->type() == nList) {
|
||||||
std::vector<std::string> ss;
|
std::vector<std::string> ss;
|
||||||
for (auto elem : setting.value->listItems()) {
|
for (auto elem : setting.value->listItems()) {
|
||||||
if (elem->type() != nString)
|
if (elem->type() != nString)
|
||||||
throw TypeError("list element in flake configuration setting '%s' is %s while a string is expected",
|
throw TypeError("list element in flake configuration setting '%s' is %s while a string is expected",
|
||||||
state.symbols[setting.name], showType(*setting.value));
|
state.symbols[setting.name], showType(*setting.value));
|
||||||
ss.emplace_back(state.forceStringNoCtx(*elem, setting.pos));
|
ss.emplace_back(state.forceStringNoCtx(*elem, setting.pos, ""));
|
||||||
}
|
}
|
||||||
flake.config.settings.emplace(state.symbols[setting.name], ss);
|
flake.config.settings.emplace(state.symbols[setting.name], ss);
|
||||||
}
|
}
|
||||||
|
@ -741,7 +741,7 @@ void callFlake(EvalState & state,
|
||||||
|
|
||||||
static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos));
|
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake"));
|
||||||
auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
|
auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
|
||||||
if (evalSettings.pureEval && !flakeRef.input.isLocked())
|
if (evalSettings.pureEval && !flakeRef.input.isLocked())
|
||||||
throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, state.positions[pos]);
|
throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, state.positions[pos]);
|
||||||
|
|
|
@ -51,7 +51,7 @@ std::string DrvInfo::queryName() const
|
||||||
if (name == "" && attrs) {
|
if (name == "" && attrs) {
|
||||||
auto i = attrs->find(state->sName);
|
auto i = attrs->find(state->sName);
|
||||||
if (i == attrs->end()) throw TypeError("derivation name missing");
|
if (i == attrs->end()) throw TypeError("derivation name missing");
|
||||||
name = state->forceStringNoCtx(*i->value);
|
name = state->forceStringNoCtx(*i->value, noPos, "while evaluating the 'name' attribute of a derivation");
|
||||||
}
|
}
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ std::string DrvInfo::querySystem() const
|
||||||
{
|
{
|
||||||
if (system == "" && attrs) {
|
if (system == "" && attrs) {
|
||||||
auto i = attrs->find(state->sSystem);
|
auto i = attrs->find(state->sSystem);
|
||||||
system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, i->pos);
|
system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, i->pos, "while evaluating the 'system' attribute of a derivation");
|
||||||
}
|
}
|
||||||
return system;
|
return system;
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ std::optional<StorePath> DrvInfo::queryDrvPath() const
|
||||||
if (i == attrs->end())
|
if (i == attrs->end())
|
||||||
drvPath = {std::nullopt};
|
drvPath = {std::nullopt};
|
||||||
else
|
else
|
||||||
drvPath = {state->coerceToStorePath(i->pos, *i->value, context)};
|
drvPath = {state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the 'drvPath' attribute of a derivation")};
|
||||||
}
|
}
|
||||||
return drvPath.value_or(std::nullopt);
|
return drvPath.value_or(std::nullopt);
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ StorePath DrvInfo::queryOutPath() const
|
||||||
Bindings::iterator i = attrs->find(state->sOutPath);
|
Bindings::iterator i = attrs->find(state->sOutPath);
|
||||||
PathSet context;
|
PathSet context;
|
||||||
if (i != attrs->end())
|
if (i != attrs->end())
|
||||||
outPath = state->coerceToStorePath(i->pos, *i->value, context);
|
outPath = state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the output path of a derivation");
|
||||||
}
|
}
|
||||||
if (!outPath)
|
if (!outPath)
|
||||||
throw UnimplementedError("CA derivations are not yet supported");
|
throw UnimplementedError("CA derivations are not yet supported");
|
||||||
|
@ -109,23 +109,23 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall
|
||||||
/* Get the ‘outputs’ list. */
|
/* Get the ‘outputs’ list. */
|
||||||
Bindings::iterator i;
|
Bindings::iterator i;
|
||||||
if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) {
|
if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) {
|
||||||
state->forceList(*i->value, i->pos);
|
state->forceList(*i->value, i->pos, "while evaluating the 'outputs' attribute of a derivation");
|
||||||
|
|
||||||
/* For each output... */
|
/* For each output... */
|
||||||
for (auto elem : i->value->listItems()) {
|
for (auto elem : i->value->listItems()) {
|
||||||
std::string output(state->forceStringNoCtx(*elem, i->pos));
|
std::string output(state->forceStringNoCtx(*elem, i->pos, "while evaluating the name of an output of a derivation"));
|
||||||
|
|
||||||
if (withPaths) {
|
if (withPaths) {
|
||||||
/* Evaluate the corresponding set. */
|
/* Evaluate the corresponding set. */
|
||||||
Bindings::iterator out = attrs->find(state->symbols.create(output));
|
Bindings::iterator out = attrs->find(state->symbols.create(output));
|
||||||
if (out == attrs->end()) continue; // FIXME: throw error?
|
if (out == attrs->end()) continue; // FIXME: throw error?
|
||||||
state->forceAttrs(*out->value, i->pos);
|
state->forceAttrs(*out->value, i->pos, "while evaluating an output of a derivation");
|
||||||
|
|
||||||
/* And evaluate its ‘outPath’ attribute. */
|
/* And evaluate its ‘outPath’ attribute. */
|
||||||
Bindings::iterator outPath = out->value->attrs->find(state->sOutPath);
|
Bindings::iterator outPath = out->value->attrs->find(state->sOutPath);
|
||||||
if (outPath == out->value->attrs->end()) continue; // FIXME: throw error?
|
if (outPath == out->value->attrs->end()) continue; // FIXME: throw error?
|
||||||
PathSet context;
|
PathSet context;
|
||||||
outputs.emplace(output, state->coerceToStorePath(outPath->pos, *outPath->value, context));
|
outputs.emplace(output, state->coerceToStorePath(outPath->pos, *outPath->value, context, "while evaluating an output path of a derivation"));
|
||||||
} else
|
} else
|
||||||
outputs.emplace(output, std::nullopt);
|
outputs.emplace(output, std::nullopt);
|
||||||
}
|
}
|
||||||
|
@ -137,7 +137,7 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall
|
||||||
return outputs;
|
return outputs;
|
||||||
|
|
||||||
Bindings::iterator i;
|
Bindings::iterator i;
|
||||||
if (attrs && (i = attrs->find(state->sOutputSpecified)) != attrs->end() && state->forceBool(*i->value, i->pos)) {
|
if (attrs && (i = attrs->find(state->sOutputSpecified)) != attrs->end() && state->forceBool(*i->value, i->pos, "while evaluating the 'outputSpecified' attribute of a derivation")) {
|
||||||
Outputs result;
|
Outputs result;
|
||||||
auto out = outputs.find(queryOutputName());
|
auto out = outputs.find(queryOutputName());
|
||||||
if (out == outputs.end())
|
if (out == outputs.end())
|
||||||
|
@ -169,7 +169,7 @@ std::string DrvInfo::queryOutputName() const
|
||||||
{
|
{
|
||||||
if (outputName == "" && attrs) {
|
if (outputName == "" && attrs) {
|
||||||
Bindings::iterator i = attrs->find(state->sOutputName);
|
Bindings::iterator i = attrs->find(state->sOutputName);
|
||||||
outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value) : "";
|
outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value, noPos, "while evaluating the output name of a derivation") : "";
|
||||||
}
|
}
|
||||||
return outputName;
|
return outputName;
|
||||||
}
|
}
|
||||||
|
@ -181,7 +181,7 @@ Bindings * DrvInfo::getMeta()
|
||||||
if (!attrs) return 0;
|
if (!attrs) return 0;
|
||||||
Bindings::iterator a = attrs->find(state->sMeta);
|
Bindings::iterator a = attrs->find(state->sMeta);
|
||||||
if (a == attrs->end()) return 0;
|
if (a == attrs->end()) return 0;
|
||||||
state->forceAttrs(*a->value, a->pos);
|
state->forceAttrs(*a->value, a->pos, "while evaluating the 'meta' attribute of a derivation");
|
||||||
meta = a->value->attrs;
|
meta = a->value->attrs;
|
||||||
return meta;
|
return meta;
|
||||||
}
|
}
|
||||||
|
@ -382,7 +382,7 @@ static void getDerivations(EvalState & state, Value & vIn,
|
||||||
`recurseForDerivations = true' attribute. */
|
`recurseForDerivations = true' attribute. */
|
||||||
if (i->value->type() == nAttrs) {
|
if (i->value->type() == nAttrs) {
|
||||||
Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations);
|
Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations);
|
||||||
if (j != i->value->attrs->end() && state.forceBool(*j->value, j->pos))
|
if (j != i->value->attrs->end() && state.forceBool(*j->value, j->pos, "while evaluating the attribute `recurseForDerivations`"))
|
||||||
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
|
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
#include "error.hh"
|
#include "error.hh"
|
||||||
#include "chunked-vector.hh"
|
#include "chunked-vector.hh"
|
||||||
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -401,22 +401,22 @@ expr_op
|
||||||
| '-' expr_op %prec NEGATE { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {new ExprInt(0), $2}); }
|
| '-' expr_op %prec NEGATE { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {new ExprInt(0), $2}); }
|
||||||
| expr_op EQ expr_op { $$ = new ExprOpEq($1, $3); }
|
| expr_op EQ expr_op { $$ = new ExprOpEq($1, $3); }
|
||||||
| expr_op NEQ expr_op { $$ = new ExprOpNEq($1, $3); }
|
| expr_op NEQ expr_op { $$ = new ExprOpNEq($1, $3); }
|
||||||
| expr_op '<' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$1, $3}); }
|
| expr_op '<' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$1, $3}); }
|
||||||
| expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$3, $1})); }
|
| expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$3, $1})); }
|
||||||
| expr_op '>' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$3, $1}); }
|
| expr_op '>' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$3, $1}); }
|
||||||
| expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$1, $3})); }
|
| expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$1, $3})); }
|
||||||
| expr_op AND expr_op { $$ = new ExprOpAnd(CUR_POS, $1, $3); }
|
| expr_op AND expr_op { $$ = new ExprOpAnd(makeCurPos(@2, data), $1, $3); }
|
||||||
| expr_op OR expr_op { $$ = new ExprOpOr(CUR_POS, $1, $3); }
|
| expr_op OR expr_op { $$ = new ExprOpOr(makeCurPos(@2, data), $1, $3); }
|
||||||
| expr_op IMPL expr_op { $$ = new ExprOpImpl(CUR_POS, $1, $3); }
|
| expr_op IMPL expr_op { $$ = new ExprOpImpl(makeCurPos(@2, data), $1, $3); }
|
||||||
| expr_op UPDATE expr_op { $$ = new ExprOpUpdate(CUR_POS, $1, $3); }
|
| expr_op UPDATE expr_op { $$ = new ExprOpUpdate(makeCurPos(@2, data), $1, $3); }
|
||||||
| expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); }
|
| expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); }
|
||||||
| expr_op '+' expr_op
|
| expr_op '+' expr_op
|
||||||
{ $$ = new ExprConcatStrings(CUR_POS, false, new std::vector<std::pair<PosIdx, Expr *>>({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); }
|
{ $$ = new ExprConcatStrings(makeCurPos(@2, data), false, new std::vector<std::pair<PosIdx, Expr *> >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); }
|
||||||
| expr_op '-' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {$1, $3}); }
|
| expr_op '-' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__sub")), {$1, $3}); }
|
||||||
| expr_op '*' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__mul")), {$1, $3}); }
|
| expr_op '*' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__mul")), {$1, $3}); }
|
||||||
| expr_op '/' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__div")), {$1, $3}); }
|
| expr_op '/' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__div")), {$1, $3}); }
|
||||||
| expr_op CONCAT expr_op { $$ = new ExprOpConcatLists(CUR_POS, $1, $3); }
|
| expr_op CONCAT expr_op { $$ = new ExprOpConcatLists(makeCurPos(@2, data), $1, $3); }
|
||||||
| expr_op '$' expr_op { $$ = new ExprCall(CUR_POS, $1, {$3}); }
|
| expr_op '$' expr_op { $$ = new ExprCall(makeCurPos(@2, data), $1, {$3}); }
|
||||||
| expr_app
|
| expr_app
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -784,13 +784,13 @@ Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, c
|
||||||
if (hasPrefix(path, "nix/"))
|
if (hasPrefix(path, "nix/"))
|
||||||
return concatStrings(corepkgsPrefix, path.substr(4));
|
return concatStrings(corepkgsPrefix, path.substr(4));
|
||||||
|
|
||||||
debugThrowLastTrace(ThrownError({
|
debugThrow(ThrownError({
|
||||||
.msg = hintfmt(evalSettings.pureEval
|
.msg = hintfmt(evalSettings.pureEval
|
||||||
? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)"
|
? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)"
|
||||||
: "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)",
|
: "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)",
|
||||||
path),
|
path),
|
||||||
.errPos = positions[pos]
|
.errPos = positions[pos]
|
||||||
}));
|
}), 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -8,7 +8,7 @@ namespace nix {
|
||||||
static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
PathSet context;
|
||||||
auto s = state.coerceToString(pos, *args[0], context);
|
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardStringContext");
|
||||||
v.mkString(*s);
|
v.mkString(*s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ static RegisterPrimOp primop_unsafeDiscardStringContext("__unsafeDiscardStringCo
|
||||||
static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
PathSet context;
|
||||||
state.forceString(*args[0], context, pos);
|
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.hasContext");
|
||||||
v.mkBool(!context.empty());
|
v.mkBool(!context.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ static RegisterPrimOp primop_hasContext("__hasContext", 1, prim_hasContext);
|
||||||
static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
PathSet context;
|
||||||
auto s = state.coerceToString(pos, *args[0], context);
|
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardOutputDependency");
|
||||||
|
|
||||||
PathSet context2;
|
PathSet context2;
|
||||||
for (auto & p : context)
|
for (auto & p : context)
|
||||||
|
@ -73,7 +73,7 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
|
||||||
Strings outputs;
|
Strings outputs;
|
||||||
};
|
};
|
||||||
PathSet context;
|
PathSet context;
|
||||||
state.forceString(*args[0], context, pos);
|
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getContext");
|
||||||
auto contextInfos = std::map<Path, ContextInfo>();
|
auto contextInfos = std::map<Path, ContextInfo>();
|
||||||
for (const auto & p : context) {
|
for (const auto & p : context) {
|
||||||
Path drv;
|
Path drv;
|
||||||
|
@ -137,9 +137,9 @@ static RegisterPrimOp primop_getContext("__getContext", 1, prim_getContext);
|
||||||
static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
PathSet context;
|
||||||
auto orig = state.forceString(*args[0], context, pos);
|
auto orig = state.forceString(*args[0], context, noPos, "while evaluating the first argument passed to builtins.appendContext");
|
||||||
|
|
||||||
state.forceAttrs(*args[1], pos);
|
state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.appendContext");
|
||||||
|
|
||||||
auto sPath = state.symbols.create("path");
|
auto sPath = state.symbols.create("path");
|
||||||
auto sAllOutputs = state.symbols.create("allOutputs");
|
auto sAllOutputs = state.symbols.create("allOutputs");
|
||||||
|
@ -147,24 +147,24 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
|
||||||
const auto & name = state.symbols[i.name];
|
const auto & name = state.symbols[i.name];
|
||||||
if (!state.store->isStorePath(name))
|
if (!state.store->isStorePath(name))
|
||||||
throw EvalError({
|
throw EvalError({
|
||||||
.msg = hintfmt("Context key '%s' is not a store path", name),
|
.msg = hintfmt("context key '%s' is not a store path", name),
|
||||||
.errPos = state.positions[i.pos]
|
.errPos = state.positions[i.pos]
|
||||||
});
|
});
|
||||||
if (!settings.readOnlyMode)
|
if (!settings.readOnlyMode)
|
||||||
state.store->ensurePath(state.store->parseStorePath(name));
|
state.store->ensurePath(state.store->parseStorePath(name));
|
||||||
state.forceAttrs(*i.value, i.pos);
|
state.forceAttrs(*i.value, i.pos, "while evaluating the value of a string context");
|
||||||
auto iter = i.value->attrs->find(sPath);
|
auto iter = i.value->attrs->find(sPath);
|
||||||
if (iter != i.value->attrs->end()) {
|
if (iter != i.value->attrs->end()) {
|
||||||
if (state.forceBool(*iter->value, iter->pos))
|
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `path` attribute of a string context"))
|
||||||
context.emplace(name);
|
context.emplace(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
iter = i.value->attrs->find(sAllOutputs);
|
iter = i.value->attrs->find(sAllOutputs);
|
||||||
if (iter != i.value->attrs->end()) {
|
if (iter != i.value->attrs->end()) {
|
||||||
if (state.forceBool(*iter->value, iter->pos)) {
|
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `allOutputs` attribute of a string context")) {
|
||||||
if (!isDerivation(name)) {
|
if (!isDerivation(name)) {
|
||||||
throw EvalError({
|
throw EvalError({
|
||||||
.msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", name),
|
.msg = hintfmt("tried to add all-outputs context of %s, which is not a derivation, to a string", name),
|
||||||
.errPos = state.positions[i.pos]
|
.errPos = state.positions[i.pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -174,15 +174,15 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
|
||||||
|
|
||||||
iter = i.value->attrs->find(state.sOutputs);
|
iter = i.value->attrs->find(state.sOutputs);
|
||||||
if (iter != i.value->attrs->end()) {
|
if (iter != i.value->attrs->end()) {
|
||||||
state.forceList(*iter->value, iter->pos);
|
state.forceList(*iter->value, iter->pos, "while evaluating the `outputs` attribute of a string context");
|
||||||
if (iter->value->listSize() && !isDerivation(name)) {
|
if (iter->value->listSize() && !isDerivation(name)) {
|
||||||
throw EvalError({
|
throw EvalError({
|
||||||
.msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", name),
|
.msg = hintfmt("tried to add derivation output context of %s, which is not a derivation, to a string", name),
|
||||||
.errPos = state.positions[i.pos]
|
.errPos = state.positions[i.pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for (auto elem : iter->value->listItems()) {
|
for (auto elem : iter->value->listItems()) {
|
||||||
auto outputName = state.forceStringNoCtx(*elem, iter->pos);
|
auto outputName = state.forceStringNoCtx(*elem, iter->pos, "while evaluating an output name within a string context");
|
||||||
context.insert(concatStrings("!", outputName, "!", name));
|
context.insert(concatStrings("!", outputName, "!", name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace nix {
|
||||||
|
|
||||||
static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
state.forceAttrs(*args[0], pos);
|
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.fetchClosure");
|
||||||
|
|
||||||
std::optional<std::string> fromStoreUrl;
|
std::optional<std::string> fromStoreUrl;
|
||||||
std::optional<StorePath> fromPath;
|
std::optional<StorePath> fromPath;
|
||||||
|
@ -19,7 +19,8 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
|
||||||
|
|
||||||
if (attrName == "fromPath") {
|
if (attrName == "fromPath") {
|
||||||
PathSet context;
|
PathSet context;
|
||||||
fromPath = state.coerceToStorePath(attr.pos, *attr.value, context);
|
fromPath = state.coerceToStorePath(attr.pos, *attr.value, context,
|
||||||
|
"while evaluating the 'fromPath' attribute passed to builtins.fetchClosure");
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (attrName == "toPath") {
|
else if (attrName == "toPath") {
|
||||||
|
@ -27,12 +28,14 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
|
||||||
toCA = true;
|
toCA = true;
|
||||||
if (attr.value->type() != nString || attr.value->string.s != std::string("")) {
|
if (attr.value->type() != nString || attr.value->string.s != std::string("")) {
|
||||||
PathSet context;
|
PathSet context;
|
||||||
toPath = state.coerceToStorePath(attr.pos, *attr.value, context);
|
toPath = state.coerceToStorePath(attr.pos, *attr.value, context,
|
||||||
|
"while evaluating the 'toPath' attribute passed to builtins.fetchClosure");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (attrName == "fromStore")
|
else if (attrName == "fromStore")
|
||||||
fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos);
|
fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos,
|
||||||
|
"while evaluating the 'fromStore' attribute passed to builtins.fetchClosure");
|
||||||
|
|
||||||
else
|
else
|
||||||
throw Error({
|
throw Error({
|
||||||
|
|
|
@ -19,23 +19,21 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
|
||||||
|
|
||||||
if (args[0]->type() == nAttrs) {
|
if (args[0]->type() == nAttrs) {
|
||||||
|
|
||||||
state.forceAttrs(*args[0], pos);
|
|
||||||
|
|
||||||
for (auto & attr : *args[0]->attrs) {
|
for (auto & attr : *args[0]->attrs) {
|
||||||
std::string_view n(state.symbols[attr.name]);
|
std::string_view n(state.symbols[attr.name]);
|
||||||
if (n == "url")
|
if (n == "url")
|
||||||
url = state.coerceToString(attr.pos, *attr.value, context, false, false).toOwned();
|
url = state.coerceToString(attr.pos, *attr.value, context, false, false, "while evaluating the `url` attribute passed to builtins.fetchMercurial").toOwned();
|
||||||
else if (n == "rev") {
|
else if (n == "rev") {
|
||||||
// Ugly: unlike fetchGit, here the "rev" attribute can
|
// Ugly: unlike fetchGit, here the "rev" attribute can
|
||||||
// be both a revision or a branch/tag name.
|
// be both a revision or a branch/tag name.
|
||||||
auto value = state.forceStringNoCtx(*attr.value, attr.pos);
|
auto value = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `rev` attribute passed to builtins.fetchMercurial");
|
||||||
if (std::regex_match(value.begin(), value.end(), revRegex))
|
if (std::regex_match(value.begin(), value.end(), revRegex))
|
||||||
rev = Hash::parseAny(value, htSHA1);
|
rev = Hash::parseAny(value, htSHA1);
|
||||||
else
|
else
|
||||||
ref = value;
|
ref = value;
|
||||||
}
|
}
|
||||||
else if (n == "name")
|
else if (n == "name")
|
||||||
name = state.forceStringNoCtx(*attr.value, attr.pos);
|
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.fetchMercurial");
|
||||||
else
|
else
|
||||||
throw EvalError({
|
throw EvalError({
|
||||||
.msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]),
|
.msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]),
|
||||||
|
@ -50,7 +48,7 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
|
||||||
});
|
});
|
||||||
|
|
||||||
} else
|
} else
|
||||||
url = state.coerceToString(pos, *args[0], context, false, false).toOwned();
|
url = state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to builtins.fetchMercurial").toOwned();
|
||||||
|
|
||||||
// FIXME: git externals probably can be used to bypass the URI
|
// FIXME: git externals probably can be used to bypass the URI
|
||||||
// whitelist. Ah well.
|
// whitelist. Ah well.
|
||||||
|
|
|
@ -102,7 +102,7 @@ static void fetchTree(
|
||||||
state.forceValue(*args[0], pos);
|
state.forceValue(*args[0], pos);
|
||||||
|
|
||||||
if (args[0]->type() == nAttrs) {
|
if (args[0]->type() == nAttrs) {
|
||||||
state.forceAttrs(*args[0], pos);
|
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.fetchTree");
|
||||||
|
|
||||||
fetchers::Attrs attrs;
|
fetchers::Attrs attrs;
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ static void fetchTree(
|
||||||
.msg = hintfmt("unexpected attribute 'type'"),
|
.msg = hintfmt("unexpected attribute 'type'"),
|
||||||
.errPos = state.positions[pos]
|
.errPos = state.positions[pos]
|
||||||
}));
|
}));
|
||||||
type = state.forceStringNoCtx(*aType->value, aType->pos);
|
type = state.forceStringNoCtx(*aType->value, aType->pos, "while evaluating the `type` attribute passed to builtins.fetchTree");
|
||||||
} else if (!type)
|
} else if (!type)
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.debugThrowLastTrace(EvalError({
|
||||||
.msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"),
|
.msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"),
|
||||||
|
@ -125,7 +125,7 @@ static void fetchTree(
|
||||||
if (attr.name == state.sType) continue;
|
if (attr.name == state.sType) continue;
|
||||||
state.forceValue(*attr.value, attr.pos);
|
state.forceValue(*attr.value, attr.pos);
|
||||||
if (attr.value->type() == nPath || attr.value->type() == nString) {
|
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],
|
attrs.emplace(state.symbols[attr.name],
|
||||||
state.symbols[attr.name] == "url"
|
state.symbols[attr.name] == "url"
|
||||||
? type == "git"
|
? type == "git"
|
||||||
|
@ -151,7 +151,7 @@ static void fetchTree(
|
||||||
|
|
||||||
input = fetchers::Input::fromAttrs(std::move(attrs));
|
input = fetchers::Input::fromAttrs(std::move(attrs));
|
||||||
} else {
|
} else {
|
||||||
auto url = state.coerceToString(pos, *args[0], context, false, false).toOwned();
|
auto url = state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to the fetcher").toOwned();
|
||||||
|
|
||||||
if (type == "git") {
|
if (type == "git") {
|
||||||
fetchers::Attrs attrs;
|
fetchers::Attrs attrs;
|
||||||
|
@ -195,16 +195,14 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
|
||||||
|
|
||||||
if (args[0]->type() == nAttrs) {
|
if (args[0]->type() == nAttrs) {
|
||||||
|
|
||||||
state.forceAttrs(*args[0], pos);
|
|
||||||
|
|
||||||
for (auto & attr : *args[0]->attrs) {
|
for (auto & attr : *args[0]->attrs) {
|
||||||
std::string_view n(state.symbols[attr.name]);
|
std::string_view n(state.symbols[attr.name]);
|
||||||
if (n == "url")
|
if (n == "url")
|
||||||
url = state.forceStringNoCtx(*attr.value, attr.pos);
|
url = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the url we should fetch");
|
||||||
else if (n == "sha256")
|
else if (n == "sha256")
|
||||||
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos), htSHA256);
|
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the sha256 of the content we should fetch"), htSHA256);
|
||||||
else if (n == "name")
|
else if (n == "name")
|
||||||
name = state.forceStringNoCtx(*attr.value, attr.pos);
|
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the name of the content we should fetch");
|
||||||
else
|
else
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.debugThrowLastTrace(EvalError({
|
||||||
.msg = hintfmt("unsupported argument '%s' to '%s'", n, who),
|
.msg = hintfmt("unsupported argument '%s' to '%s'", n, who),
|
||||||
|
@ -218,7 +216,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
|
||||||
.errPos = state.positions[pos]
|
.errPos = state.positions[pos]
|
||||||
}));
|
}));
|
||||||
} else
|
} else
|
||||||
url = state.forceStringNoCtx(*args[0], pos);
|
url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch");
|
||||||
|
|
||||||
state.checkURI(*url);
|
state.checkURI(*url);
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace nix {
|
||||||
|
|
||||||
static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, Value & val)
|
static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, Value & val)
|
||||||
{
|
{
|
||||||
auto toml = state.forceStringNoCtx(*args[0], pos);
|
auto toml = state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.fromTOML");
|
||||||
|
|
||||||
std::istringstream tomlStream(std::string{toml});
|
std::istringstream tomlStream(std::string{toml});
|
||||||
|
|
||||||
|
|
94
src/libexpr/tests/error_traces.cc
Normal file
94
src/libexpr/tests/error_traces.cc
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "libexprtests.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
using namespace testing;
|
||||||
|
|
||||||
|
// Testing eval of PrimOp's
|
||||||
|
class ErrorTraceTest : public LibExprTest { };
|
||||||
|
|
||||||
|
#define ASSERT_TRACE1(args, type, message) \
|
||||||
|
ASSERT_THROW( \
|
||||||
|
try { \
|
||||||
|
eval("builtins." args); \
|
||||||
|
} catch (BaseError & e) { \
|
||||||
|
ASSERT_EQ(PrintToString(e.info().msg), \
|
||||||
|
PrintToString(message)); \
|
||||||
|
auto trace = e.info().traces.rbegin(); \
|
||||||
|
ASSERT_EQ(PrintToString(trace->hint), \
|
||||||
|
PrintToString(hintfmt("while calling the '%s' builtin", "genericClosure"))); \
|
||||||
|
throw; \
|
||||||
|
} \
|
||||||
|
, type \
|
||||||
|
)
|
||||||
|
|
||||||
|
#define ASSERT_TRACE2(args, type, message, context) \
|
||||||
|
ASSERT_THROW( \
|
||||||
|
try { \
|
||||||
|
eval("builtins." args); \
|
||||||
|
} catch (BaseError & e) { \
|
||||||
|
ASSERT_EQ(PrintToString(e.info().msg), \
|
||||||
|
PrintToString(message)); \
|
||||||
|
auto trace = e.info().traces.rbegin(); \
|
||||||
|
ASSERT_EQ(PrintToString(trace->hint), \
|
||||||
|
PrintToString(context)); \
|
||||||
|
++trace; \
|
||||||
|
ASSERT_EQ(PrintToString(trace->hint), \
|
||||||
|
PrintToString(hintfmt("while calling the '%s' builtin", "genericClosure"))); \
|
||||||
|
throw; \
|
||||||
|
} \
|
||||||
|
, type \
|
||||||
|
)
|
||||||
|
|
||||||
|
TEST_F(ErrorTraceTest, genericClosure) { \
|
||||||
|
ASSERT_TRACE2("genericClosure 1",
|
||||||
|
TypeError,
|
||||||
|
hintfmt("value is %s while a set was expected", "an integer"),
|
||||||
|
hintfmt("while evaluating the first argument passed to builtins.genericClosure"));
|
||||||
|
|
||||||
|
ASSERT_TRACE1("genericClosure {}",
|
||||||
|
TypeError,
|
||||||
|
hintfmt("attribute '%s' missing %s", "startSet", normaltxt("in the attrset passed as argument to builtins.genericClosure")));
|
||||||
|
|
||||||
|
ASSERT_TRACE2("genericClosure { startSet = 1; }",
|
||||||
|
TypeError,
|
||||||
|
hintfmt("value is %s while a list was expected", "an integer"),
|
||||||
|
hintfmt("while evaluating the 'startSet' attribute passed as argument to builtins.genericClosure"));
|
||||||
|
|
||||||
|
// Okay: "genericClosure { startSet = []; }"
|
||||||
|
|
||||||
|
ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = true; }",
|
||||||
|
TypeError,
|
||||||
|
hintfmt("value is %s while a function was expected", "a Boolean"),
|
||||||
|
hintfmt("while evaluating the 'operator' attribute passed as argument to builtins.genericClosure"));
|
||||||
|
|
||||||
|
ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: true; }",
|
||||||
|
TypeError,
|
||||||
|
hintfmt("value is %s while a list was expected", "a Boolean"),
|
||||||
|
hintfmt("while evaluating the return value of the `operator` passed to builtins.genericClosure")); // TODO: inconsistent naming
|
||||||
|
|
||||||
|
ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: [ true ]; }",
|
||||||
|
TypeError,
|
||||||
|
hintfmt("value is %s while a set was expected", "a Boolean"),
|
||||||
|
hintfmt("while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure"));
|
||||||
|
|
||||||
|
ASSERT_TRACE1("genericClosure { startSet = [{ key = 1;}]; operator = item: [ {} ]; }",
|
||||||
|
TypeError,
|
||||||
|
hintfmt("attribute '%s' missing %s", "key", normaltxt("in one of the attrsets generated by (or initially passed to) builtins.genericClosure")));
|
||||||
|
|
||||||
|
ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: [{ key = ''a''; }]; }",
|
||||||
|
EvalError,
|
||||||
|
hintfmt("cannot compare %s with %s", "a string", "an integer"),
|
||||||
|
hintfmt("while comparing the `key` attributes of two genericClosure elements"));
|
||||||
|
|
||||||
|
ASSERT_TRACE2("genericClosure { startSet = [ true ]; operator = item: [{ key = ''a''; }]; }",
|
||||||
|
TypeError,
|
||||||
|
hintfmt("value is %s while a set was expected", "a Boolean"),
|
||||||
|
hintfmt("while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* namespace nix */
|
|
@ -12,6 +12,7 @@ namespace nix {
|
||||||
class LibExprTest : public ::testing::Test {
|
class LibExprTest : public ::testing::Test {
|
||||||
public:
|
public:
|
||||||
static void SetUpTestSuite() {
|
static void SetUpTestSuite() {
|
||||||
|
initLibStore();
|
||||||
initGC();
|
initGC();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -823,4 +823,10 @@ namespace nix {
|
||||||
for (const auto [n, elem] : enumerate(v.listItems()))
|
for (const auto [n, elem] : enumerate(v.listItems()))
|
||||||
ASSERT_THAT(*elem, IsStringEq(expected[n]));
|
ASSERT_THAT(*elem, IsStringEq(expected[n]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(PrimOpTest, genericClosure_not_strict) {
|
||||||
|
// Operator should not be used when startSet is empty
|
||||||
|
auto v = eval("builtins.genericClosure { startSet = []; }");
|
||||||
|
ASSERT_THAT(v, IsListOfSize(0));
|
||||||
|
}
|
||||||
} /* namespace nix */
|
} /* namespace nix */
|
||||||
|
|
|
@ -90,7 +90,7 @@ class ExternalValueBase
|
||||||
/* Coerce the value to a string. Defaults to uncoercable, i.e. throws an
|
/* Coerce the value to a string. Defaults to uncoercable, i.e. throws an
|
||||||
* error.
|
* error.
|
||||||
*/
|
*/
|
||||||
virtual std::string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const;
|
virtual std::string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, std::string_view errorCtx) const;
|
||||||
|
|
||||||
/* Compare to another value of the same type. Defaults to uncomparable,
|
/* Compare to another value of the same type. Defaults to uncomparable,
|
||||||
* i.e. always false.
|
* i.e. always false.
|
||||||
|
|
|
@ -235,6 +235,7 @@ void initNix()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
preloadNSS();
|
preloadNSS();
|
||||||
|
initLibStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -362,6 +363,7 @@ void printVersion(const std::string & programName)
|
||||||
<< "\n";
|
<< "\n";
|
||||||
std::cout << "Store directory: " << settings.nixStore << "\n";
|
std::cout << "Store directory: " << settings.nixStore << "\n";
|
||||||
std::cout << "State directory: " << settings.nixStateDir << "\n";
|
std::cout << "State directory: " << settings.nixStateDir << "\n";
|
||||||
|
std::cout << "Data directory: " << settings.nixDataDir << "\n";
|
||||||
}
|
}
|
||||||
throw Exit();
|
throw Exit();
|
||||||
}
|
}
|
||||||
|
@ -402,8 +404,6 @@ int handleExceptions(const std::string & programName, std::function<void()> fun)
|
||||||
return 1;
|
return 1;
|
||||||
} catch (BaseError & e) {
|
} catch (BaseError & e) {
|
||||||
logError(e.info());
|
logError(e.info());
|
||||||
if (e.hasTrace() && !loggerSettings.showTrace.get())
|
|
||||||
printError("(use '--show-trace' to show detailed location information)");
|
|
||||||
return e.status;
|
return e.status;
|
||||||
} catch (std::bad_alloc & e) {
|
} catch (std::bad_alloc & e) {
|
||||||
printError(error + "out of memory");
|
printError(error + "out of memory");
|
||||||
|
|
|
@ -39,7 +39,6 @@
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <net/if.h>
|
#include <net/if.h>
|
||||||
#include <netinet/ip.h>
|
#include <netinet/ip.h>
|
||||||
#include <sys/personality.h>
|
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <sched.h>
|
#include <sched.h>
|
||||||
#include <sys/param.h>
|
#include <sys/param.h>
|
||||||
|
@ -545,7 +544,8 @@ void DerivationGoal::inputsRealised()
|
||||||
However, the impure derivations feature still relies on this
|
However, the impure derivations feature still relies on this
|
||||||
fragile way of doing things, because its builds do not have
|
fragile way of doing things, because its builds do not have
|
||||||
a representation in the store, which is a usability problem
|
a representation in the store, which is a usability problem
|
||||||
in itself */
|
in itself. When implementing this logic entirely with lookups
|
||||||
|
make sure that they're cached. */
|
||||||
if (auto outPath = get(inputDrvOutputs, { depDrvPath, j })) {
|
if (auto outPath = get(inputDrvOutputs, { depDrvPath, j })) {
|
||||||
worker.store.computeFSClosure(*outPath, inputPaths);
|
worker.store.computeFSClosure(*outPath, inputPaths);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include "callback.hh"
|
#include "callback.hh"
|
||||||
#include "json-utils.hh"
|
#include "json-utils.hh"
|
||||||
#include "cgroup.hh"
|
#include "cgroup.hh"
|
||||||
|
#include "personality.hh"
|
||||||
|
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
@ -24,7 +25,6 @@
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <sys/utsname.h>
|
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
|
||||||
|
@ -37,7 +37,6 @@
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <net/if.h>
|
#include <net/if.h>
|
||||||
#include <netinet/ip.h>
|
#include <netinet/ip.h>
|
||||||
#include <sys/personality.h>
|
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <sched.h>
|
#include <sched.h>
|
||||||
#include <sys/param.h>
|
#include <sys/param.h>
|
||||||
|
@ -1964,33 +1963,7 @@ void LocalDerivationGoal::runChild()
|
||||||
/* Close all other file descriptors. */
|
/* Close all other file descriptors. */
|
||||||
closeMostFDs({STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO});
|
closeMostFDs({STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO});
|
||||||
|
|
||||||
#if __linux__
|
setPersonality(drv->platform);
|
||||||
/* Change the personality to 32-bit if we're doing an
|
|
||||||
i686-linux build on an x86_64-linux machine. */
|
|
||||||
struct utsname utsbuf;
|
|
||||||
uname(&utsbuf);
|
|
||||||
if ((drv->platform == "i686-linux"
|
|
||||||
&& (settings.thisSystem == "x86_64-linux"
|
|
||||||
|| (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64"))))
|
|
||||||
|| drv->platform == "armv7l-linux"
|
|
||||||
|| drv->platform == "armv6l-linux")
|
|
||||||
{
|
|
||||||
if (personality(PER_LINUX32) == -1)
|
|
||||||
throw SysError("cannot set 32-bit personality");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Impersonate a Linux 2.6 machine to get some determinism in
|
|
||||||
builds that depend on the kernel version. */
|
|
||||||
if ((drv->platform == "i686-linux" || drv->platform == "x86_64-linux") && settings.impersonateLinux26) {
|
|
||||||
int cur = personality(0xffffffff);
|
|
||||||
if (cur != -1) personality(cur | 0x0020000 /* == UNAME26 */);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Disable address space randomization for improved
|
|
||||||
determinism. */
|
|
||||||
int cur = personality(0xffffffff);
|
|
||||||
if (cur != -1) personality(cur | ADDR_NO_RANDOMIZE);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Disable core dumps by default. */
|
/* Disable core dumps by default. */
|
||||||
struct rlimit limit = { 0, RLIM_INFINITY };
|
struct rlimit limit = { 0, RLIM_INFINITY };
|
||||||
|
@ -2077,10 +2050,14 @@ void LocalDerivationGoal::runChild()
|
||||||
sandboxProfile += "(deny default (with no-log))\n";
|
sandboxProfile += "(deny default (with no-log))\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
sandboxProfile += "(import \"sandbox-defaults.sb\")\n";
|
sandboxProfile +=
|
||||||
|
#include "sandbox-defaults.sb"
|
||||||
|
;
|
||||||
|
|
||||||
if (!derivationType.isSandboxed())
|
if (!derivationType.isSandboxed())
|
||||||
sandboxProfile += "(import \"sandbox-network.sb\")\n";
|
sandboxProfile +=
|
||||||
|
#include "sandbox-network.sb"
|
||||||
|
;
|
||||||
|
|
||||||
/* Add the output paths we'll use at build-time to the chroot */
|
/* Add the output paths we'll use at build-time to the chroot */
|
||||||
sandboxProfile += "(allow file-read* file-write* process-exec\n";
|
sandboxProfile += "(allow file-read* file-write* process-exec\n";
|
||||||
|
@ -2123,7 +2100,9 @@ void LocalDerivationGoal::runChild()
|
||||||
|
|
||||||
sandboxProfile += additionalSandboxProfile;
|
sandboxProfile += additionalSandboxProfile;
|
||||||
} else
|
} else
|
||||||
sandboxProfile += "(import \"sandbox-minimal.sb\")\n";
|
sandboxProfile +=
|
||||||
|
#include "sandbox-minimal.sb"
|
||||||
|
;
|
||||||
|
|
||||||
debug("Generated sandbox profile:");
|
debug("Generated sandbox profile:");
|
||||||
debug(sandboxProfile);
|
debug(sandboxProfile);
|
||||||
|
@ -2148,8 +2127,6 @@ void LocalDerivationGoal::runChild()
|
||||||
args.push_back(sandboxFile);
|
args.push_back(sandboxFile);
|
||||||
args.push_back("-D");
|
args.push_back("-D");
|
||||||
args.push_back("_GLOBAL_TMP_DIR=" + globalTmpDir);
|
args.push_back("_GLOBAL_TMP_DIR=" + globalTmpDir);
|
||||||
args.push_back("-D");
|
|
||||||
args.push_back("IMPORT_DIR=" + settings.nixDataDir + "/nix/sandbox/");
|
|
||||||
if (allowLocalNetworking) {
|
if (allowLocalNetworking) {
|
||||||
args.push_back("-D");
|
args.push_back("-D");
|
||||||
args.push_back(std::string("_ALLOW_LOCAL_NETWORKING=1"));
|
args.push_back(std::string("_ALLOW_LOCAL_NETWORKING=1"));
|
||||||
|
|
44
src/libstore/build/personality.cc
Normal file
44
src/libstore/build/personality.cc
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
#include "personality.hh"
|
||||||
|
#include "globals.hh"
|
||||||
|
|
||||||
|
#if __linux__
|
||||||
|
#include <sys/utsname.h>
|
||||||
|
#include <sys/personality.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
void setPersonality(std::string_view system)
|
||||||
|
{
|
||||||
|
#if __linux__
|
||||||
|
/* Change the personality to 32-bit if we're doing an
|
||||||
|
i686-linux build on an x86_64-linux machine. */
|
||||||
|
struct utsname utsbuf;
|
||||||
|
uname(&utsbuf);
|
||||||
|
if ((system == "i686-linux"
|
||||||
|
&& (std::string_view(SYSTEM) == "x86_64-linux"
|
||||||
|
|| (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64"))))
|
||||||
|
|| system == "armv7l-linux"
|
||||||
|
|| system == "armv6l-linux")
|
||||||
|
{
|
||||||
|
if (personality(PER_LINUX32) == -1)
|
||||||
|
throw SysError("cannot set 32-bit personality");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Impersonate a Linux 2.6 machine to get some determinism in
|
||||||
|
builds that depend on the kernel version. */
|
||||||
|
if ((system == "i686-linux" || system == "x86_64-linux") && settings.impersonateLinux26) {
|
||||||
|
int cur = personality(0xffffffff);
|
||||||
|
if (cur != -1) personality(cur | 0x0020000 /* == UNAME26 */);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disable address space randomization for improved
|
||||||
|
determinism. */
|
||||||
|
int cur = personality(0xffffffff);
|
||||||
|
if (cur != -1) personality(cur | ADDR_NO_RANDOMIZE);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
11
src/libstore/build/personality.hh
Normal file
11
src/libstore/build/personality.hh
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
void setPersonality(std::string_view system);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
R""(
|
||||||
|
|
||||||
(define TMPDIR (param "_GLOBAL_TMP_DIR"))
|
(define TMPDIR (param "_GLOBAL_TMP_DIR"))
|
||||||
|
|
||||||
(deny default)
|
(deny default)
|
||||||
|
@ -104,3 +106,5 @@
|
||||||
(subpath "/System/Library/Apple/usr/libexec/oah")
|
(subpath "/System/Library/Apple/usr/libexec/oah")
|
||||||
(subpath "/System/Library/LaunchDaemons/com.apple.oahd.plist")
|
(subpath "/System/Library/LaunchDaemons/com.apple.oahd.plist")
|
||||||
(subpath "/Library/Apple/System/Library/LaunchDaemons/com.apple.oahd.plist"))
|
(subpath "/Library/Apple/System/Library/LaunchDaemons/com.apple.oahd.plist"))
|
||||||
|
|
||||||
|
)""
|
|
@ -1,5 +1,9 @@
|
||||||
|
R""(
|
||||||
|
|
||||||
(allow default)
|
(allow default)
|
||||||
|
|
||||||
; Disallow creating setuid/setgid binaries, since that
|
; Disallow creating setuid/setgid binaries, since that
|
||||||
; would allow breaking build user isolation.
|
; would allow breaking build user isolation.
|
||||||
(deny file-write-setugid)
|
(deny file-write-setugid)
|
||||||
|
|
||||||
|
)""
|
|
@ -1,3 +1,5 @@
|
||||||
|
R""(
|
||||||
|
|
||||||
; Allow local and remote network traffic.
|
; Allow local and remote network traffic.
|
||||||
(allow network* (local ip) (remote ip))
|
(allow network* (local ip) (remote ip))
|
||||||
|
|
||||||
|
@ -18,3 +20,5 @@
|
||||||
; Allow access to trustd.
|
; Allow access to trustd.
|
||||||
(allow mach-lookup (global-name "com.apple.trustd"))
|
(allow mach-lookup (global-name "com.apple.trustd"))
|
||||||
(allow mach-lookup (global-name "com.apple.trustd.agent"))
|
(allow mach-lookup (global-name "com.apple.trustd.agent"))
|
||||||
|
|
||||||
|
)""
|
|
@ -95,7 +95,7 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
|
||||||
throw Error(
|
throw Error(
|
||||||
"files '%1%' and '%2%' have the same priority %3%; "
|
"files '%1%' and '%2%' have the same priority %3%; "
|
||||||
"use 'nix-env --set-flag priority NUMBER INSTALLED_PKGNAME' "
|
"use 'nix-env --set-flag priority NUMBER INSTALLED_PKGNAME' "
|
||||||
"or type 'nix profile install --help' if using 'nix profile' to find out how"
|
"or type 'nix profile install --help' if using 'nix profile' to find out how "
|
||||||
"to change the priority of one of the conflicting packages"
|
"to change the priority of one of the conflicting packages"
|
||||||
" (0 being the highest priority)",
|
" (0 being the highest priority)",
|
||||||
srcFile, readLink(dstFile), priority);
|
srcFile, readLink(dstFile), priority);
|
||||||
|
|
|
@ -77,60 +77,73 @@ Path LocalFSStore::addPermRoot(const StorePath & storePath, const Path & _gcRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void LocalStore::addTempRoot(const StorePath & path)
|
void LocalStore::createTempRootsFile()
|
||||||
{
|
{
|
||||||
auto state(_state.lock());
|
auto fdTempRoots(_fdTempRoots.lock());
|
||||||
|
|
||||||
/* Create the temporary roots file for this process. */
|
/* Create the temporary roots file for this process. */
|
||||||
if (!state->fdTempRoots) {
|
if (*fdTempRoots) return;
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
if (pathExists(fnTempRoots))
|
if (pathExists(fnTempRoots))
|
||||||
/* It *must* be stale, since there can be no two
|
/* It *must* be stale, since there can be no two
|
||||||
processes with the same pid. */
|
processes with the same pid. */
|
||||||
unlink(fnTempRoots.c_str());
|
unlink(fnTempRoots.c_str());
|
||||||
|
|
||||||
state->fdTempRoots = openLockFile(fnTempRoots, true);
|
*fdTempRoots = openLockFile(fnTempRoots, true);
|
||||||
|
|
||||||
debug("acquiring write lock on '%s'", fnTempRoots);
|
debug("acquiring write lock on '%s'", fnTempRoots);
|
||||||
lockFile(state->fdTempRoots.get(), ltWrite, true);
|
lockFile(fdTempRoots->get(), ltWrite, true);
|
||||||
|
|
||||||
/* Check whether the garbage collector didn't get in our
|
/* Check whether the garbage collector didn't get in our
|
||||||
way. */
|
way. */
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (fstat(state->fdTempRoots.get(), &st) == -1)
|
if (fstat(fdTempRoots->get(), &st) == -1)
|
||||||
throw SysError("statting '%1%'", fnTempRoots);
|
throw SysError("statting '%1%'", fnTempRoots);
|
||||||
if (st.st_size == 0) break;
|
if (st.st_size == 0) break;
|
||||||
|
|
||||||
/* The garbage collector deleted this file before we could
|
/* The garbage collector deleted this file before we could get
|
||||||
get a lock. (It won't delete the file after we get a
|
a lock. (It won't delete the file after we get a lock.)
|
||||||
lock.) Try again. */
|
Try again. */
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LocalStore::addTempRoot(const StorePath & path)
|
||||||
|
{
|
||||||
|
createTempRootsFile();
|
||||||
|
|
||||||
|
/* Open/create the global GC lock file. */
|
||||||
|
{
|
||||||
|
auto fdGCLock(_fdGCLock.lock());
|
||||||
|
if (!*fdGCLock)
|
||||||
|
*fdGCLock = openGCLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state->fdGCLock)
|
|
||||||
state->fdGCLock = openGCLock();
|
|
||||||
|
|
||||||
restart:
|
restart:
|
||||||
FdLock gcLock(state->fdGCLock.get(), ltRead, false, "");
|
/* Try to acquire a shared global GC lock (non-blocking). This
|
||||||
|
only succeeds if the garbage collector is not currently
|
||||||
|
running. */
|
||||||
|
FdLock gcLock(_fdGCLock.lock()->get(), ltRead, false, "");
|
||||||
|
|
||||||
if (!gcLock.acquired) {
|
if (!gcLock.acquired) {
|
||||||
/* We couldn't get a shared global GC lock, so the garbage
|
/* We couldn't get a shared global GC lock, so the garbage
|
||||||
collector is running. So we have to connect to the garbage
|
collector is running. So we have to connect to the garbage
|
||||||
collector and inform it about our root. */
|
collector and inform it about our root. */
|
||||||
if (!state->fdRootsSocket) {
|
auto fdRootsSocket(_fdRootsSocket.lock());
|
||||||
|
|
||||||
|
if (!*fdRootsSocket) {
|
||||||
auto socketPath = stateDir.get() + gcSocketPath;
|
auto socketPath = stateDir.get() + gcSocketPath;
|
||||||
debug("connecting to '%s'", socketPath);
|
debug("connecting to '%s'", socketPath);
|
||||||
state->fdRootsSocket = createUnixDomainSocket();
|
*fdRootsSocket = createUnixDomainSocket();
|
||||||
try {
|
try {
|
||||||
nix::connect(state->fdRootsSocket.get(), socketPath);
|
nix::connect(fdRootsSocket->get(), socketPath);
|
||||||
} catch (SysError & e) {
|
} catch (SysError & e) {
|
||||||
/* The garbage collector may have exited, so we need to
|
/* The garbage collector may have exited, so we need to
|
||||||
restart. */
|
restart. */
|
||||||
if (e.errNo == ECONNREFUSED) {
|
if (e.errNo == ECONNREFUSED) {
|
||||||
debug("GC socket connection refused");
|
debug("GC socket connection refused");
|
||||||
state->fdRootsSocket.close();
|
fdRootsSocket->close();
|
||||||
goto restart;
|
goto restart;
|
||||||
}
|
}
|
||||||
throw;
|
throw;
|
||||||
|
@ -139,9 +152,9 @@ void LocalStore::addTempRoot(const StorePath & path)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
debug("sending GC root '%s'", printStorePath(path));
|
debug("sending GC root '%s'", printStorePath(path));
|
||||||
writeFull(state->fdRootsSocket.get(), printStorePath(path) + "\n", false);
|
writeFull(fdRootsSocket->get(), printStorePath(path) + "\n", false);
|
||||||
char c;
|
char c;
|
||||||
readFull(state->fdRootsSocket.get(), &c, 1);
|
readFull(fdRootsSocket->get(), &c, 1);
|
||||||
assert(c == '1');
|
assert(c == '1');
|
||||||
debug("got ack for GC root '%s'", printStorePath(path));
|
debug("got ack for GC root '%s'", printStorePath(path));
|
||||||
} catch (SysError & e) {
|
} catch (SysError & e) {
|
||||||
|
@ -149,20 +162,21 @@ void LocalStore::addTempRoot(const StorePath & path)
|
||||||
restart. */
|
restart. */
|
||||||
if (e.errNo == EPIPE || e.errNo == ECONNRESET) {
|
if (e.errNo == EPIPE || e.errNo == ECONNRESET) {
|
||||||
debug("GC socket disconnected");
|
debug("GC socket disconnected");
|
||||||
state->fdRootsSocket.close();
|
fdRootsSocket->close();
|
||||||
goto restart;
|
goto restart;
|
||||||
}
|
}
|
||||||
throw;
|
throw;
|
||||||
} catch (EndOfFile & e) {
|
} catch (EndOfFile & e) {
|
||||||
debug("GC socket disconnected");
|
debug("GC socket disconnected");
|
||||||
state->fdRootsSocket.close();
|
fdRootsSocket->close();
|
||||||
goto restart;
|
goto restart;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Append the store path to the temporary roots file. */
|
/* Record the store path in the temporary roots file so it will be
|
||||||
|
seen by a future run of the garbage collector. */
|
||||||
auto s = printStorePath(path) + '\0';
|
auto s = printStorePath(path) + '\0';
|
||||||
writeFull(state->fdTempRoots.get(), s);
|
writeFull(_fdTempRoots.lock()->get(), s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -290,4 +290,18 @@ void initPlugins()
|
||||||
settings.pluginFiles.pluginsLoaded = true;
|
settings.pluginFiles.pluginsLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool initLibStoreDone = false;
|
||||||
|
|
||||||
|
void assertLibStoreInitialized() {
|
||||||
|
if (!initLibStoreDone) {
|
||||||
|
printError("The program must call nix::initNix() before calling any libstore library functions.");
|
||||||
|
abort();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void initLibStore() {
|
||||||
|
initLibStoreDone = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -491,6 +491,9 @@ public:
|
||||||
for example, `/dev/nvidiactl?` specifies that `/dev/nvidiactl` will
|
for example, `/dev/nvidiactl?` specifies that `/dev/nvidiactl` will
|
||||||
only be mounted in the sandbox if it exists in the host filesystem.
|
only be mounted in the sandbox if it exists in the host filesystem.
|
||||||
|
|
||||||
|
If the source is in the Nix store, then its closure will be added to
|
||||||
|
the sandbox as well.
|
||||||
|
|
||||||
Depending on how Nix was built, the default value for this option
|
Depending on how Nix was built, the default value for this option
|
||||||
may be empty or provide `/bin/sh` as a bind-mount of `bash`.
|
may be empty or provide `/bin/sh` as a bind-mount of `bash`.
|
||||||
)",
|
)",
|
||||||
|
@ -984,4 +987,12 @@ std::vector<Path> getUserConfigFiles();
|
||||||
|
|
||||||
extern const std::string nixVersion;
|
extern const std::string nixVersion;
|
||||||
|
|
||||||
|
/* NB: This is not sufficient. You need to call initNix() */
|
||||||
|
void initLibStore();
|
||||||
|
|
||||||
|
/* It's important to initialize before doing _anything_, which is why we
|
||||||
|
call upon the programmer to handle this correctly. However, we only add
|
||||||
|
this in a key locations, so as not to litter the code. */
|
||||||
|
void assertLibStoreInitialized();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,6 +91,7 @@ void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd)
|
||||||
|
|
||||||
if (!lockFile(lockFd.get(), ltWrite, false)) {
|
if (!lockFile(lockFd.get(), ltWrite, false)) {
|
||||||
printInfo("waiting for exclusive access to the Nix store for ca drvs...");
|
printInfo("waiting for exclusive access to the Nix store for ca drvs...");
|
||||||
|
lockFile(lockFd.get(), ltNone, false); // We have acquired a shared lock; release it to prevent deadlocks
|
||||||
lockFile(lockFd.get(), ltWrite, true);
|
lockFile(lockFd.get(), ltWrite, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,6 +300,7 @@ LocalStore::LocalStore(const Params & params)
|
||||||
|
|
||||||
if (!lockFile(globalLock.get(), ltWrite, false)) {
|
if (!lockFile(globalLock.get(), ltWrite, false)) {
|
||||||
printInfo("waiting for exclusive access to the Nix store...");
|
printInfo("waiting for exclusive access to the Nix store...");
|
||||||
|
lockFile(globalLock.get(), ltNone, false); // We have acquired a shared lock; release it to prevent deadlocks
|
||||||
lockFile(globalLock.get(), ltWrite, true);
|
lockFile(globalLock.get(), ltWrite, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,9 +441,9 @@ LocalStore::~LocalStore()
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto state(_state.lock());
|
auto fdTempRoots(_fdTempRoots.lock());
|
||||||
if (state->fdTempRoots) {
|
if (*fdTempRoots) {
|
||||||
state->fdTempRoots = -1;
|
*fdTempRoots = -1;
|
||||||
unlink(fnTempRoots.c_str());
|
unlink(fnTempRoots.c_str());
|
||||||
}
|
}
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
|
|
|
@ -59,15 +59,6 @@ private:
|
||||||
struct Stmts;
|
struct Stmts;
|
||||||
std::unique_ptr<Stmts> stmts;
|
std::unique_ptr<Stmts> stmts;
|
||||||
|
|
||||||
/* The global GC lock */
|
|
||||||
AutoCloseFD fdGCLock;
|
|
||||||
|
|
||||||
/* The file to which we write our temporary roots. */
|
|
||||||
AutoCloseFD fdTempRoots;
|
|
||||||
|
|
||||||
/* Connection to the garbage collector. */
|
|
||||||
AutoCloseFD fdRootsSocket;
|
|
||||||
|
|
||||||
/* The last time we checked whether to do an auto-GC, or an
|
/* The last time we checked whether to do an auto-GC, or an
|
||||||
auto-GC finished. */
|
auto-GC finished. */
|
||||||
std::chrono::time_point<std::chrono::steady_clock> lastGCCheck;
|
std::chrono::time_point<std::chrono::steady_clock> lastGCCheck;
|
||||||
|
@ -156,6 +147,21 @@ public:
|
||||||
|
|
||||||
void addTempRoot(const StorePath & path) override;
|
void addTempRoot(const StorePath & path) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void createTempRootsFile();
|
||||||
|
|
||||||
|
/* The file to which we write our temporary roots. */
|
||||||
|
Sync<AutoCloseFD> _fdTempRoots;
|
||||||
|
|
||||||
|
/* The global GC lock. */
|
||||||
|
Sync<AutoCloseFD> _fdGCLock;
|
||||||
|
|
||||||
|
/* Connection to the garbage collector. */
|
||||||
|
Sync<AutoCloseFD> _fdRootsSocket;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
void addIndirectRoot(const Path & path) override;
|
void addIndirectRoot(const Path & path) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -13,10 +13,6 @@ ifdef HOST_LINUX
|
||||||
libstore_LDFLAGS += -ldl
|
libstore_LDFLAGS += -ldl
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifdef HOST_DARWIN
|
|
||||||
libstore_FILES = sandbox-defaults.sb sandbox-minimal.sb sandbox-network.sb
|
|
||||||
endif
|
|
||||||
|
|
||||||
$(foreach file,$(libstore_FILES),$(eval $(call install-data-in,$(d)/$(file),$(datadir)/nix/sandbox)))
|
$(foreach file,$(libstore_FILES),$(eval $(call install-data-in,$(d)/$(file),$(datadir)/nix/sandbox)))
|
||||||
|
|
||||||
ifeq ($(ENABLE_S3), 1)
|
ifeq ($(ENABLE_S3), 1)
|
||||||
|
|
|
@ -123,8 +123,12 @@ struct AutoUserLock : UserLock
|
||||||
|
|
||||||
std::vector<gid_t> getSupplementaryGIDs() override { return {}; }
|
std::vector<gid_t> getSupplementaryGIDs() override { return {}; }
|
||||||
|
|
||||||
static std::unique_ptr<UserLock> acquire(uid_t nrIds, bool useChroot)
|
static std::unique_ptr<UserLock> acquire(uid_t nrIds, bool useUserNamespace)
|
||||||
{
|
{
|
||||||
|
#if !defined(__linux__)
|
||||||
|
useUserNamespace = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
settings.requireExperimentalFeature(Xp::AutoAllocateUids);
|
settings.requireExperimentalFeature(Xp::AutoAllocateUids);
|
||||||
assert(settings.startId > 0);
|
assert(settings.startId > 0);
|
||||||
assert(settings.uidCount % maxIdsPerBuild == 0);
|
assert(settings.uidCount % maxIdsPerBuild == 0);
|
||||||
|
@ -157,7 +161,7 @@ struct AutoUserLock : UserLock
|
||||||
auto lock = std::make_unique<AutoUserLock>();
|
auto lock = std::make_unique<AutoUserLock>();
|
||||||
lock->fdUserLock = std::move(fd);
|
lock->fdUserLock = std::move(fd);
|
||||||
lock->firstUid = firstUid;
|
lock->firstUid = firstUid;
|
||||||
if (useChroot)
|
if (useUserNamespace)
|
||||||
lock->firstGid = firstUid;
|
lock->firstGid = firstUid;
|
||||||
else {
|
else {
|
||||||
struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str());
|
struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str());
|
||||||
|
@ -174,10 +178,10 @@ struct AutoUserLock : UserLock
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<UserLock> acquireUserLock(uid_t nrIds, bool useChroot)
|
std::unique_ptr<UserLock> acquireUserLock(uid_t nrIds, bool useUserNamespace)
|
||||||
{
|
{
|
||||||
if (settings.autoAllocateUids)
|
if (settings.autoAllocateUids)
|
||||||
return AutoUserLock::acquire(nrIds, useChroot);
|
return AutoUserLock::acquire(nrIds, useUserNamespace);
|
||||||
else
|
else
|
||||||
return SimpleUserLock::acquire();
|
return SimpleUserLock::acquire();
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ struct UserLock
|
||||||
|
|
||||||
/* Acquire a user lock for a UID range of size `nrIds`. Note that this
|
/* Acquire a user lock for a UID range of size `nrIds`. Note that this
|
||||||
may return nullptr if no user is available. */
|
may return nullptr if no user is available. */
|
||||||
std::unique_ptr<UserLock> acquireUserLock(uid_t nrIds, bool useChroot);
|
std::unique_ptr<UserLock> acquireUserLock(uid_t nrIds, bool useUserNamespace);
|
||||||
|
|
||||||
bool useBuildUsers();
|
bool useBuildUsers();
|
||||||
|
|
||||||
|
|
|
@ -93,4 +93,14 @@ struct RealisedPath {
|
||||||
GENERATE_CMP(RealisedPath, me->raw);
|
GENERATE_CMP(RealisedPath, me->raw);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class MissingRealisation : public Error
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MissingRealisation(DrvOutput & outputId)
|
||||||
|
: Error( "cannot operate on an output of the "
|
||||||
|
"unbuilt derivation '%s'",
|
||||||
|
outputId.to_string())
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -879,10 +879,7 @@ std::vector<BuildResult> RemoteStore::buildPathsWithResults(
|
||||||
auto realisation =
|
auto realisation =
|
||||||
queryRealisation(outputId);
|
queryRealisation(outputId);
|
||||||
if (!realisation)
|
if (!realisation)
|
||||||
throw Error(
|
throw MissingRealisation(outputId);
|
||||||
"cannot operate on an output of unbuilt "
|
|
||||||
"content-addressed derivation '%s'",
|
|
||||||
outputId.to_string());
|
|
||||||
res.builtOutputs.emplace(realisation->id, *realisation);
|
res.builtOutputs.emplace(realisation->id, *realisation);
|
||||||
} else {
|
} else {
|
||||||
// If ca-derivations isn't enabled, assume that
|
// If ca-derivations isn't enabled, assume that
|
||||||
|
|
|
@ -47,9 +47,13 @@ SQLite::SQLite(const Path & path, bool create)
|
||||||
// `unix-dotfile` is needed on NFS file systems and on Windows' Subsystem
|
// `unix-dotfile` is needed on NFS file systems and on Windows' Subsystem
|
||||||
// for Linux (WSL) where useSQLiteWAL should be false by default.
|
// for Linux (WSL) where useSQLiteWAL should be false by default.
|
||||||
const char *vfs = settings.useSQLiteWAL ? 0 : "unix-dotfile";
|
const char *vfs = settings.useSQLiteWAL ? 0 : "unix-dotfile";
|
||||||
if (sqlite3_open_v2(path.c_str(), &db,
|
int flags = SQLITE_OPEN_READWRITE;
|
||||||
SQLITE_OPEN_READWRITE | (create ? SQLITE_OPEN_CREATE : 0), vfs) != SQLITE_OK)
|
if (create) flags |= SQLITE_OPEN_CREATE;
|
||||||
throw Error("cannot open SQLite database '%s'", path);
|
int ret = sqlite3_open_v2(path.c_str(), &db, flags, vfs);
|
||||||
|
if (ret != SQLITE_OK) {
|
||||||
|
const char * err = sqlite3_errstr(ret);
|
||||||
|
throw Error("cannot open SQLite database '%s': %s", path, err);
|
||||||
|
}
|
||||||
|
|
||||||
if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK)
|
if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK)
|
||||||
SQLiteError::throw_(db, "setting timeout");
|
SQLiteError::throw_(db, "setting timeout");
|
||||||
|
|
|
@ -458,6 +458,7 @@ Store::Store(const Params & params)
|
||||||
: StoreConfig(params)
|
: StoreConfig(params)
|
||||||
, state({(size_t) pathInfoCacheSize})
|
, state({(size_t) pathInfoCacheSize})
|
||||||
{
|
{
|
||||||
|
assertLibStoreInitialized();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,9 @@ namespace nix {
|
||||||
|
|
||||||
const std::string nativeSystem = SYSTEM;
|
const std::string nativeSystem = SYSTEM;
|
||||||
|
|
||||||
void BaseError::addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint)
|
void BaseError::addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint, bool frame)
|
||||||
{
|
{
|
||||||
err.traces.push_front(Trace { .pos = std::move(e), .hint = hint });
|
err.traces.push_front(Trace { .pos = std::move(e), .hint = hint, .frame = frame });
|
||||||
}
|
}
|
||||||
|
|
||||||
// c++ std::exception descendants must have a 'const char* what()' function.
|
// c++ std::exception descendants must have a 'const char* what()' function.
|
||||||
|
@ -200,13 +200,125 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
|
||||||
|
|
||||||
auto noSource = ANSI_ITALIC " (source not available)" ANSI_NORMAL "\n";
|
auto noSource = ANSI_ITALIC " (source not available)" ANSI_NORMAL "\n";
|
||||||
|
|
||||||
// traces
|
/*
|
||||||
if (showTrace && !einfo.traces.empty()) {
|
* Traces
|
||||||
|
* ------
|
||||||
|
*
|
||||||
|
* The semantics of traces is a bit weird. We have only one option to
|
||||||
|
* print them and to make them verbose (--show-trace). In the code they
|
||||||
|
* are always collected, but they are not printed by default. The code
|
||||||
|
* also collects more traces when the option is on. This means that there
|
||||||
|
* is no way to print the simplified traces at all.
|
||||||
|
*
|
||||||
|
* I (layus) designed the code to attach positions to a restricted set of
|
||||||
|
* messages. This means that we have a lot of traces with no position at
|
||||||
|
* all, including most of the base error messages. For example "type
|
||||||
|
* error: found a string while a set was expected" has no position, but
|
||||||
|
* will come with several traces detailing it's precise relation to the
|
||||||
|
* closest know position. This makes erroring without printing traces
|
||||||
|
* quite useless.
|
||||||
|
*
|
||||||
|
* This is why I introduced the idea to always print a few traces on
|
||||||
|
* error. The number 3 is quite arbitrary, and was selected so as not to
|
||||||
|
* clutter the console on error. For the same reason, a trace with an
|
||||||
|
* error position takes more space, and counts as two traces towards the
|
||||||
|
* limit.
|
||||||
|
*
|
||||||
|
* The rest is truncated, unless --show-trace is passed. This preserves
|
||||||
|
* the same bad semantics of --show-trace to both show the trace and
|
||||||
|
* augment it with new data. Not too sure what is the best course of
|
||||||
|
* action.
|
||||||
|
*
|
||||||
|
* The issue is that it is fundamentally hard to provide a trace for a
|
||||||
|
* lazy language. The trace will only cover the current spine of the
|
||||||
|
* evaluation, missing things that have been evaluated before. For
|
||||||
|
* example, most type errors are hard to inspect because there is not
|
||||||
|
* trace for the faulty value. These errors should really print the faulty
|
||||||
|
* value itself.
|
||||||
|
*
|
||||||
|
* In function calls, the --show-trace flag triggers extra traces for each
|
||||||
|
* function invocation. These work as scopes, allowing to follow the
|
||||||
|
* current spine of the evaluation graph. Without that flag, the error
|
||||||
|
* trace should restrict itself to a restricted prefix of that trace,
|
||||||
|
* until the first scope. If we ever get to such a precise error
|
||||||
|
* reporting, there would be no need to add an arbitrary limit here. We
|
||||||
|
* could always print the full trace, and it would just be small without
|
||||||
|
* the flag.
|
||||||
|
*
|
||||||
|
* One idea I had is for XxxError.addTrace() to perform nothing if one
|
||||||
|
* scope has already been traced. Alternatively, we could stop here when
|
||||||
|
* we encounter such a scope instead of after an arbitrary number of
|
||||||
|
* traces. This however requires to augment traces with the notion of
|
||||||
|
* "scope".
|
||||||
|
*
|
||||||
|
* This is particularly visible in code like evalAttrs(...) where we have
|
||||||
|
* to make a decision between the two following options.
|
||||||
|
*
|
||||||
|
* ``` long traces
|
||||||
|
* inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, std::string_view errorCtx)
|
||||||
|
* {
|
||||||
|
* try {
|
||||||
|
* e->eval(*this, env, v);
|
||||||
|
* if (v.type() != nAttrs)
|
||||||
|
* throwTypeError("value is %1% while a set was expected", v);
|
||||||
|
* } catch (Error & e) {
|
||||||
|
* e.addTrace(pos, errorCtx);
|
||||||
|
* throw;
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ``` short traces
|
||||||
|
* inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, std::string_view errorCtx)
|
||||||
|
* {
|
||||||
|
* e->eval(*this, env, v);
|
||||||
|
* try {
|
||||||
|
* if (v.type() != nAttrs)
|
||||||
|
* throwTypeError("value is %1% while a set was expected", v);
|
||||||
|
* } catch (Error & e) {
|
||||||
|
* e.addTrace(pos, errorCtx);
|
||||||
|
* throw;
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* The second example can be rewritten more concisely, but kept in this
|
||||||
|
* form to highlight the symmetry. The first option adds more information,
|
||||||
|
* because whatever caused an error down the line, in the generic eval
|
||||||
|
* function, will get annotated with the code location that uses and
|
||||||
|
* required it. The second option is less verbose, but does not provide
|
||||||
|
* any context at all as to where and why a failing value was required.
|
||||||
|
*
|
||||||
|
* Scopes would fix that, by adding context only when --show-trace is
|
||||||
|
* passed, and keeping the trace terse otherwise.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Enough indent to align with with the `... `
|
||||||
|
// prepended to each element of the trace
|
||||||
|
auto ellipsisIndent = " ";
|
||||||
|
|
||||||
|
bool frameOnly = false;
|
||||||
|
if (!einfo.traces.empty()) {
|
||||||
|
size_t count = 0;
|
||||||
for (const auto & trace : einfo.traces) {
|
for (const auto & trace : einfo.traces) {
|
||||||
|
if (!showTrace && count > 3) {
|
||||||
|
oss << "\n" << ANSI_WARNING "(stack trace truncated; use '--show-trace' to show the full trace)" ANSI_NORMAL << "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trace.hint.str().empty()) continue;
|
||||||
|
if (frameOnly && !trace.frame) continue;
|
||||||
|
|
||||||
|
count++;
|
||||||
|
frameOnly = trace.frame;
|
||||||
|
|
||||||
oss << "\n" << "… " << trace.hint.str() << "\n";
|
oss << "\n" << "… " << trace.hint.str() << "\n";
|
||||||
|
|
||||||
if (trace.pos) {
|
if (trace.pos) {
|
||||||
oss << "\n" << ANSI_BLUE << "at " ANSI_WARNING << *trace.pos << ANSI_NORMAL << ":";
|
count++;
|
||||||
|
|
||||||
|
oss << "\n" << ellipsisIndent << ANSI_BLUE << "at " ANSI_WARNING << *trace.pos << ANSI_NORMAL << ":";
|
||||||
|
|
||||||
if (auto loc = trace.pos->getCodeLines()) {
|
if (auto loc = trace.pos->getCodeLines()) {
|
||||||
oss << "\n";
|
oss << "\n";
|
||||||
|
|
|
@ -86,6 +86,7 @@ void printCodeLines(std::ostream & out,
|
||||||
struct Trace {
|
struct Trace {
|
||||||
std::shared_ptr<AbstractPos> pos;
|
std::shared_ptr<AbstractPos> pos;
|
||||||
hintformat hint;
|
hintformat hint;
|
||||||
|
bool frame;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ErrorInfo {
|
struct ErrorInfo {
|
||||||
|
@ -114,6 +115,8 @@ protected:
|
||||||
public:
|
public:
|
||||||
unsigned int status = 1; // exit status
|
unsigned int status = 1; // exit status
|
||||||
|
|
||||||
|
BaseError(const BaseError &) = default;
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
BaseError(unsigned int status, const Args & ... args)
|
BaseError(unsigned int status, const Args & ... args)
|
||||||
: err { .level = lvlError, .msg = hintfmt(args...) }
|
: err { .level = lvlError, .msg = hintfmt(args...) }
|
||||||
|
@ -152,15 +155,22 @@ public:
|
||||||
const std::string & msg() const { return calcWhat(); }
|
const std::string & msg() const { return calcWhat(); }
|
||||||
const ErrorInfo & info() const { calcWhat(); return err; }
|
const ErrorInfo & info() const { calcWhat(); return err; }
|
||||||
|
|
||||||
template<typename... Args>
|
void pushTrace(Trace trace)
|
||||||
void addTrace(std::shared_ptr<AbstractPos> && e, const std::string & fs, const Args & ... args)
|
|
||||||
{
|
{
|
||||||
addTrace(std::move(e), hintfmt(fs, args...));
|
err.traces.push_front(trace);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint);
|
template<typename... Args>
|
||||||
|
void addTrace(std::shared_ptr<AbstractPos> && e, std::string_view fs, const Args & ... args)
|
||||||
|
{
|
||||||
|
addTrace(std::move(e), hintfmt(std::string(fs), args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
void addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint, bool frame = false);
|
||||||
|
|
||||||
bool hasTrace() const { return !err.traces.empty(); }
|
bool hasTrace() const { return !err.traces.empty(); }
|
||||||
|
|
||||||
|
const ErrorInfo & info() { return err; };
|
||||||
};
|
};
|
||||||
|
|
||||||
#define MakeError(newClass, superClass) \
|
#define MakeError(newClass, superClass) \
|
||||||
|
|
|
@ -134,9 +134,9 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
|
||||||
state.forceValue(topLevel, [&]() { return topLevel.determinePos(noPos); });
|
state.forceValue(topLevel, [&]() { return topLevel.determinePos(noPos); });
|
||||||
PathSet context;
|
PathSet context;
|
||||||
Attr & aDrvPath(*topLevel.attrs->find(state.sDrvPath));
|
Attr & aDrvPath(*topLevel.attrs->find(state.sDrvPath));
|
||||||
auto topLevelDrv = state.coerceToStorePath(aDrvPath.pos, *aDrvPath.value, context);
|
auto topLevelDrv = state.coerceToStorePath(aDrvPath.pos, *aDrvPath.value, context, "");
|
||||||
Attr & aOutPath(*topLevel.attrs->find(state.sOutPath));
|
Attr & aOutPath(*topLevel.attrs->find(state.sOutPath));
|
||||||
auto topLevelOut = state.coerceToStorePath(aOutPath.pos, *aOutPath.value, context);
|
auto topLevelOut = state.coerceToStorePath(aOutPath.pos, *aOutPath.value, context, "");
|
||||||
|
|
||||||
/* Realise the resulting store expression. */
|
/* Realise the resulting store expression. */
|
||||||
debug("building user environment");
|
debug("building user environment");
|
||||||
|
|
|
@ -97,13 +97,13 @@ struct CmdBundle : InstallableCommand
|
||||||
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
|
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
|
||||||
|
|
||||||
PathSet context2;
|
PathSet context2;
|
||||||
auto drvPath = evalState->coerceToStorePath(attr1->pos, *attr1->value, context2);
|
auto drvPath = evalState->coerceToStorePath(attr1->pos, *attr1->value, context2, "");
|
||||||
|
|
||||||
auto attr2 = vRes->attrs->get(evalState->sOutPath);
|
auto attr2 = vRes->attrs->get(evalState->sOutPath);
|
||||||
if (!attr2)
|
if (!attr2)
|
||||||
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
|
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
|
||||||
|
|
||||||
auto outPath = evalState->coerceToStorePath(attr2->pos, *attr2->value, context2);
|
auto outPath = evalState->coerceToStorePath(attr2->pos, *attr2->value, context2, "");
|
||||||
|
|
||||||
store->buildPaths({ DerivedPath::Built { drvPath } });
|
store->buildPaths({ DerivedPath::Built { drvPath } });
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ struct CmdBundle : InstallableCommand
|
||||||
auto * attr = vRes->attrs->get(evalState->sName);
|
auto * attr = vRes->attrs->get(evalState->sName);
|
||||||
if (!attr)
|
if (!attr)
|
||||||
throw Error("attribute 'name' missing");
|
throw Error("attribute 'name' missing");
|
||||||
outLink = evalState->forceStringNoCtx(*attr->value, attr->pos);
|
outLink = evalState->forceStringNoCtx(*attr->value, attr->pos, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: will crash if not a localFSStore?
|
// TODO: will crash if not a localFSStore?
|
||||||
|
|
|
@ -164,6 +164,14 @@ struct BuildEnvironment
|
||||||
{
|
{
|
||||||
return vars == other.vars && bashFunctions == other.bashFunctions;
|
return vars == other.vars && bashFunctions == other.bashFunctions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string getSystem() const
|
||||||
|
{
|
||||||
|
if (auto v = get(vars, "system"))
|
||||||
|
return getString(*v);
|
||||||
|
else
|
||||||
|
return settings.thisSystem;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const static std::string getEnvSh =
|
const static std::string getEnvSh =
|
||||||
|
@ -570,7 +578,7 @@ struct CmdDevelop : Common, MixEnvironment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
runProgramInStore(store, shell, args);
|
runProgramInStore(store, shell, args, buildEnvironment.getSystem());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -112,7 +112,7 @@ struct CmdEval : MixJSON, InstallableCommand
|
||||||
|
|
||||||
else if (raw) {
|
else if (raw) {
|
||||||
stopProgressBar();
|
stopProgressBar();
|
||||||
std::cout << *state->coerceToString(noPos, *v, context);
|
std::cout << *state->coerceToString(noPos, *v, context, "while generating the eval command output");
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (json) {
|
else if (json) {
|
||||||
|
|
|
@ -126,12 +126,12 @@ static void enumerateOutputs(EvalState & state, Value & vFlake,
|
||||||
std::function<void(const std::string & name, Value & vProvide, const PosIdx pos)> callback)
|
std::function<void(const std::string & name, Value & vProvide, const PosIdx pos)> callback)
|
||||||
{
|
{
|
||||||
auto pos = vFlake.determinePos(noPos);
|
auto pos = vFlake.determinePos(noPos);
|
||||||
state.forceAttrs(vFlake, pos);
|
state.forceAttrs(vFlake, pos, "while evaluating a flake to get its outputs");
|
||||||
|
|
||||||
auto aOutputs = vFlake.attrs->get(state.symbols.create("outputs"));
|
auto aOutputs = vFlake.attrs->get(state.symbols.create("outputs"));
|
||||||
assert(aOutputs);
|
assert(aOutputs);
|
||||||
|
|
||||||
state.forceAttrs(*aOutputs->value, pos);
|
state.forceAttrs(*aOutputs->value, pos, "while evaluating the outputs of a flake");
|
||||||
|
|
||||||
auto sHydraJobs = state.symbols.create("hydraJobs");
|
auto sHydraJobs = state.symbols.create("hydraJobs");
|
||||||
|
|
||||||
|
@ -381,23 +381,6 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
auto checkModule = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
|
auto checkModule = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
|
||||||
try {
|
try {
|
||||||
state->forceValue(v, pos);
|
state->forceValue(v, pos);
|
||||||
if (v.isLambda()) {
|
|
||||||
if (!v.lambda.fun->hasFormals() || !v.lambda.fun->formals->ellipsis)
|
|
||||||
throw Error("module must match an open attribute set ('{ config, ... }')");
|
|
||||||
} else if (v.type() == nAttrs) {
|
|
||||||
for (auto & attr : *v.attrs)
|
|
||||||
try {
|
|
||||||
state->forceValue(*attr.value, attr.pos);
|
|
||||||
} catch (Error & e) {
|
|
||||||
e.addTrace(
|
|
||||||
state->positions[attr.pos],
|
|
||||||
hintfmt("while evaluating the option '%s'", state->symbols[attr.name]));
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
throw Error("module must be a function or an attribute set");
|
|
||||||
// FIXME: if we have a 'nixpkgs' input, use it to
|
|
||||||
// check the module.
|
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace(resolve(pos), hintfmt("while checking the NixOS module '%s'", attrPath));
|
e.addTrace(resolve(pos), hintfmt("while checking the NixOS module '%s'", attrPath));
|
||||||
reportError(e);
|
reportError(e);
|
||||||
|
@ -408,13 +391,13 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
|
|
||||||
checkHydraJobs = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
|
checkHydraJobs = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
|
||||||
try {
|
try {
|
||||||
state->forceAttrs(v, pos);
|
state->forceAttrs(v, pos, "");
|
||||||
|
|
||||||
if (state->isDerivation(v))
|
if (state->isDerivation(v))
|
||||||
throw Error("jobset should not be a derivation at top-level");
|
throw Error("jobset should not be a derivation at top-level");
|
||||||
|
|
||||||
for (auto & attr : *v.attrs) {
|
for (auto & attr : *v.attrs) {
|
||||||
state->forceAttrs(*attr.value, attr.pos);
|
state->forceAttrs(*attr.value, attr.pos, "");
|
||||||
auto attrPath2 = concatStrings(attrPath, ".", state->symbols[attr.name]);
|
auto attrPath2 = concatStrings(attrPath, ".", state->symbols[attr.name]);
|
||||||
if (state->isDerivation(*attr.value)) {
|
if (state->isDerivation(*attr.value)) {
|
||||||
Activity act(*logger, lvlChatty, actUnknown,
|
Activity act(*logger, lvlChatty, actUnknown,
|
||||||
|
@ -436,7 +419,7 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
fmt("checking NixOS configuration '%s'", attrPath));
|
fmt("checking NixOS configuration '%s'", attrPath));
|
||||||
Bindings & bindings(*state->allocBindings(0));
|
Bindings & bindings(*state->allocBindings(0));
|
||||||
auto vToplevel = findAlongAttrPath(*state, "config.system.build.toplevel", bindings, v).first;
|
auto vToplevel = findAlongAttrPath(*state, "config.system.build.toplevel", bindings, v).first;
|
||||||
state->forceAttrs(*vToplevel, pos);
|
state->forceValue(*vToplevel, pos);
|
||||||
if (!state->isDerivation(*vToplevel))
|
if (!state->isDerivation(*vToplevel))
|
||||||
throw Error("attribute 'config.system.build.toplevel' is not a derivation");
|
throw Error("attribute 'config.system.build.toplevel' is not a derivation");
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
|
@ -450,12 +433,12 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
Activity act(*logger, lvlChatty, actUnknown,
|
Activity act(*logger, lvlChatty, actUnknown,
|
||||||
fmt("checking template '%s'", attrPath));
|
fmt("checking template '%s'", attrPath));
|
||||||
|
|
||||||
state->forceAttrs(v, pos);
|
state->forceAttrs(v, pos, "");
|
||||||
|
|
||||||
if (auto attr = v.attrs->get(state->symbols.create("path"))) {
|
if (auto attr = v.attrs->get(state->symbols.create("path"))) {
|
||||||
if (attr->name == state->symbols.create("path")) {
|
if (attr->name == state->symbols.create("path")) {
|
||||||
PathSet context;
|
PathSet context;
|
||||||
auto path = state->coerceToPath(attr->pos, *attr->value, context);
|
auto path = state->coerceToPath(attr->pos, *attr->value, context, "");
|
||||||
if (!store->isInStore(path))
|
if (!store->isInStore(path))
|
||||||
throw Error("template '%s' has a bad 'path' attribute");
|
throw Error("template '%s' has a bad 'path' attribute");
|
||||||
// TODO: recursively check the flake in 'path'.
|
// TODO: recursively check the flake in 'path'.
|
||||||
|
@ -464,7 +447,7 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
throw Error("template '%s' lacks attribute 'path'", attrPath);
|
throw Error("template '%s' lacks attribute 'path'", attrPath);
|
||||||
|
|
||||||
if (auto attr = v.attrs->get(state->symbols.create("description")))
|
if (auto attr = v.attrs->get(state->symbols.create("description")))
|
||||||
state->forceStringNoCtx(*attr->value, attr->pos);
|
state->forceStringNoCtx(*attr->value, attr->pos, "");
|
||||||
else
|
else
|
||||||
throw Error("template '%s' lacks attribute 'description'", attrPath);
|
throw Error("template '%s' lacks attribute 'description'", attrPath);
|
||||||
|
|
||||||
|
@ -521,11 +504,11 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
warn("flake output attribute '%s' is deprecated; use '%s' instead", name, replacement);
|
warn("flake output attribute '%s' is deprecated; use '%s' instead", name, replacement);
|
||||||
|
|
||||||
if (name == "checks") {
|
if (name == "checks") {
|
||||||
state->forceAttrs(vOutput, pos);
|
state->forceAttrs(vOutput, pos, "");
|
||||||
for (auto & attr : *vOutput.attrs) {
|
for (auto & attr : *vOutput.attrs) {
|
||||||
const auto & attr_name = state->symbols[attr.name];
|
const auto & attr_name = state->symbols[attr.name];
|
||||||
checkSystemName(attr_name, attr.pos);
|
checkSystemName(attr_name, attr.pos);
|
||||||
state->forceAttrs(*attr.value, attr.pos);
|
state->forceAttrs(*attr.value, attr.pos, "");
|
||||||
for (auto & attr2 : *attr.value->attrs) {
|
for (auto & attr2 : *attr.value->attrs) {
|
||||||
auto drvPath = checkDerivation(
|
auto drvPath = checkDerivation(
|
||||||
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
|
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
|
||||||
|
@ -537,7 +520,7 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (name == "formatter") {
|
else if (name == "formatter") {
|
||||||
state->forceAttrs(vOutput, pos);
|
state->forceAttrs(vOutput, pos, "");
|
||||||
for (auto & attr : *vOutput.attrs) {
|
for (auto & attr : *vOutput.attrs) {
|
||||||
const auto & attr_name = state->symbols[attr.name];
|
const auto & attr_name = state->symbols[attr.name];
|
||||||
checkSystemName(attr_name, attr.pos);
|
checkSystemName(attr_name, attr.pos);
|
||||||
|
@ -548,11 +531,11 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (name == "packages" || name == "devShells") {
|
else if (name == "packages" || name == "devShells") {
|
||||||
state->forceAttrs(vOutput, pos);
|
state->forceAttrs(vOutput, pos, "");
|
||||||
for (auto & attr : *vOutput.attrs) {
|
for (auto & attr : *vOutput.attrs) {
|
||||||
const auto & attr_name = state->symbols[attr.name];
|
const auto & attr_name = state->symbols[attr.name];
|
||||||
checkSystemName(attr_name, attr.pos);
|
checkSystemName(attr_name, attr.pos);
|
||||||
state->forceAttrs(*attr.value, attr.pos);
|
state->forceAttrs(*attr.value, attr.pos, "");
|
||||||
for (auto & attr2 : *attr.value->attrs)
|
for (auto & attr2 : *attr.value->attrs)
|
||||||
checkDerivation(
|
checkDerivation(
|
||||||
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
|
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
|
||||||
|
@ -561,11 +544,11 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (name == "apps") {
|
else if (name == "apps") {
|
||||||
state->forceAttrs(vOutput, pos);
|
state->forceAttrs(vOutput, pos, "");
|
||||||
for (auto & attr : *vOutput.attrs) {
|
for (auto & attr : *vOutput.attrs) {
|
||||||
const auto & attr_name = state->symbols[attr.name];
|
const auto & attr_name = state->symbols[attr.name];
|
||||||
checkSystemName(attr_name, attr.pos);
|
checkSystemName(attr_name, attr.pos);
|
||||||
state->forceAttrs(*attr.value, attr.pos);
|
state->forceAttrs(*attr.value, attr.pos, "");
|
||||||
for (auto & attr2 : *attr.value->attrs)
|
for (auto & attr2 : *attr.value->attrs)
|
||||||
checkApp(
|
checkApp(
|
||||||
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
|
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
|
||||||
|
@ -574,7 +557,7 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (name == "defaultPackage" || name == "devShell") {
|
else if (name == "defaultPackage" || name == "devShell") {
|
||||||
state->forceAttrs(vOutput, pos);
|
state->forceAttrs(vOutput, pos, "");
|
||||||
for (auto & attr : *vOutput.attrs) {
|
for (auto & attr : *vOutput.attrs) {
|
||||||
const auto & attr_name = state->symbols[attr.name];
|
const auto & attr_name = state->symbols[attr.name];
|
||||||
checkSystemName(attr_name, attr.pos);
|
checkSystemName(attr_name, attr.pos);
|
||||||
|
@ -585,7 +568,7 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (name == "defaultApp") {
|
else if (name == "defaultApp") {
|
||||||
state->forceAttrs(vOutput, pos);
|
state->forceAttrs(vOutput, pos, "");
|
||||||
for (auto & attr : *vOutput.attrs) {
|
for (auto & attr : *vOutput.attrs) {
|
||||||
const auto & attr_name = state->symbols[attr.name];
|
const auto & attr_name = state->symbols[attr.name];
|
||||||
checkSystemName(attr_name, attr.pos);
|
checkSystemName(attr_name, attr.pos);
|
||||||
|
@ -596,7 +579,7 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (name == "legacyPackages") {
|
else if (name == "legacyPackages") {
|
||||||
state->forceAttrs(vOutput, pos);
|
state->forceAttrs(vOutput, pos, "");
|
||||||
for (auto & attr : *vOutput.attrs) {
|
for (auto & attr : *vOutput.attrs) {
|
||||||
checkSystemName(state->symbols[attr.name], attr.pos);
|
checkSystemName(state->symbols[attr.name], attr.pos);
|
||||||
// FIXME: do getDerivations?
|
// FIXME: do getDerivations?
|
||||||
|
@ -607,7 +590,7 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
checkOverlay(name, vOutput, pos);
|
checkOverlay(name, vOutput, pos);
|
||||||
|
|
||||||
else if (name == "overlays") {
|
else if (name == "overlays") {
|
||||||
state->forceAttrs(vOutput, pos);
|
state->forceAttrs(vOutput, pos, "");
|
||||||
for (auto & attr : *vOutput.attrs)
|
for (auto & attr : *vOutput.attrs)
|
||||||
checkOverlay(fmt("%s.%s", name, state->symbols[attr.name]),
|
checkOverlay(fmt("%s.%s", name, state->symbols[attr.name]),
|
||||||
*attr.value, attr.pos);
|
*attr.value, attr.pos);
|
||||||
|
@ -617,14 +600,14 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
checkModule(name, vOutput, pos);
|
checkModule(name, vOutput, pos);
|
||||||
|
|
||||||
else if (name == "nixosModules") {
|
else if (name == "nixosModules") {
|
||||||
state->forceAttrs(vOutput, pos);
|
state->forceAttrs(vOutput, pos, "");
|
||||||
for (auto & attr : *vOutput.attrs)
|
for (auto & attr : *vOutput.attrs)
|
||||||
checkModule(fmt("%s.%s", name, state->symbols[attr.name]),
|
checkModule(fmt("%s.%s", name, state->symbols[attr.name]),
|
||||||
*attr.value, attr.pos);
|
*attr.value, attr.pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (name == "nixosConfigurations") {
|
else if (name == "nixosConfigurations") {
|
||||||
state->forceAttrs(vOutput, pos);
|
state->forceAttrs(vOutput, pos, "");
|
||||||
for (auto & attr : *vOutput.attrs)
|
for (auto & attr : *vOutput.attrs)
|
||||||
checkNixOSConfiguration(fmt("%s.%s", name, state->symbols[attr.name]),
|
checkNixOSConfiguration(fmt("%s.%s", name, state->symbols[attr.name]),
|
||||||
*attr.value, attr.pos);
|
*attr.value, attr.pos);
|
||||||
|
@ -637,14 +620,14 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
checkTemplate(name, vOutput, pos);
|
checkTemplate(name, vOutput, pos);
|
||||||
|
|
||||||
else if (name == "templates") {
|
else if (name == "templates") {
|
||||||
state->forceAttrs(vOutput, pos);
|
state->forceAttrs(vOutput, pos, "");
|
||||||
for (auto & attr : *vOutput.attrs)
|
for (auto & attr : *vOutput.attrs)
|
||||||
checkTemplate(fmt("%s.%s", name, state->symbols[attr.name]),
|
checkTemplate(fmt("%s.%s", name, state->symbols[attr.name]),
|
||||||
*attr.value, attr.pos);
|
*attr.value, attr.pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (name == "defaultBundler") {
|
else if (name == "defaultBundler") {
|
||||||
state->forceAttrs(vOutput, pos);
|
state->forceAttrs(vOutput, pos, "");
|
||||||
for (auto & attr : *vOutput.attrs) {
|
for (auto & attr : *vOutput.attrs) {
|
||||||
const auto & attr_name = state->symbols[attr.name];
|
const auto & attr_name = state->symbols[attr.name];
|
||||||
checkSystemName(attr_name, attr.pos);
|
checkSystemName(attr_name, attr.pos);
|
||||||
|
@ -655,11 +638,11 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (name == "bundlers") {
|
else if (name == "bundlers") {
|
||||||
state->forceAttrs(vOutput, pos);
|
state->forceAttrs(vOutput, pos, "");
|
||||||
for (auto & attr : *vOutput.attrs) {
|
for (auto & attr : *vOutput.attrs) {
|
||||||
const auto & attr_name = state->symbols[attr.name];
|
const auto & attr_name = state->symbols[attr.name];
|
||||||
checkSystemName(attr_name, attr.pos);
|
checkSystemName(attr_name, attr.pos);
|
||||||
state->forceAttrs(*attr.value, attr.pos);
|
state->forceAttrs(*attr.value, attr.pos, "");
|
||||||
for (auto & attr2 : *attr.value->attrs) {
|
for (auto & attr2 : *attr.value->attrs) {
|
||||||
checkBundler(
|
checkBundler(
|
||||||
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
|
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
|
||||||
|
|
|
@ -199,7 +199,7 @@ static void showHelp(std::vector<std::string> subcommand, MultiCommand & topleve
|
||||||
if (!attr)
|
if (!attr)
|
||||||
throw UsageError("Nix has no subcommand '%s'", concatStringsSep("", subcommand));
|
throw UsageError("Nix has no subcommand '%s'", concatStringsSep("", subcommand));
|
||||||
|
|
||||||
auto markdown = state.forceString(*attr->value);
|
auto markdown = state.forceString(*attr->value, noPos, "while evaluating the lowdown help text");
|
||||||
|
|
||||||
RunPager pager;
|
RunPager pager;
|
||||||
std::cout << renderMarkdownToTerminal(markdown) << "\n";
|
std::cout << renderMarkdownToTerminal(markdown) << "\n";
|
||||||
|
|
|
@ -115,12 +115,11 @@ the Nix store. Here are the recognised types of installables:
|
||||||
|
|
||||||
* **Store derivations**: `/nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv`
|
* **Store derivations**: `/nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv`
|
||||||
|
|
||||||
Store derivations are store paths with extension `.drv` and are a
|
By default, if you pass a [store derivation] path to a `nix` subcommand, the command will operate on the [output path]s of the derivation.
|
||||||
low-level representation of a build-time dependency graph used
|
|
||||||
internally by Nix. By default, if you pass a store derivation to a
|
[output path]: ../../glossary.md#gloss-output-path
|
||||||
`nix` subcommand, it will operate on the *output paths* of the
|
|
||||||
derivation. For example, `nix path-info` prints information about
|
For example, `nix path-info` prints information about the output paths:
|
||||||
the output paths:
|
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# nix path-info --json /nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv
|
# nix path-info --json /nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv
|
||||||
|
@ -202,9 +201,11 @@ operate are determined as follows:
|
||||||
a command like `nix shell nixpkgs#libxml2` will provide only those
|
a command like `nix shell nixpkgs#libxml2` will provide only those
|
||||||
two outputs by default.
|
two outputs by default.
|
||||||
|
|
||||||
Note that a store derivation (given by `.drv` file store path) doesn't have
|
Note that a [store derivation] (given by its `.drv` file store path) doesn't have
|
||||||
any attributes like `meta`, and thus this case doesn't apply to it.
|
any attributes like `meta`, and thus this case doesn't apply to it.
|
||||||
|
|
||||||
|
[store derivation]: ../../glossary.md#gloss-store-derivation
|
||||||
|
|
||||||
* Otherwise, Nix will use all outputs of the derivation.
|
* Otherwise, Nix will use all outputs of the derivation.
|
||||||
|
|
||||||
# Nix stores
|
# Nix stores
|
||||||
|
|
|
@ -68,7 +68,9 @@ R""(
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
* Print the path of the store derivation produced by `nixpkgs#hello`:
|
* Print the path of the [store derivation] produced by `nixpkgs#hello`:
|
||||||
|
|
||||||
|
[store derivation]: ../../glossary.md#gloss-store-derivation
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# nix path-info --derivation nixpkgs#hello
|
# nix path-info --derivation nixpkgs#hello
|
||||||
|
|
|
@ -28,17 +28,17 @@ std::string resolveMirrorUrl(EvalState & state, const std::string & url)
|
||||||
Value vMirrors;
|
Value vMirrors;
|
||||||
// FIXME: use nixpkgs flake
|
// FIXME: use nixpkgs flake
|
||||||
state.eval(state.parseExprFromString("import <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>", "."), vMirrors);
|
state.eval(state.parseExprFromString("import <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>", "."), vMirrors);
|
||||||
state.forceAttrs(vMirrors, noPos);
|
state.forceAttrs(vMirrors, noPos, "while evaluating the set of all mirrors");
|
||||||
|
|
||||||
auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName));
|
auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName));
|
||||||
if (mirrorList == vMirrors.attrs->end())
|
if (mirrorList == vMirrors.attrs->end())
|
||||||
throw Error("unknown mirror name '%s'", mirrorName);
|
throw Error("unknown mirror name '%s'", mirrorName);
|
||||||
state.forceList(*mirrorList->value, noPos);
|
state.forceList(*mirrorList->value, noPos, "while evaluating one mirror configuration");
|
||||||
|
|
||||||
if (mirrorList->value->listSize() < 1)
|
if (mirrorList->value->listSize() < 1)
|
||||||
throw Error("mirror URL '%s' did not expand to anything", url);
|
throw Error("mirror URL '%s' did not expand to anything", url);
|
||||||
|
|
||||||
std::string mirror(state.forceString(*mirrorList->value->listElems()[0]));
|
std::string mirror(state.forceString(*mirrorList->value->listElems()[0], noPos, "while evaluating the first available mirror"));
|
||||||
return mirror + (hasSuffix(mirror, "/") ? "" : "/") + s.substr(p + 1);
|
return mirror + (hasSuffix(mirror, "/") ? "" : "/") + s.substr(p + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,29 +196,29 @@ static int main_nix_prefetch_url(int argc, char * * argv)
|
||||||
Value vRoot;
|
Value vRoot;
|
||||||
state->evalFile(path, vRoot);
|
state->evalFile(path, vRoot);
|
||||||
Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first);
|
Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first);
|
||||||
state->forceAttrs(v, noPos);
|
state->forceAttrs(v, noPos, "while evaluating the source attribute to prefetch");
|
||||||
|
|
||||||
/* Extract the URL. */
|
/* Extract the URL. */
|
||||||
auto * attr = v.attrs->get(state->symbols.create("urls"));
|
auto * attr = v.attrs->get(state->symbols.create("urls"));
|
||||||
if (!attr)
|
if (!attr)
|
||||||
throw Error("attribute 'urls' missing");
|
throw Error("attribute 'urls' missing");
|
||||||
state->forceList(*attr->value, noPos);
|
state->forceList(*attr->value, noPos, "while evaluating the urls to prefetch");
|
||||||
if (attr->value->listSize() < 1)
|
if (attr->value->listSize() < 1)
|
||||||
throw Error("'urls' list is empty");
|
throw Error("'urls' list is empty");
|
||||||
url = state->forceString(*attr->value->listElems()[0]);
|
url = state->forceString(*attr->value->listElems()[0], noPos, "while evaluating the first url from the urls list");
|
||||||
|
|
||||||
/* Extract the hash mode. */
|
/* Extract the hash mode. */
|
||||||
auto attr2 = v.attrs->get(state->symbols.create("outputHashMode"));
|
auto attr2 = v.attrs->get(state->symbols.create("outputHashMode"));
|
||||||
if (!attr2)
|
if (!attr2)
|
||||||
printInfo("warning: this does not look like a fetchurl call");
|
printInfo("warning: this does not look like a fetchurl call");
|
||||||
else
|
else
|
||||||
unpack = state->forceString(*attr2->value) == "recursive";
|
unpack = state->forceString(*attr2->value, noPos, "while evaluating the outputHashMode of the source to prefetch") == "recursive";
|
||||||
|
|
||||||
/* Extract the name. */
|
/* Extract the name. */
|
||||||
if (!name) {
|
if (!name) {
|
||||||
auto attr3 = v.attrs->get(state->symbols.create("name"));
|
auto attr3 = v.attrs->get(state->symbols.create("name"));
|
||||||
if (!attr3)
|
if (!attr3)
|
||||||
name = state->forceString(*attr3->value);
|
name = state->forceString(*attr3->value, noPos, "while evaluating the name of the source to prefetch");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "fs-accessor.hh"
|
#include "fs-accessor.hh"
|
||||||
#include "progress-bar.hh"
|
#include "progress-bar.hh"
|
||||||
#include "eval.hh"
|
#include "eval.hh"
|
||||||
|
#include "build/personality.hh"
|
||||||
|
|
||||||
#if __linux__
|
#if __linux__
|
||||||
#include <sys/mount.h>
|
#include <sys/mount.h>
|
||||||
|
@ -24,7 +25,8 @@ namespace nix {
|
||||||
|
|
||||||
void runProgramInStore(ref<Store> store,
|
void runProgramInStore(ref<Store> store,
|
||||||
const std::string & program,
|
const std::string & program,
|
||||||
const Strings & args)
|
const Strings & args,
|
||||||
|
std::optional<std::string_view> system)
|
||||||
{
|
{
|
||||||
stopProgressBar();
|
stopProgressBar();
|
||||||
|
|
||||||
|
@ -44,7 +46,7 @@ void runProgramInStore(ref<Store> store,
|
||||||
throw Error("store '%s' is not a local store so it does not support command execution", store->getUri());
|
throw Error("store '%s' is not a local store so it does not support command execution", store->getUri());
|
||||||
|
|
||||||
if (store->storeDir != store2->getRealStoreDir()) {
|
if (store->storeDir != store2->getRealStoreDir()) {
|
||||||
Strings helperArgs = { chrootHelperName, store->storeDir, store2->getRealStoreDir(), program };
|
Strings helperArgs = { chrootHelperName, store->storeDir, store2->getRealStoreDir(), std::string(system.value_or("")), program };
|
||||||
for (auto & arg : args) helperArgs.push_back(arg);
|
for (auto & arg : args) helperArgs.push_back(arg);
|
||||||
|
|
||||||
execv(getSelfExe().value_or("nix").c_str(), stringsToCharPtrs(helperArgs).data());
|
execv(getSelfExe().value_or("nix").c_str(), stringsToCharPtrs(helperArgs).data());
|
||||||
|
@ -52,6 +54,9 @@ void runProgramInStore(ref<Store> store,
|
||||||
throw SysError("could not execute chroot helper");
|
throw SysError("could not execute chroot helper");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (system)
|
||||||
|
setPersonality(*system);
|
||||||
|
|
||||||
execvp(program.c_str(), stringsToCharPtrs(args).data());
|
execvp(program.c_str(), stringsToCharPtrs(args).data());
|
||||||
|
|
||||||
throw SysError("unable to execute '%s'", program);
|
throw SysError("unable to execute '%s'", program);
|
||||||
|
@ -241,6 +246,7 @@ void chrootHelper(int argc, char * * argv)
|
||||||
int p = 1;
|
int p = 1;
|
||||||
std::string storeDir = argv[p++];
|
std::string storeDir = argv[p++];
|
||||||
std::string realStoreDir = argv[p++];
|
std::string realStoreDir = argv[p++];
|
||||||
|
std::string system = argv[p++];
|
||||||
std::string cmd = argv[p++];
|
std::string cmd = argv[p++];
|
||||||
Strings args;
|
Strings args;
|
||||||
while (p < argc)
|
while (p < argc)
|
||||||
|
@ -304,6 +310,9 @@ void chrootHelper(int argc, char * * argv)
|
||||||
writeFile("/proc/self/uid_map", fmt("%d %d %d", uid, uid, 1));
|
writeFile("/proc/self/uid_map", fmt("%d %d %d", uid, uid, 1));
|
||||||
writeFile("/proc/self/gid_map", fmt("%d %d %d", gid, gid, 1));
|
writeFile("/proc/self/gid_map", fmt("%d %d %d", gid, gid, 1));
|
||||||
|
|
||||||
|
if (system != "")
|
||||||
|
setPersonality(system);
|
||||||
|
|
||||||
execvp(cmd.c_str(), stringsToCharPtrs(args).data());
|
execvp(cmd.c_str(), stringsToCharPtrs(args).data());
|
||||||
|
|
||||||
throw SysError("unable to exec '%s'", cmd);
|
throw SysError("unable to exec '%s'", cmd);
|
||||||
|
|
|
@ -6,6 +6,7 @@ namespace nix {
|
||||||
|
|
||||||
void runProgramInStore(ref<Store> store,
|
void runProgramInStore(ref<Store> store,
|
||||||
const std::string & program,
|
const std::string & program,
|
||||||
const Strings & args);
|
const Strings & args,
|
||||||
|
std::optional<std::string_view> system = std::nullopt);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,11 @@ R""(
|
||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
|
|
||||||
* Show the store derivation that results from evaluating the Hello
|
* Show the [store derivation] that results from evaluating the Hello
|
||||||
package:
|
package:
|
||||||
|
|
||||||
|
[store derivation]: ../../glossary.md#gloss-store-derivation
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# nix show-derivation nixpkgs#hello
|
# nix show-derivation nixpkgs#hello
|
||||||
{
|
{
|
||||||
|
@ -37,7 +39,7 @@ R""(
|
||||||
# Description
|
# Description
|
||||||
|
|
||||||
This command prints on standard output a JSON representation of the
|
This command prints on standard output a JSON representation of the
|
||||||
store derivations to which *installables* evaluate. Store derivations
|
[store derivation]s to which *installables* evaluate. Store derivations
|
||||||
are used internally by Nix. They are store paths with extension `.drv`
|
are used internally by Nix. They are store paths with extension `.drv`
|
||||||
that represent the build-time dependency graph to which a Nix
|
that represent the build-time dependency graph to which a Nix
|
||||||
expression evaluates.
|
expression evaluates.
|
||||||
|
|
|
@ -18,7 +18,9 @@ R""(
|
||||||
(The flag `--substituters ''` avoids querying
|
(The flag `--substituters ''` avoids querying
|
||||||
`https://cache.nixos.org` for the log.)
|
`https://cache.nixos.org` for the log.)
|
||||||
|
|
||||||
* To copy the log for a specific store derivation via SSH:
|
* To copy the log for a specific [store derivation] via SSH:
|
||||||
|
|
||||||
|
[store derivation]: ../../glossary.md#gloss-store-derivation
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# nix store copy-log --to ssh-ng://machine /nix/store/ilgm50plpmcgjhcp33z6n4qbnpqfhxym-glibc-2.33-59.drv
|
# nix store copy-log --to ssh-ng://machine /nix/store/ilgm50plpmcgjhcp33z6n4qbnpqfhxym-glibc-2.33-59.drv
|
||||||
|
|
|
@ -144,7 +144,7 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
|
||||||
Bindings & bindings(*state->allocBindings(0));
|
Bindings & bindings(*state->allocBindings(0));
|
||||||
auto v2 = findAlongAttrPath(*state, settings.thisSystem, bindings, *v).first;
|
auto v2 = findAlongAttrPath(*state, settings.thisSystem, bindings, *v).first;
|
||||||
|
|
||||||
return store->parseStorePath(state->forceString(*v2));
|
return store->parseStorePath(state->forceString(*v2, noPos, "while evaluating the path tho latest nix version"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -95,23 +95,13 @@ struct CmdWhyDepends : SourceExprCommand
|
||||||
* to build.
|
* to build.
|
||||||
*/
|
*/
|
||||||
auto dependency = parseInstallable(store, _dependency);
|
auto dependency = parseInstallable(store, _dependency);
|
||||||
auto derivedDependency = dependency->toDerivedPath();
|
auto optDependencyPath = [&]() -> std::optional<StorePath> {
|
||||||
auto optDependencyPath = std::visit(overloaded {
|
try {
|
||||||
[](const DerivedPath::Opaque & nodrv) -> std::optional<StorePath> {
|
return {Installable::toStorePath(getEvalStore(), store, Realise::Derivation, operateOn, dependency)};
|
||||||
return { nodrv.path };
|
} catch (MissingRealisation &) {
|
||||||
},
|
return std::nullopt;
|
||||||
[&](const DerivedPath::Built & hasdrv) -> std::optional<StorePath> {
|
}
|
||||||
if (hasdrv.outputs.size() != 1) {
|
}();
|
||||||
throw Error("argument '%s' should evaluate to one store path", dependency->what());
|
|
||||||
}
|
|
||||||
auto outputMap = store->queryPartialDerivationOutputMap(hasdrv.drvPath);
|
|
||||||
auto maybePath = outputMap.find(*hasdrv.outputs.begin());
|
|
||||||
if (maybePath == outputMap.end()) {
|
|
||||||
throw Error("unexpected end of iterator");
|
|
||||||
}
|
|
||||||
return maybePath->second;
|
|
||||||
},
|
|
||||||
}, derivedDependency.raw());
|
|
||||||
|
|
||||||
StorePathSet closure;
|
StorePathSet closure;
|
||||||
store->computeFSClosure({packagePath}, closure, false, false);
|
store->computeFSClosure({packagePath}, closure, false, false);
|
||||||
|
|
|
@ -20,7 +20,7 @@ makeTest ({
|
||||||
(import ./systemd-nspawn.nix { inherit nixpkgs; }).toplevel
|
(import ./systemd-nspawn.nix { inherit nixpkgs; }).toplevel
|
||||||
];
|
];
|
||||||
virtualisation.memorySize = 4096;
|
virtualisation.memorySize = 4096;
|
||||||
nix.binaryCaches = lib.mkForce [ ];
|
nix.settings.substituters = lib.mkForce [ ];
|
||||||
nix.extraOptions =
|
nix.extraOptions =
|
||||||
''
|
''
|
||||||
extra-experimental-features = nix-command auto-allocate-uids cgroups
|
extra-experimental-features = nix-command auto-allocate-uids cgroups
|
||||||
|
|
|
@ -41,9 +41,9 @@ nix flake check $flakeDir
|
||||||
cat > $flakeDir/flake.nix <<EOF
|
cat > $flakeDir/flake.nix <<EOF
|
||||||
{
|
{
|
||||||
outputs = { self }: {
|
outputs = { self }: {
|
||||||
nixosModules.foo = {
|
nixosModules.foo = assert false; {
|
||||||
a.b.c = 123;
|
a.b.c = 123;
|
||||||
foo = assert false; true;
|
foo = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -63,18 +63,6 @@ EOF
|
||||||
|
|
||||||
nix flake check $flakeDir
|
nix flake check $flakeDir
|
||||||
|
|
||||||
cat > $flakeDir/flake.nix <<EOF
|
|
||||||
{
|
|
||||||
outputs = { self }: {
|
|
||||||
nixosModule = { config, pkgs }: {
|
|
||||||
a.b.c = 123;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
(! nix flake check $flakeDir)
|
|
||||||
|
|
||||||
cat > $flakeDir/flake.nix <<EOF
|
cat > $flakeDir/flake.nix <<EOF
|
||||||
{
|
{
|
||||||
outputs = { self }: {
|
outputs = { self }: {
|
||||||
|
|
|
@ -149,7 +149,7 @@ makeTest (
|
||||||
virtualisation.diskSize = 2048;
|
virtualisation.diskSize = 2048;
|
||||||
virtualisation.additionalPaths = [ pkgs.hello pkgs.fuse ];
|
virtualisation.additionalPaths = [ pkgs.hello pkgs.fuse ];
|
||||||
virtualisation.memorySize = 4096;
|
virtualisation.memorySize = 4096;
|
||||||
nix.binaryCaches = lib.mkForce [ ];
|
nix.settings.substituters = lib.mkForce [ ];
|
||||||
nix.extraOptions = "experimental-features = nix-command flakes";
|
nix.extraOptions = "experimental-features = nix-command flakes";
|
||||||
networking.hosts.${(builtins.head nodes.github.config.networking.interfaces.eth1.ipv4.addresses).address} =
|
networking.hosts.${(builtins.head nodes.github.config.networking.interfaces.eth1.ipv4.addresses).address} =
|
||||||
[ "channels.nixos.org" "api.github.com" "github.com" ];
|
[ "channels.nixos.org" "api.github.com" "github.com" ];
|
||||||
|
|
|
@ -110,7 +110,7 @@ let
|
||||||
And finally to interpret \n etc. as in a string: ''\n, ''\r, ''\t.
|
And finally to interpret \n etc. as in a string: ''\n, ''\r, ''\t.
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# Regression test: antiquotation in '${x}' should work, but didn't.
|
# Regression test: string interpolation in '${x}' should work, but didn't.
|
||||||
s15 = let x = "bla"; in ''
|
s15 = let x = "bla"; in ''
|
||||||
foo
|
foo
|
||||||
'${x}'
|
'${x}'
|
||||||
|
|
1
tests/lang/eval-okay-intersectAttrs.exp
Normal file
1
tests/lang/eval-okay-intersectAttrs.exp
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[ { } { a = 1; } { a = 1; } { a = "a"; } { m = 1; } { m = "m"; } { n = 1; } { n = "n"; } { n = 1; p = 2; } { n = "n"; p = "p"; } { n = 1; p = 2; } { n = "n"; p = "p"; } { a = "a"; b = "b"; c = "c"; d = "d"; e = "e"; f = "f"; g = "g"; h = "h"; i = "i"; j = "j"; k = "k"; l = "l"; m = "m"; n = "n"; o = "o"; p = "p"; q = "q"; r = "r"; s = "s"; t = "t"; u = "u"; v = "v"; w = "w"; x = "x"; y = "y"; z = "z"; } true ]
|
50
tests/lang/eval-okay-intersectAttrs.nix
Normal file
50
tests/lang/eval-okay-intersectAttrs.nix
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
let
|
||||||
|
alphabet =
|
||||||
|
{ a = "a";
|
||||||
|
b = "b";
|
||||||
|
c = "c";
|
||||||
|
d = "d";
|
||||||
|
e = "e";
|
||||||
|
f = "f";
|
||||||
|
g = "g";
|
||||||
|
h = "h";
|
||||||
|
i = "i";
|
||||||
|
j = "j";
|
||||||
|
k = "k";
|
||||||
|
l = "l";
|
||||||
|
m = "m";
|
||||||
|
n = "n";
|
||||||
|
o = "o";
|
||||||
|
p = "p";
|
||||||
|
q = "q";
|
||||||
|
r = "r";
|
||||||
|
s = "s";
|
||||||
|
t = "t";
|
||||||
|
u = "u";
|
||||||
|
v = "v";
|
||||||
|
w = "w";
|
||||||
|
x = "x";
|
||||||
|
y = "y";
|
||||||
|
z = "z";
|
||||||
|
};
|
||||||
|
foo = {
|
||||||
|
inherit (alphabet) f o b a r z q u x;
|
||||||
|
aa = throw "aa";
|
||||||
|
};
|
||||||
|
alphabetFail = builtins.mapAttrs throw alphabet;
|
||||||
|
in
|
||||||
|
[ (builtins.intersectAttrs { a = abort "l1"; } { b = abort "r1"; })
|
||||||
|
(builtins.intersectAttrs { a = abort "l2"; } { a = 1; })
|
||||||
|
(builtins.intersectAttrs alphabetFail { a = 1; })
|
||||||
|
(builtins.intersectAttrs { a = abort "laa"; } alphabet)
|
||||||
|
(builtins.intersectAttrs alphabetFail { m = 1; })
|
||||||
|
(builtins.intersectAttrs { m = abort "lam"; } alphabet)
|
||||||
|
(builtins.intersectAttrs alphabetFail { n = 1; })
|
||||||
|
(builtins.intersectAttrs { n = abort "lan"; } alphabet)
|
||||||
|
(builtins.intersectAttrs alphabetFail { n = 1; p = 2; })
|
||||||
|
(builtins.intersectAttrs { n = abort "lan2"; p = abort "lap"; } alphabet)
|
||||||
|
(builtins.intersectAttrs alphabetFail { n = 1; p = 2; })
|
||||||
|
(builtins.intersectAttrs { n = abort "lan2"; p = abort "lap"; } alphabet)
|
||||||
|
(builtins.intersectAttrs alphabetFail alphabet)
|
||||||
|
(builtins.intersectAttrs alphabet foo == builtins.intersectAttrs foo alphabet)
|
||||||
|
]
|
|
@ -92,6 +92,7 @@ nix_tests = \
|
||||||
fmt.sh \
|
fmt.sh \
|
||||||
eval-store.sh \
|
eval-store.sh \
|
||||||
why-depends.sh \
|
why-depends.sh \
|
||||||
|
ca/why-depends.sh \
|
||||||
import-derivation.sh \
|
import-derivation.sh \
|
||||||
ca/import-derivation.sh \
|
ca/import-derivation.sh \
|
||||||
nix_path.sh \
|
nix_path.sh \
|
||||||
|
@ -134,8 +135,6 @@ endif
|
||||||
|
|
||||||
install-tests += $(foreach x, $(nix_tests), tests/$(x))
|
install-tests += $(foreach x, $(nix_tests), tests/$(x))
|
||||||
|
|
||||||
tests-environment = NIX_REMOTE= $(bash) -e
|
|
||||||
|
|
||||||
clean-files += $(d)/common.sh $(d)/config.nix $(d)/ca/config.nix
|
clean-files += $(d)/common.sh $(d)/config.nix $(d)/ca/config.nix
|
||||||
|
|
||||||
test-deps += tests/common.sh tests/config.nix tests/ca/config.nix
|
test-deps += tests/common.sh tests/config.nix tests/ca/config.nix
|
||||||
|
|
|
@ -15,7 +15,7 @@ makeTest (let pkgA = pkgs.cowsay; pkgB = pkgs.wget; pkgC = pkgs.hello; pkgD = pk
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
{ virtualisation.writableStore = true;
|
{ virtualisation.writableStore = true;
|
||||||
virtualisation.additionalPaths = [ pkgA pkgD.drvPath ];
|
virtualisation.additionalPaths = [ pkgA pkgD.drvPath ];
|
||||||
nix.binaryCaches = lib.mkForce [ ];
|
nix.settings.substituters = lib.mkForce [ ];
|
||||||
};
|
};
|
||||||
|
|
||||||
server =
|
server =
|
||||||
|
|
|
@ -98,9 +98,9 @@ rec {
|
||||||
{ address = "192.168.0.10"; prefixLength = 24; }
|
{ address = "192.168.0.10"; prefixLength = 24; }
|
||||||
];
|
];
|
||||||
|
|
||||||
nix.sandboxPaths = lib.mkForce [];
|
nix.settings.extra-sandbox-paths = lib.mkForce [];
|
||||||
nix.binaryCaches = lib.mkForce [];
|
nix.settings.substituters = lib.mkForce [];
|
||||||
nix.useSandbox = lib.mkForce true;
|
nix.settings.sandbox = lib.mkForce true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ let
|
||||||
{ config, pkgs, ... }:
|
{ config, pkgs, ... }:
|
||||||
{ services.openssh.enable = true;
|
{ services.openssh.enable = true;
|
||||||
virtualisation.writableStore = true;
|
virtualisation.writableStore = true;
|
||||||
nix.useSandbox = true;
|
nix.settings.sandbox = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
# Trivial Nix expression to build remotely.
|
# Trivial Nix expression to build remotely.
|
||||||
|
@ -44,7 +44,7 @@ in
|
||||||
|
|
||||||
client =
|
client =
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
{ nix.maxJobs = 0; # force remote building
|
{ nix.settings.max-jobs = 0; # force remote building
|
||||||
nix.distributedBuilds = true;
|
nix.distributedBuilds = true;
|
||||||
nix.buildMachines =
|
nix.buildMachines =
|
||||||
[ { hostName = "builder1";
|
[ { hostName = "builder1";
|
||||||
|
@ -62,7 +62,7 @@ in
|
||||||
];
|
];
|
||||||
virtualisation.writableStore = true;
|
virtualisation.writableStore = true;
|
||||||
virtualisation.additionalPaths = [ config.system.build.extraUtils ];
|
virtualisation.additionalPaths = [ config.system.build.extraUtils ];
|
||||||
nix.binaryCaches = lib.mkForce [ ];
|
nix.settings.substituters = lib.mkForce [ ];
|
||||||
programs.ssh.extraConfig = "ConnectTimeout 30";
|
programs.ssh.extraConfig = "ConnectTimeout 30";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,7 +13,7 @@ makeTest {
|
||||||
nodes.machine =
|
nodes.machine =
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
{ virtualisation.writableStore = true;
|
{ virtualisation.writableStore = true;
|
||||||
nix.binaryCaches = lib.mkForce [ ];
|
nix.settings.substituters = lib.mkForce [ ];
|
||||||
nix.nixPath = [ "nixpkgs=${lib.cleanSource pkgs.path}" ];
|
nix.nixPath = [ "nixpkgs=${lib.cleanSource pkgs.path}" ];
|
||||||
virtualisation.additionalPaths = [ pkgs.stdenv pkgs.pkgsi686Linux.stdenv ];
|
virtualisation.additionalPaths = [ pkgs.stdenv pkgs.pkgsi686Linux.stdenv ];
|
||||||
};
|
};
|
||||||
|
|
|
@ -108,7 +108,7 @@ makeTest (
|
||||||
virtualisation.diskSize = 2048;
|
virtualisation.diskSize = 2048;
|
||||||
virtualisation.additionalPaths = [ pkgs.hello pkgs.fuse ];
|
virtualisation.additionalPaths = [ pkgs.hello pkgs.fuse ];
|
||||||
virtualisation.memorySize = 4096;
|
virtualisation.memorySize = 4096;
|
||||||
nix.binaryCaches = lib.mkForce [ ];
|
nix.settings.substituters = lib.mkForce [ ];
|
||||||
nix.extraOptions = ''
|
nix.extraOptions = ''
|
||||||
experimental-features = nix-command flakes
|
experimental-features = nix-command flakes
|
||||||
flake-registry = https://git.sr.ht/~NixOS/flake-registry/blob/master/flake-registry.json
|
flake-registry = https://git.sr.ht/~NixOS/flake-registry/blob/master/flake-registry.json
|
||||||
|
|
|
@ -6,6 +6,9 @@ cp ./dependencies.nix ./dependencies.builder0.sh ./config.nix $TEST_HOME
|
||||||
|
|
||||||
cd $TEST_HOME
|
cd $TEST_HOME
|
||||||
|
|
||||||
|
nix why-depends --derivation --file ./dependencies.nix input2_drv input1_drv
|
||||||
|
nix why-depends --file ./dependencies.nix input2_drv input1_drv
|
||||||
|
|
||||||
nix-build ./dependencies.nix -A input0_drv -o dep
|
nix-build ./dependencies.nix -A input0_drv -o dep
|
||||||
nix-build ./dependencies.nix -o toplevel
|
nix-build ./dependencies.nix -o toplevel
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue