Merge remote-tracking branch 'origin/master' into p/flake-update

This commit is contained in:
Eelco Dolstra 2023-09-19 13:33:56 +02:00
commit c451b48993
54 changed files with 1204 additions and 508 deletions

View file

@ -10,24 +10,6 @@
<!-- Large change: Provide instructions to reviewers how to read the diff. --> <!-- Large change: Provide instructions to reviewers how to read the diff. -->
# Checklist for maintainers
<!-- Contributors: please leave this as is -->
Maintainers: tick if completed or explain if not relevant
- [ ] agreed on idea
- [ ] agreed on implementation strategy
- [ ] tests, as appropriate
- functional tests - `tests/**.sh`
- unit tests - `src/*/tests`
- integration tests - `tests/nixos/*`
- [ ] documentation in the manual
- [ ] documentation in the internal API docs
- [ ] code and comments are self-explanatory
- [ ] commit message explains why the change was made
- [ ] new feature or incompatible change: updated release notes
# Priorities # Priorities
Add :+1: to [pull requests you find important](https://github.com/NixOS/nix/pulls?q=is%3Aopen+sort%3Areactions-%2B1-desc). Add :+1: to [pull requests you find important](https://github.com/NixOS/nix/pulls?q=is%3Aopen+sort%3Areactions-%2B1-desc).

View file

@ -14,7 +14,7 @@ jobs:
if: github.repository_owner == 'NixOS' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name)) if: github.repository_owner == 'NixOS' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name))
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
# required to find all branches # required to find all branches

View file

@ -17,10 +17,10 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
timeout-minutes: 60 timeout-minutes: 60
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v23
with: with:
# The sandbox would otherwise be disabled by default on Darwin # The sandbox would otherwise be disabled by default on Darwin
extra_nix_config: "sandbox = true" extra_nix_config: "sandbox = true"
@ -58,11 +58,11 @@ jobs:
outputs: outputs:
installerURL: ${{ steps.prepare-installer.outputs.installerURL }} installerURL: ${{ steps.prepare-installer.outputs.installerURL }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v23
with: with:
install_url: https://releases.nixos.org/nix/nix-2.13.3/install install_url: https://releases.nixos.org/nix/nix-2.13.3/install
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@ -82,9 +82,9 @@ jobs:
os: [ubuntu-latest, macos-latest] os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v23
with: with:
install_url: '${{needs.installer.outputs.installerURL}}' install_url: '${{needs.installer.outputs.installerURL}}'
install_options: "--tarball-url-prefix https://${{ env.CACHIX_NAME }}.cachix.org/serve" install_options: "--tarball-url-prefix https://${{ env.CACHIX_NAME }}.cachix.org/serve"
@ -108,10 +108,10 @@ jobs:
needs.check_secrets.outputs.docker == 'true' needs.check_secrets.outputs.docker == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v23
with: with:
install_url: https://releases.nixos.org/nix/nix-2.13.3/install install_url: https://releases.nixos.org/nix/nix-2.13.3/install
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV

View file

@ -13,7 +13,7 @@ jobs:
if: github.repository_owner == 'NixOS' if: github.repository_owner == 'NixOS'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- run: bash scripts/check-hydra-status.sh - run: bash scripts/check-hydra-status.sh

View file

@ -30,8 +30,8 @@ Check out the [security policy](https://github.com/NixOS/nix/security/policy).
2. Search for related issues that cover what you're going to work on. It could help to mention there that you will work on the issue. 2. Search for related issues that cover what you're going to work on. It could help to mention there that you will work on the issue.
Issues labeled ["good first issue"](https://github.com/NixOS/nix/labels/good-first-issue) should be relatively easy to fix and are likely to get merged quickly. Issues labeled [good first issue](https://github.com/NixOS/nix/labels/good-first-issue) should be relatively easy to fix and are likely to get merged quickly.
Pull requests addressing issues labeled ["idea approved"](https://github.com/NixOS/nix/labels/idea%20approved) are especially welcomed by maintainers and will receive prioritised review. Pull requests addressing issues labeled [idea approved](https://github.com/NixOS/nix/labels/idea%20approved) are especially welcomed by maintainers and will receive prioritised review.
3. Check the [Nix reference manual](https://nixos.org/manual/nix/unstable/contributing/hacking.html) for information on building Nix and running its tests. 3. Check the [Nix reference manual](https://nixos.org/manual/nix/unstable/contributing/hacking.html) for information on building Nix and running its tests.
@ -40,14 +40,27 @@ Check out the [security policy](https://github.com/NixOS/nix/security/policy).
4. Make your changes! 4. Make your changes!
5. [Create a pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) for your changes. 5. [Create a pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) for your changes.
* [Mark the pull request as draft](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-stage-of-a-pull-request) if you're not done with the changes.
* Make sure to have [a clean history of commits on your branch by using rebase](https://www.digitalocean.com/community/tutorials/how-to-rebase-and-update-a-pull-request).
* Link related issues in your pull request to inform interested parties and future contributors about your change. * Link related issues in your pull request to inform interested parties and future contributors about your change.
* Make sure to have [a clean history of commits on your branch by using rebase](https://www.digitalocean.com/community/tutorials/how-to-rebase-and-update-a-pull-request).
If your pull request closes one or multiple issues, note that in the description using `Closes: #<number>`, as it will then happen automatically when your change is merged. If your pull request closes one or multiple issues, note that in the description using `Closes: #<number>`, as it will then happen automatically when your change is merged.
* [Mark the pull request as draft](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-stage-of-a-pull-request) if you're not done with the changes.
6. Do not expect your pull request to be reviewed immediately. 6. Do not expect your pull request to be reviewed immediately.
Nix maintainers follow a [structured process for reviews and design decisions](https://github.com/NixOS/nix/tree/master/maintainers#project-board-protocol), which may or may not prioritise your work. Nix maintainers follow a [structured process for reviews and design decisions](https://github.com/NixOS/nix/tree/master/maintainers#project-board-protocol), which may or may not prioritise your work.
Following this checklist will make the process smoother for everyone:
- [ ] Fixes an [idea approved](https://github.com/NixOS/nix/labels/idea%20approved) issue
- [ ] Tests, as appropriate:
- Functional tests [`tests/**.sh`](./tests)
- Unit tests [`src/*/tests`](./src/)
- Integration tests [`tests/nixos/*`](./tests/nixos)
- [ ] User documentation in the [manual](..doc/manual/src)
- [ ] API documentation in header files
- [ ] Code and comments are self-explanatory
- [ ] Commit message explains **why** the change was made
- [ ] New feature or incompatible change: updated [release notes](./doc/manual/src/release-notes/rl-next.md)
7. If you need additional feedback or help to getting pull request into shape, ask other contributors using [@mentions](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#mentioning-people-and-teams). 7. If you need additional feedback or help to getting pull request into shape, ask other contributors using [@mentions](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#mentioning-people-and-teams).
## Making changes to the Nix manual ## Making changes to the Nix manual

View file

@ -98,7 +98,7 @@ let
(option ? labels) (option ? labels)
(concatStringsSep " " (map (s: "*${s}*") option.labels)); (concatStringsSep " " (map (s: "*${s}*") option.labels));
in trim '' in trim ''
- `--${name}` ${shortName} ${labels} - <span id="opt-${name}">[`--${name}`](#opt-${name})</span> ${shortName} ${labels}
${option.description} ${option.description}
''; '';

View file

@ -100,6 +100,7 @@
- [File System Object](architecture/file-system-object.md) - [File System Object](architecture/file-system-object.md)
- [Protocols](protocols/protocols.md) - [Protocols](protocols/protocols.md)
- [Serving Tarball Flakes](protocols/tarball-fetcher.md) - [Serving Tarball Flakes](protocols/tarball-fetcher.md)
- [Derivation "ATerm" file format](protocols/derivation-aterm.md)
- [Glossary](glossary.md) - [Glossary](glossary.md)
- [Contributing](contributing/contributing.md) - [Contributing](contributing/contributing.md)
- [Hacking](contributing/hacking.md) - [Hacking](contributing/hacking.md)

View file

@ -30,7 +30,7 @@ a number of possible ways:
derivation with the highest *priority* is used. A derivation can derivation with the highest *priority* is used. A derivation can
define a priority by declaring the `meta.priority` attribute. This define a priority by declaring the `meta.priority` attribute. This
attribute should be a number, with a higher value denoting a lower attribute should be a number, with a higher value denoting a lower
priority. The default priority is `0`. priority. The default priority is `5`.
If there are multiple matching derivations with the same priority, If there are multiple matching derivations with the same priority,
then the derivation with the highest version will be installed. then the derivation with the highest version will be installed.

View file

@ -5,8 +5,8 @@
# Synopsis # Synopsis
`nix-store` {`--query` | `-q`} `nix-store` {`--query` | `-q`}
{`--outputs` | `--requisites` | `-R` | `--references` | {`--outputs` | `--requisites` | `-R` | `--references` | `--referrers` |
`--referrers` | `--referrers-closure` | `--deriver` | `-d` | `--referrers-closure` | `--deriver` | `-d` | `--valid-derivers` |
`--graph` | `--tree` | `--binding` *name* | `-b` *name* | `--hash` | `--graph` | `--tree` | `--binding` *name* | `-b` *name* | `--hash` |
`--size` | `--roots`} `--size` | `--roots`}
[`--use-output`] [`-u`] [`--force-realise`] [`-f`] [`--use-output`] [`-u`] [`--force-realise`] [`-f`]
@ -82,13 +82,21 @@ symlink.
in the Nix store that are dependent on *paths*. in the Nix store that are dependent on *paths*.
- `--deriver`; `-d`\ - `--deriver`; `-d`\
Prints the [deriver] of the store paths *paths*. If Prints the [deriver] that was used to build the store paths *paths*. If
the path has no deriver (e.g., if it is a source file), or if the the path has no deriver (e.g., if it is a source file), or if the
deriver is not known (e.g., in the case of a binary-only deriver is not known (e.g., in the case of a binary-only
deployment), the string `unknown-deriver` is printed. deployment), the string `unknown-deriver` is printed.
The returned deriver is not guaranteed to exist in the local store, for
example when *paths* were substituted from a binary cache.
Use `--valid-derivers` instead to obtain valid paths only.
[deriver]: ../../glossary.md#gloss-deriver [deriver]: ../../glossary.md#gloss-deriver
- `--valid-derivers`\
Prints a set of derivation files (`.drv`) which are supposed produce
said paths when realized. Might print nothing, for example for source paths
or paths subsituted from a binary cache.
- `--graph`\ - `--graph`\
Prints the references graph of the store paths *paths* in the format Prints the references graph of the store paths *paths* in the format
of the `dot` tool of AT\&T's [Graphviz of the `dot` tool of AT\&T's [Graphviz

View file

@ -1,6 +1,6 @@
# Name # Name
`nix-store --realise` - realise specified store paths `nix-store --realise` - build or fetch store objects
# Synopsis # Synopsis
@ -8,33 +8,35 @@
# Description # Description
The operation `--realise` essentially “builds” the specified store
paths. Realisation is a somewhat overloaded term:
- If the store path is a *derivation*, realisation ensures that the Each of *paths* is processed as follows:
output paths of the derivation are [valid] (i.e.,
the output path and its closure exist in the file system). This
can be done in several ways. First, it is possible that the
outputs are already valid, in which case we are done
immediately. Otherwise, there may be [substitutes]
that produce the outputs (e.g., by downloading them). Finally, the
outputs can be produced by running the build task described
by the derivation.
- If the store path is not a derivation, realisation ensures that the - If the path leads to a [store derivation]:
specified path is valid (i.e., it and its closure exist in the file 1. If it is not [valid], substitute the store derivation file itself.
system). If the path is already valid, we are done immediately. 2. Realise its [output paths]:
Otherwise, the path and any missing paths in its closure may be - Try to fetch from [substituters] the [store objects] associated with the output paths in the store derivation's [closure].
produced through substitutes. If there are no (successful) - With [content-addressed derivations] (experimental): Determine the output paths to realise by querying content-addressed realisation entries in the [Nix database].
substitutes, realisation fails. - For any store paths that cannot be substituted, produce the required store objects. This involves first realising all outputs of the derivation's dependencies and then running the derivation's [`builder`](@docroot@/language/derivations.md#attr-builder) executable. <!-- TODO: Link to build process page #8888 -->
- Otherwise, and if the path is not already valid: Try to fetch the associated [store objects] in the path's [closure] from [substituters].
If no substitutes are available and no store derivation is given, realisation fails.
[store paths]: @docroot@/glossary.md#gloss-store-path
[valid]: @docroot@/glossary.md#gloss-validity [valid]: @docroot@/glossary.md#gloss-validity
[substitutes]: @docroot@/glossary.md#gloss-substitute [store derivation]: @docroot@/glossary.md#gloss-store-derivation
[output paths]: @docroot@/glossary.md#gloss-output-path
[store objects]: @docroot@/glossary.md#gloss-store-object
[closure]: @docroot@/glossary.md#gloss-closure
[substituters]: @docroot@/command-ref/conf-file.md#conf-substituters
[content-addressed derivations]: @docroot@/contributing/experimental-features.md#xp-feature-ca-derivations
[Nix database]: @docroot@/glossary.md#gloss-nix-database
The output path of each derivation is printed on standard output. (For The resulting paths are printed on standard output.
non-derivations argument, the argument itself is printed.) For non-derivation arguments, the argument itself is printed.
The following flags are available: {{#include ../status-build-failure.md}}
# Options
- `--dry-run`\ - `--dry-run`\
Print on standard error a description of what packages would be Print on standard error a description of what packages would be
@ -54,8 +56,6 @@ The following flags are available:
previous build, the new output path is left in previous build, the new output path is left in
`/nix/store/name.check.` `/nix/store/name.check.`
{{#include ../status-build-failure.md}}
{{#include ./opt-common.md}} {{#include ./opt-common.md}}
{{#include ../opt-common.md}} {{#include ../opt-common.md}}
@ -67,8 +67,6 @@ The following flags are available:
This operation is typically used to build [store derivation]s produced by This operation is typically used to build [store derivation]s produced by
[`nix-instantiate`](@docroot@/command-ref/nix-instantiate.md): [`nix-instantiate`](@docroot@/command-ref/nix-instantiate.md):
[store derivation]: @docroot@/glossary.md#gloss-store-derivation
```console ```console
$ nix-store --realise $(nix-instantiate ./test.nix) $ nix-store --realise $(nix-instantiate ./test.nix)
/nix/store/31axcgrlbfsxzmfff1gyj1bf62hvkby2-aterm-2.3.1 /nix/store/31axcgrlbfsxzmfff1gyj1bf62hvkby2-aterm-2.3.1

View file

@ -2,56 +2,66 @@
Most Nix commands accept the following command-line options: Most Nix commands accept the following command-line options:
- <span id="opt-help">[`--help`](#opt-help)</span>\ - <span id="opt-help">[`--help`](#opt-help)</span>
Prints out a summary of the command syntax and exits. Prints out a summary of the command syntax and exits.
- <span id="opt-version">[`--version`](#opt-version)</span>\ - <span id="opt-version">[`--version`](#opt-version)</span>
Prints out the Nix version number on standard output and exits. Prints out the Nix version number on standard output and exits.
- <span id="opt-verbose">[`--verbose`](#opt-verbose)</span> / `-v`\ - <span id="opt-verbose">[`--verbose`](#opt-verbose)</span> / `-v`
Increases the level of verbosity of diagnostic messages printed on
standard error. For each Nix operation, the information printed on
standard output is well-defined; any diagnostic information is
printed on standard error, never on standard output.
This option may be specified repeatedly. Currently, the following Increases the level of verbosity of diagnostic messages printed on standard error.
verbosity levels exist: For each Nix operation, the information printed on standard output is well-defined;
any diagnostic information is printed on standard error, never on standard output.
- 0\ This option may be specified repeatedly.
“Errors only”: only print messages explaining why the Nix Currently, the following verbosity levels exist:
invocation failed.
- 1\ - `0` “Errors only”
“Informational”: print *useful* messages about what Nix is
doing. This is the default.
- 2\ Only print messages explaining why the Nix invocation failed.
“Talkative”: print more informational messages.
- 3\ - `1` “Informational”
“Chatty”: print even more informational messages.
- 4\ Print *useful* messages about what Nix is doing.
“Debug”: print debug information. This is the default.
- 5\ - `2` “Talkative”
“Vomit”: print vast amounts of debug information.
- <span id="opt-quiet">[`--quiet`](#opt-quiet)</span>\ Print more informational messages.
Decreases the level of verbosity of diagnostic messages printed on
standard error. This is the inverse option to `-v` / `--verbose`.
This option may be specified repeatedly. See the previous verbosity - `3` “Chatty”
levels list.
- <span id="opt-log-format">[`--log-format`](#opt-log-format)</span> *format*\ Print even more informational messages.
This option can be used to change the output of the log format, with
*format* being one of: - `4` “Debug”
Print debug information.
- `5` “Vomit”
Print vast amounts of debug information.
- <span id="opt-quiet">[`--quiet`](#opt-quiet)</span>
Decreases the level of verbosity of diagnostic messages printed on standard error.
This is the inverse option to `-v` / `--verbose`.
This option may be specified repeatedly.
See the previous verbosity levels list.
- <span id="opt-log-format">[`--log-format`](#opt-log-format)</span> *format*
This option can be used to change the output of the log format, with *format* being one of:
- `raw`
- raw\
This is the raw format, as outputted by nix-build. This is the raw format, as outputted by nix-build.
- internal-json\ - `internal-json`
Outputs the logs in a structured manner. Outputs the logs in a structured manner.
> **Warning** > **Warning**
@ -60,100 +70,85 @@ Most Nix commands accept the following command-line options:
> the error-messages (namely of the `msg`-field) can change > the error-messages (namely of the `msg`-field) can change
> between releases. > between releases.
- bar\ - `bar`
Only display a progress bar during the builds. Only display a progress bar during the builds.
- bar-with-logs\ - `bar-with-logs`
Display the raw logs, with the progress bar at the bottom. Display the raw logs, with the progress bar at the bottom.
- <span id="opt-no-build-output">[`--no-build-output`](#opt-no-build-output)</span> / `-Q`\ - <span id="opt-no-build-output">[`--no-build-output`](#opt-no-build-output)</span> / `-Q`
By default, output written by builders to standard output and
standard error is echoed to the Nix command's standard error. This
option suppresses this behaviour. Note that the builder's standard
output and error are always written to a log file in
`prefix/nix/var/log/nix`.
- <span id="opt-max-jobs">[`--max-jobs`](#opt-max-jobs)</span> / `-j` *number*\ By default, output written by builders to standard output and standard error is echoed to the Nix command's standard error.
Sets the maximum number of build jobs that Nix will perform in This option suppresses this behaviour.
parallel to the specified number. Specify `auto` to use the number Note that the builder's standard output and error are always written to a log file in `prefix/nix/var/log/nix`.
of CPUs in the system. The default is specified by the `max-jobs`
configuration setting, which itself defaults to `1`. A higher
value is useful on SMP systems or to exploit I/O latency.
Setting it to `0` disallows building on the local machine, which is - <span id="opt-max-jobs">[`--max-jobs`](#opt-max-jobs)</span> / `-j` *number*
useful when you want builds to happen only on remote builders.
- <span id="opt-cores">[`--cores`](#opt-cores)</span>\ Sets the maximum number of build jobs that Nix will perform in parallel to the specified number.
Sets the value of the `NIX_BUILD_CORES` environment variable in Specify `auto` to use the number of CPUs in the system.
the invocation of builders. Builders can use this variable at The default is specified by the `max-jobs` configuration setting, which itself defaults to `1`.
their discretion to control the maximum amount of parallelism. For A higher value is useful on SMP systems or to exploit I/O latency.
instance, in Nixpkgs, if the derivation attribute
`enableParallelBuilding` is set to `true`, the builder passes the
`-jN` flag to GNU Make. It defaults to the value of the `cores`
configuration setting, if set, or `1` otherwise. The value `0`
means that the builder should use all available CPU cores in the
system.
- <span id="opt-max-silent-time">[`--max-silent-time`](#opt-max-silent-time)</span>\ Setting it to `0` disallows building on the local machine, which is useful when you want builds to happen only on remote builders.
Sets the maximum number of seconds that a builder can go without
producing any data on standard output or standard error. The
default is specified by the `max-silent-time` configuration
setting. `0` means no time-out.
- <span id="opt-timeout">[`--timeout`](#opt-timeout)</span>\ - <span id="opt-cores">[`--cores`](#opt-cores)</span>
Sets the maximum number of seconds that a builder can run. The
default is specified by the `timeout` configuration setting. `0`
means no timeout.
- <span id="opt-keep-going">[`--keep-going`](#opt-keep-going)</span> / `-k`\ Sets the value of the `NIX_BUILD_CORES` environment variable in the invocation of builders.
Keep going in case of failed builds, to the greatest extent Builders can use this variable at their discretion to control the maximum amount of parallelism.
possible. That is, if building an input of some derivation fails, For instance, in Nixpkgs, if the derivation attribute `enableParallelBuilding` is set to `true`, the builder passes the `-jN` flag to GNU Make.
Nix will still build the other inputs, but not the derivation It defaults to the value of the `cores` configuration setting, if set, or `1` otherwise.
itself. Without this option, Nix stops if any build fails (except The value `0` means that the builder should use all available CPU cores in the system.
for builds of substitutes), possibly killing builds in progress (in
case of parallel or distributed builds).
- <span id="opt-keep-failed">[`--keep-failed`](#opt-keep-failed)</span> / `-K`\ - <span id="opt-max-silent-time">[`--max-silent-time`](#opt-max-silent-time)</span>
Specifies that in case of a build failure, the temporary directory
(usually in `/tmp`) in which the build takes place should not be
deleted. The path of the build directory is printed as an
informational message.
- <span id="opt-fallback">[`--fallback`](#opt-fallback)</span>\ Sets the maximum number of seconds that a builder can go without producing any data on standard output or standard error.
Whenever Nix attempts to build a derivation for which substitutes The default is specified by the `max-silent-time` configuration setting.
are known for each output path, but realising the output paths `0` means no time-out.
through the substitutes fails, fall back on building the derivation.
The most common scenario in which this is useful is when we have - <span id="opt-timeout">[`--timeout`](#opt-timeout)</span>
registered substitutes in order to perform binary distribution from,
say, a network repository. If the repository is down, the
realisation of the derivation will fail. When this option is
specified, Nix will build the derivation instead. Thus, installation
from binaries falls back on installation from source. This option is
not the default since it is generally not desirable for a transient
failure in obtaining the substitutes to lead to a full build from
source (with the related consumption of resources).
- <span id="opt-readonly-mode">[`--readonly-mode`](#opt-readonly-mode)</span>\ Sets the maximum number of seconds that a builder can run.
When this option is used, no attempt is made to open the Nix The default is specified by the `timeout` configuration setting.
database. Most Nix operations do need database access, so those `0` means no timeout.
operations will fail.
- <span id="opt-arg">[`--arg`](#opt-arg)</span> *name* *value*\ - <span id="opt-keep-going">[`--keep-going`](#opt-keep-going)</span> / `-k`
This option is accepted by `nix-env`, `nix-instantiate`,
`nix-shell` and `nix-build`. When evaluating Nix expressions, the
expression evaluator will automatically try to call functions that
it encounters. It can automatically call functions for which every
argument has a [default
value](@docroot@/language/constructs.md#functions) (e.g.,
`{ argName ? defaultValue }: ...`). With `--arg`, you can also
call functions that have arguments without a default value (or
override a default value). That is, if the evaluator encounters a
function with an argument named *name*, it will call it with value
*value*.
For instance, the top-level `default.nix` in Nixpkgs is actually a Keep going in case of failed builds, to the greatest extent possible.
function: That is, if building an input of some derivation fails, Nix will still build the other inputs, but not the derivation itself.
Without this option, Nix stops if any build fails (except for builds of substitutes), possibly killing builds in progress (in case of parallel or distributed builds).
- <span id="opt-keep-failed">[`--keep-failed`](#opt-keep-failed)</span> / `-K`
Specifies that in case of a build failure, the temporary directory (usually in `/tmp`) in which the build takes place should not be deleted.
The path of the build directory is printed as an informational message.
- <span id="opt-fallback">[`--fallback`](#opt-fallback)</span>
Whenever Nix attempts to build a derivation for which substitutes are known for each output path, but realising the output paths through the substitutes fails, fall back on building the derivation.
The most common scenario in which this is useful is when we have registered substitutes in order to perform binary distribution from, say, a network repository.
If the repository is down, the realisation of the derivation will fail.
When this option is specified, Nix will build the derivation instead.
Thus, installation from binaries falls back on installation from source.
This option is not the default since it is generally not desirable for a transient failure in obtaining the substitutes to lead to a full build from source (with the related consumption of resources).
- <span id="opt-readonly-mode">[`--readonly-mode`](#opt-readonly-mode)</span>
When this option is used, no attempt is made to open the Nix database.
Most Nix operations do need database access, so those operations will fail.
- <span id="opt-arg">[`--arg`](#opt-arg)</span> *name* *value*
This option is accepted by `nix-env`, `nix-instantiate`, `nix-shell` and `nix-build`.
When evaluating Nix expressions, the expression evaluator will automatically try to call functions that it encounters.
It can automatically call functions for which every argument has a [default value](@docroot@/language/constructs.md#functions) (e.g., `{ argName ? defaultValue }: ...`).
With `--arg`, you can also call functions that have arguments without a default value (or override a default value).
That is, if the evaluator encounters a function with an argument named *name*, it will call it with value *value*.
For instance, the top-level `default.nix` in Nixpkgs is actually a function:
```nix ```nix
{ # The system (e.g., `i686-linux') for which to build the packages. { # The system (e.g., `i686-linux') for which to build the packages.
@ -162,57 +157,49 @@ Most Nix commands accept the following command-line options:
}: ... }: ...
``` ```
So if you call this Nix expression (e.g., when you do `nix-env --install --attr So if you call this Nix expression (e.g., when you do `nix-env --install --attr pkgname`), the function will be called automatically using the value [`builtins.currentSystem`](@docroot@/language/builtins.md) for the `system` argument.
pkgname`), the function will be called automatically using the You can override this using `--arg`, e.g., `nix-env --install --attr pkgname --arg system \"i686-freebsd\"`.
value [`builtins.currentSystem`](@docroot@/language/builtins.md) for (Note that since the argument is a Nix string literal, you have to escape the quotes.)
the `system` argument. You can override this using `--arg`, e.g.,
`nix-env --install --attr pkgname --arg system \"i686-freebsd\"`. (Note that
since the argument is a Nix string literal, you have to escape the
quotes.)
- <span id="opt-argstr">[`--argstr`](#opt-argstr)</span> *name* *value*\ - <span id="opt-argstr">[`--argstr`](#opt-argstr)</span> *name* *value*
This option is like `--arg`, only the value is not a Nix
expression but a string. So instead of `--arg system
\"i686-linux\"` (the outer quotes are to keep the shell happy) you
can say `--argstr system i686-linux`.
- <span id="opt-attr">[`--attr`](#opt-attr)</span> / `-A` *attrPath*\ This option is like `--arg`, only the value is not a Nix expression but a string.
Select an attribute from the top-level Nix expression being So instead of `--arg system \"i686-linux\"` (the outer quotes are to keep the shell happy) you can say `--argstr system i686-linux`.
evaluated. (`nix-env`, `nix-instantiate`, `nix-build` and
`nix-shell` only.) The *attribute path* *attrPath* is a sequence - <span id="opt-attr">[`--attr`](#opt-attr)</span> / `-A` *attrPath*
of attribute names separated by dots. For instance, given a
top-level Nix expression *e*, the attribute path `xorg.xorgserver` Select an attribute from the top-level Nix expression being evaluated.
would cause the expression `e.xorg.xorgserver` to be used. See (`nix-env`, `nix-instantiate`, `nix-build` and `nix-shell` only.)
[`nix-env --install`](@docroot@/command-ref/nix-env/install.md) for some The *attribute path* *attrPath* is a sequence of attribute names separated by dots.
concrete examples. For instance, given a top-level Nix expression *e*, the attribute path `xorg.xorgserver` would cause the expression `e.xorg.xorgserver` to be used.
See [`nix-env --install`](@docroot@/command-ref/nix-env/install.md) for some concrete examples.
In addition to attribute names, you can also specify array indices. In addition to attribute names, you can also specify array indices.
For instance, the attribute path `foo.3.bar` selects the `bar` For instance, the attribute path `foo.3.bar` selects the `bar`
attribute of the fourth element of the array in the `foo` attribute attribute of the fourth element of the array in the `foo` attribute
of the top-level expression. of the top-level expression.
- <span id="opt-expr">[`--expr`](#opt-expr)</span> / `-E`\ - <span id="opt-expr">[`--expr`](#opt-expr)</span> / `-E`
Interpret the command line arguments as a list of Nix expressions to
be parsed and evaluated, rather than as a list of file names of Nix
expressions. (`nix-instantiate`, `nix-build` and `nix-shell` only.)
For `nix-shell`, this option is commonly used to give you a shell in Interpret the command line arguments as a list of Nix expressions to be parsed and evaluated, rather than as a list of file names of Nix expressions.
which you can build the packages returned by the expression. If you (`nix-instantiate`, `nix-build` and `nix-shell` only.)
want to get a shell which contain the *built* packages ready for
use, give your expression to the `nix-shell --packages ` convenience flag For `nix-shell`, this option is commonly used to give you a shell in which you can build the packages returned by the expression.
instead. If you want to get a shell which contain the *built* packages ready for use, give your expression to the `nix-shell --packages ` convenience flag instead.
- <span id="opt-I">[`-I`](#opt-I)</span> *path*
- <span id="opt-I">[`-I`](#opt-I)</span> *path*\
Add an entry to the [Nix expression search path](@docroot@/command-ref/conf-file.md#conf-nix-path). Add an entry to the [Nix expression search path](@docroot@/command-ref/conf-file.md#conf-nix-path).
This option may be given multiple times. This option may be given multiple times.
Paths added through `-I` take precedence over [`NIX_PATH`](@docroot@/command-ref/env-common.md#env-NIX_PATH). Paths added through `-I` take precedence over [`NIX_PATH`](@docroot@/command-ref/env-common.md#env-NIX_PATH).
- <span id="opt-option">[`--option`](#opt-option)</span> *name* *value*\ - <span id="opt-option">[`--option`](#opt-option)</span> *name* *value*
Set the Nix configuration option *name* to *value*. This overrides
settings in the Nix configuration file (see nix.conf5).
- <span id="opt-repair">[`--repair`](#opt-repair)</span>\ Set the Nix configuration option *name* to *value*.
Fix corrupted or missing store paths by redownloading or rebuilding This overrides settings in the Nix configuration file (see nix.conf5).
them. Note that this is slow because it requires computing a
cryptographic hash of the contents of every path in the closure of - <span id="opt-repair">[`--repair`](#opt-repair)</span>
the build. Also note the warning under `nix-store --repair-path`.
Fix corrupted or missing store paths by redownloading or rebuilding them.
Note that this is slow because it requires computing a cryptographic hash of the contents of every path in the closure of the build.
Also note the warning under `nix-store --repair-path`.

View file

@ -33,7 +33,7 @@
Ensure a [store path] is [valid][validity]. Ensure a [store path] is [valid][validity].
This means either running the `builder` executable as specified in the corresponding [derivation] or fetching a pre-built [store object] from a [substituter]. This means either running the [`builder`](@docroot@/language/derivations.md#attr-builder) executable as specified in the corresponding [derivation], or fetching a pre-built [store object] from a [substituter], or delegating to a [remote builder](@docroot@/advanced-topics/distributed-builds.html) and retrieving the outputs. <!-- TODO: link [running] to build process page, #8888 -->
See [`nix-build`](./command-ref/nix-build.md) and [`nix-store --realise`](@docroot@/command-ref/nix-store/realise.md). See [`nix-build`](./command-ref/nix-build.md) and [`nix-store --realise`](@docroot@/command-ref/nix-store/realise.md).
@ -197,9 +197,15 @@
[closure]: #gloss-closure [closure]: #gloss-closure
- [output]{#gloss-output}
A [store object] produced by a [derivation].
[output]: #gloss-output
- [output path]{#gloss-output-path} - [output path]{#gloss-output-path}
A [store path] produced by a [derivation]. The [store path] to the [output] of a [derivation].
[output path]: #gloss-output-path [output path]: #gloss-output-path

View file

@ -17,7 +17,7 @@ the attributes of which specify the inputs of the build.
string. This is used as a symbolic name for the package by string. This is used as a symbolic name for the package by
`nix-env`, and it is appended to the output paths of the derivation. `nix-env`, and it is appended to the output paths of the derivation.
- There must be an attribute named `builder` that identifies the - There must be an attribute named [`builder`]{#attr-builder} that identifies the
program that is executed to perform the build. It can be either a program that is executed to perform the build. It can be either a
derivation or a source (a local file reference, e.g., derivation or a source (a local file reference, e.g.,
`./builder.sh`). `./builder.sh`).

View file

@ -0,0 +1,19 @@
# Derivation "ATerm" file format
For historical reasons, [derivations](@docroot@/glossary.md#gloss-store-derivation) are stored on-disk in [ATerm](https://homepages.cwi.nl/~daybuild/daily-books/technology/aterm-guide/aterm-guide.html) format.
Derivations are serialised in one of the following formats:
- ```
Derive(...)
```
For all stable derivations.
- ```
DrvWithVersion(<version-string>, ...)
```
The only `version-string`s that are in use today are for [experimental features](@docroot@/contributing/experimental-features.md):
- `"xp-dyn-drv"` for the [`dynamic-derivations`](@docroot@/contributing/experimental-features.md#xp-feature-dynamic-derivations) experimental feature.

View file

@ -24,3 +24,7 @@
It is part of the [`dynamic-derivations`](@docroot@/contributing/experimental-features.md#xp-feature-dynamic-derivations) experimental feature. It is part of the [`dynamic-derivations`](@docroot@/contributing/experimental-features.md#xp-feature-dynamic-derivations) experimental feature.
- Flake follow paths at depths greater than 2 are now handled correctly, preventing "follows a non-existent input" errors. - Flake follow paths at depths greater than 2 are now handled correctly, preventing "follows a non-existent input" errors.
- [`nix-store --query`](@docroot@/command-ref/nix-store/query.md) gained a new type of query: `--valid-derivers`. It returns all `.drv` files in the local store that *can be* used to build the output passed in argument.
This is in contrast to `--deriver`, which returns the single `.drv` file that *was actually* used to build the output passed in argument. In case the output was substituted from a binary cache,
this `.drv` file may only exist on said binary cache and not locally.

View file

@ -324,7 +324,7 @@ SV * derivationFromPath(char * drvPath)
hv_stores(hash, "outputs", newRV((SV *) outputs)); hv_stores(hash, "outputs", newRV((SV *) outputs));
AV * inputDrvs = newAV(); AV * inputDrvs = newAV();
for (auto & i : drv.inputDrvs) for (auto & i : drv.inputDrvs.map)
av_push(inputDrvs, newSVpv(store()->printStorePath(i.first).c_str(), 0)); // !!! ignores i->second av_push(inputDrvs, newSVpv(store()->printStorePath(i.first).c_str(), 0)); // !!! ignores i->second
hv_stores(hash, "inputDrvs", newRV((SV *) inputDrvs)); hv_stores(hash, "inputDrvs", newRV((SV *) inputDrvs));

View file

@ -314,7 +314,7 @@ connected:
// //
// 2. Changing the `inputSrcs` set changes the associated // 2. Changing the `inputSrcs` set changes the associated
// output ids, which break CA derivations // output ids, which break CA derivations
if (!drv.inputDrvs.empty()) if (!drv.inputDrvs.map.empty())
drv.inputSrcs = store->parseStorePathSet(inputs); drv.inputSrcs = store->parseStorePathSet(inputs);
optResult = sshStore->buildDerivation(*drvPath, (const BasicDerivation &) drv); optResult = sshStore->buildDerivation(*drvPath, (const BasicDerivation &) drv);
auto & result = *optResult; auto & result = *optResult;

View file

@ -58,6 +58,28 @@ StorePathSet BuiltPath::outPaths() const
); );
} }
SingleDerivedPath::Built SingleBuiltPath::Built::discardOutputPath() const
{
return SingleDerivedPath::Built {
.drvPath = make_ref<SingleDerivedPath>(drvPath->discardOutputPath()),
.output = output.first,
};
}
SingleDerivedPath SingleBuiltPath::discardOutputPath() const
{
return std::visit(
overloaded{
[](const SingleBuiltPath::Opaque & p) -> SingleDerivedPath {
return p;
},
[](const SingleBuiltPath::Built & b) -> SingleDerivedPath {
return b.discardOutputPath();
},
}, raw()
);
}
nlohmann::json BuiltPath::Built::toJSON(const Store & store) const nlohmann::json BuiltPath::Built::toJSON(const Store & store) const
{ {
nlohmann::json res; nlohmann::json res;

View file

@ -9,6 +9,8 @@ struct SingleBuiltPathBuilt {
ref<SingleBuiltPath> drvPath; ref<SingleBuiltPath> drvPath;
std::pair<std::string, StorePath> output; std::pair<std::string, StorePath> output;
SingleDerivedPathBuilt discardOutputPath() const;
std::string to_string(const Store & store) const; std::string to_string(const Store & store) const;
static SingleBuiltPathBuilt parse(const Store & store, std::string_view, std::string_view); static SingleBuiltPathBuilt parse(const Store & store, std::string_view, std::string_view);
nlohmann::json toJSON(const Store & store) const; nlohmann::json toJSON(const Store & store) const;
@ -34,6 +36,8 @@ struct SingleBuiltPath : _SingleBuiltPathRaw {
StorePath outPath() const; StorePath outPath() const;
SingleDerivedPath discardOutputPath() const;
static SingleBuiltPath parse(const Store & store, std::string_view); static SingleBuiltPath parse(const Store & store, std::string_view);
nlohmann::json toJSON(const Store & store) const; nlohmann::json toJSON(const Store & store) const;
}; };

View file

@ -490,33 +490,38 @@ bool NixRepl::processLine(std::string line)
<< "\n" << "\n"
<< " <expr> Evaluate and print expression\n" << " <expr> Evaluate and print expression\n"
<< " <x> = <expr> Bind expression to variable\n" << " <x> = <expr> Bind expression to variable\n"
<< " :a <expr> Add attributes from resulting set to scope\n" << " :a, :add <expr> Add attributes from resulting set to scope\n"
<< " :b <expr> Build a derivation\n" << " :b <expr> Build a derivation\n"
<< " :bl <expr> Build a derivation, creating GC roots in the working directory\n" << " :bl <expr> Build a derivation, creating GC roots in the\n"
<< " :e <expr> Open package or function in $EDITOR\n" << " working directory\n"
<< " :i <expr> Build derivation, then install result into current profile\n" << " :e, :edit <expr> Open package or function in $EDITOR\n"
<< " :l <path> Load Nix expression and add it to scope\n" << " :i <expr> Build derivation, then install result into\n"
<< " :lf <ref> Load Nix flake and add it to scope\n" << " current profile\n"
<< " :p <expr> Evaluate and print expression recursively\n" << " :l, :load <path> Load Nix expression and add it to scope\n"
<< " :q Exit nix-repl\n" << " :lf, :load-flake <ref> Load Nix flake and add it to scope\n"
<< " :r Reload all files\n" << " :p, :print <expr> Evaluate and print expression recursively\n"
<< " :sh <expr> Build dependencies of derivation, then start nix-shell\n" << " :q, :quit Exit nix-repl\n"
<< " :r, :reload Reload all files\n"
<< " :sh <expr> Build dependencies of derivation, then start\n"
<< " nix-shell\n"
<< " :t <expr> Describe result of evaluation\n" << " :t <expr> Describe result of evaluation\n"
<< " :u <expr> Build derivation, then start nix-shell\n" << " :u <expr> Build derivation, then start nix-shell\n"
<< " :doc <expr> Show documentation of a builtin function\n" << " :doc <expr> Show documentation of a builtin function\n"
<< " :log <expr> Show logs for a derivation\n" << " :log <expr> Show logs for a derivation\n"
<< " :te [bool] Enable, disable or toggle showing traces for errors\n" << " :te, :trace-enable [bool] Enable, disable or toggle showing traces for\n"
<< " errors\n"
<< " :?, :help Brings up this help menu\n"
; ;
if (state->debugRepl) { if (state->debugRepl) {
std::cout std::cout
<< "\n" << "\n"
<< " Debug mode commands\n" << " Debug mode commands\n"
<< " :env Show env stack\n" << " :env Show env stack\n"
<< " :bt Show trace stack\n" << " :bt, :backtrace Show trace stack\n"
<< " :st Show current trace\n" << " :st Show current trace\n"
<< " :st <idx> Change to another trace in the stack\n" << " :st <idx> Change to another trace in the stack\n"
<< " :c Go until end of program, exception, or builtins.break\n" << " :c, :continue Go until end of program, exception, or builtins.break\n"
<< " :s Go one step\n" << " :s, :step Go one step\n"
; ;
} }

View file

@ -21,7 +21,7 @@ struct EvalSettings : Config
R"( R"(
List of directories to be searched for `<...>` file references List of directories to be searched for `<...>` file references
In particular, outside of [pure evaluation mode](#conf-pure-evaluation), this determines the value of In particular, outside of [pure evaluation mode](#conf-pure-eval), this determines the value of
[`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtins-nixPath). [`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtins-nixPath).
)"}; )"};

View file

@ -520,11 +520,6 @@ LockedFlake lockFlake(
} }
} }
auto localPath(parentPath);
// If this input is a path, recurse it down.
// This allows us to resolve path inputs relative to the current flake.
if ((*input.ref).input.getType() == "path")
localPath = absPath(*input.ref->input.getSourcePath(), parentPath);
computeLocks( computeLocks(
mustRefetch mustRefetch
? getFlake(state, oldLock->lockedRef, false, flakeCache, inputPath).inputs ? getFlake(state, oldLock->lockedRef, false, flakeCache, inputPath).inputs

View file

@ -1252,15 +1252,13 @@ drvName, Bindings * attrs, Value & v)
state.store->computeFSClosure(d.drvPath, refs); state.store->computeFSClosure(d.drvPath, refs);
for (auto & j : refs) { for (auto & j : refs) {
drv.inputSrcs.insert(j); drv.inputSrcs.insert(j);
if (j.isDerivation()) if (j.isDerivation()) {
drv.inputDrvs[j] = state.store->readDerivation(j).outputNames(); drv.inputDrvs.map[j].value = state.store->readDerivation(j).outputNames();
}
} }
}, },
[&](const NixStringContextElem::Built & b) { [&](const NixStringContextElem::Built & b) {
if (auto * p = std::get_if<DerivedPath::Opaque>(&*b.drvPath)) drv.inputDrvs.ensureSlot(*b.drvPath).value.insert(b.output);
drv.inputDrvs[p->path].insert(b.output);
else
throw UnimplementedError("Dependencies on the outputs of dynamic derivations are not yet supported");
}, },
[&](const NixStringContextElem::Opaque & o) { [&](const NixStringContextElem::Opaque & o) {
drv.inputSrcs.insert(o.path); drv.inputSrcs.insert(o.path);
@ -1520,15 +1518,25 @@ static RegisterPrimOp primop_storePath({
static void prim_pathExists(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_pathExists(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
auto & arg = *args[0];
/* We dont check the path right now, because we dont want to /* We dont check the path right now, because we dont want to
throw if the path isnt allowed, but just return false (and we throw if the path isnt allowed, but just return false (and we
cant just catch the exception here because we still want to cant just catch the exception here because we still want to
throw if something in the evaluation of `*args[0]` tries to throw if something in the evaluation of `arg` tries to
access an unauthorized path). */ access an unauthorized path). */
auto path = realisePath(state, pos, *args[0], { .checkForPureEval = false }); auto path = realisePath(state, pos, arg, { .checkForPureEval = false });
/* SourcePath doesn't know about trailing slash. */
auto mustBeDir = arg.type() == nString && arg.str().ends_with("/");
try { try {
v.mkBool(state.checkSourcePath(path).pathExists()); auto checked = state.checkSourcePath(path);
auto exists = checked.pathExists();
if (exists && mustBeDir) {
exists = checked.lstat().type == InputAccessor::tDirectory;
}
v.mkBool(exists);
} catch (SysError & e) { } catch (SysError & e) {
/* Don't give away info from errors while canonicalising /* Don't give away info from errors while canonicalising
path in restricted mode. */ path in restricted mode. */

View file

@ -96,7 +96,7 @@ void CreateDerivationAndRealiseGoal::getDerivation()
auto drvPath = StorePath::dummy; auto drvPath = StorePath::dummy;
try { try {
drvPath = resolveDerivedPath(worker.store, *drvReq); drvPath = resolveDerivedPath(worker.store, *drvReq);
} catch (MissingRealisation) { } catch (MissingRealisation &) {
return std::nullopt; return std::nullopt;
} }
return worker.evalStore.isValidPath(drvPath) || worker.store.isValidPath(drvPath) return worker.evalStore.isValidPath(drvPath) || worker.store.isValidPath(drvPath)

View file

@ -350,24 +350,36 @@ void DerivationGoal::gaveUpOnSubstitution()
/* The inputs must be built before we can build this goal. */ /* The inputs must be built before we can build this goal. */
inputDrvOutputs.clear(); inputDrvOutputs.clear();
if (useDerivation) if (useDerivation) {
for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs) { std::function<void(ref<SingleDerivedPath>, const DerivedPathMap<StringSet>::ChildNode &)> addWaiteeDerivedPath;
addWaiteeDerivedPath = [&](ref<SingleDerivedPath> inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
if (!inputNode.value.empty())
addWaitee(worker.makeGoal(
DerivedPath::Built {
.drvPath = inputDrv,
.outputs = inputNode.value,
},
buildMode == bmRepair ? bmRepair : bmNormal));
for (const auto & [outputName, childNode] : inputNode.childMap)
addWaiteeDerivedPath(
make_ref<SingleDerivedPath>(SingleDerivedPath::Built { inputDrv, outputName }),
childNode);
};
for (const auto & [inputDrvPath, inputNode] : dynamic_cast<Derivation *>(drv.get())->inputDrvs.map) {
/* Ensure that pure, non-fixed-output derivations don't /* Ensure that pure, non-fixed-output derivations don't
depend on impure derivations. */ depend on impure derivations. */
if (experimentalFeatureSettings.isEnabled(Xp::ImpureDerivations) && drv->type().isPure() && !drv->type().isFixed()) { if (experimentalFeatureSettings.isEnabled(Xp::ImpureDerivations) && drv->type().isPure() && !drv->type().isFixed()) {
auto inputDrv = worker.evalStore.readDerivation(i.first); auto inputDrv = worker.evalStore.readDerivation(inputDrvPath);
if (!inputDrv.type().isPure()) if (!inputDrv.type().isPure())
throw Error("pure derivation '%s' depends on impure derivation '%s'", throw Error("pure derivation '%s' depends on impure derivation '%s'",
worker.store.printStorePath(drvPath), worker.store.printStorePath(drvPath),
worker.store.printStorePath(i.first)); worker.store.printStorePath(inputDrvPath));
} }
addWaitee(worker.makeGoal( addWaiteeDerivedPath(makeConstantStorePathRef(inputDrvPath), inputNode);
DerivedPath::Built { }
.drvPath = makeConstantStorePathRef(i.first),
.outputs = i.second,
},
buildMode == bmRepair ? bmRepair : bmNormal));
} }
/* Copy the input sources from the eval store to the build /* Copy the input sources from the eval store to the build
@ -501,7 +513,7 @@ void DerivationGoal::inputsRealised()
return ia.deferred; return ia.deferred;
}, },
[&](const DerivationType::ContentAddressed & ca) { [&](const DerivationType::ContentAddressed & ca) {
return !fullDrv.inputDrvs.empty() && ( return !fullDrv.inputDrvs.map.empty() && (
ca.fixed ca.fixed
/* Can optionally resolve if fixed, which is good /* Can optionally resolve if fixed, which is good
for avoiding unnecessary rebuilds. */ for avoiding unnecessary rebuilds. */
@ -515,7 +527,7 @@ void DerivationGoal::inputsRealised()
} }
}, drvType.raw); }, drvType.raw);
if (resolveDrv && !fullDrv.inputDrvs.empty()) { if (resolveDrv && !fullDrv.inputDrvs.map.empty()) {
experimentalFeatureSettings.require(Xp::CaDerivations); experimentalFeatureSettings.require(Xp::CaDerivations);
/* We are be able to resolve this derivation based on the /* We are be able to resolve this derivation based on the
@ -552,11 +564,13 @@ void DerivationGoal::inputsRealised()
return; return;
} }
for (auto & [depDrvPath, wantedDepOutputs] : fullDrv.inputDrvs) { std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accumInputPaths;
accumInputPaths = [&](const StorePath & depDrvPath, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
/* Add the relevant output closures of the input derivation /* Add the relevant output closures of the input derivation
`i' as input paths. Only add the closures of output paths `i' as input paths. Only add the closures of output paths
that are specified as inputs. */ that are specified as inputs. */
for (auto & j : wantedDepOutputs) { auto getOutput = [&](const std::string & outputName) {
/* TODO (impure derivations-induced tech debt): /* TODO (impure derivations-induced tech debt):
Tracking input derivation outputs statefully through the Tracking input derivation outputs statefully through the
goals is error prone and has led to bugs. goals is error prone and has led to bugs.
@ -568,21 +582,30 @@ void DerivationGoal::inputsRealised()
a representation in the store, which is a usability problem a representation in the store, which is a usability problem
in itself. When implementing this logic entirely with lookups in itself. When implementing this logic entirely with lookups
make sure that they're cached. */ make sure that they're cached. */
if (auto outPath = get(inputDrvOutputs, { depDrvPath, j })) { if (auto outPath = get(inputDrvOutputs, { depDrvPath, outputName })) {
worker.store.computeFSClosure(*outPath, inputPaths); return *outPath;
} }
else { else {
auto outMap = worker.evalStore.queryDerivationOutputMap(depDrvPath); auto outMap = worker.evalStore.queryDerivationOutputMap(depDrvPath);
auto outMapPath = outMap.find(j); auto outMapPath = outMap.find(outputName);
if (outMapPath == outMap.end()) { if (outMapPath == outMap.end()) {
throw Error( throw Error(
"derivation '%s' requires non-existent output '%s' from input derivation '%s'", "derivation '%s' requires non-existent output '%s' from input derivation '%s'",
worker.store.printStorePath(drvPath), j, worker.store.printStorePath(depDrvPath)); worker.store.printStorePath(drvPath), outputName, worker.store.printStorePath(depDrvPath));
}
worker.store.computeFSClosure(outMapPath->second, inputPaths);
}
} }
return outMapPath->second;
} }
};
for (auto & outputName : inputNode.value)
worker.store.computeFSClosure(getOutput(outputName), inputPaths);
for (auto & [outputName, childNode] : inputNode.childMap)
accumInputPaths(getOutput(outputName), childNode);
};
for (auto & [depDrvPath, depNode] : fullDrv.inputDrvs.map)
accumInputPaths(depDrvPath, depNode);
} }
/* Second, the input sources. */ /* Second, the input sources. */
@ -1475,22 +1498,24 @@ void DerivationGoal::waiteeDone(GoalPtr waitee, ExitCode result)
if (!useDerivation) return; if (!useDerivation) return;
auto & fullDrv = *dynamic_cast<Derivation *>(drv.get()); auto & fullDrv = *dynamic_cast<Derivation *>(drv.get());
auto * dg = tryGetConcreteDrvGoal(waitee); std::optional info = tryGetConcreteDrvGoal(waitee);
if (!dg) return; if (!info) return;
const auto & [dg, drvReq] = *info;
auto outputs = fullDrv.inputDrvs.find(dg->drvPath); auto * nodeP = fullDrv.inputDrvs.findSlot(drvReq.get());
if (outputs == fullDrv.inputDrvs.end()) return; if (!nodeP) return;
auto & outputs = nodeP->value;
for (auto & outputName : outputs->second) { for (auto & outputName : outputs) {
auto buildResult = dg->getBuildResult(DerivedPath::Built { auto buildResult = dg.get().getBuildResult(DerivedPath::Built {
.drvPath = makeConstantStorePathRef(dg->drvPath), .drvPath = makeConstantStorePathRef(dg.get().drvPath),
.outputs = OutputsSpec::Names { outputName }, .outputs = OutputsSpec::Names { outputName },
}); });
if (buildResult.success()) { if (buildResult.success()) {
auto i = buildResult.builtOutputs.find(outputName); auto i = buildResult.builtOutputs.find(outputName);
if (i != buildResult.builtOutputs.end()) if (i != buildResult.builtOutputs.end())
inputDrvOutputs.insert_or_assign( inputDrvOutputs.insert_or_assign(
{ dg->drvPath, outputName }, { dg.get().drvPath, outputName },
i->second.outPath); i->second.outPath);
} }
} }

View file

@ -594,11 +594,14 @@ GoalPtr upcast_goal(std::shared_ptr<DerivationGoal> subGoal)
return subGoal; return subGoal;
} }
const DerivationGoal * tryGetConcreteDrvGoal(GoalPtr waitee) std::optional<std::pair<std::reference_wrapper<const DerivationGoal>, std::reference_wrapper<const SingleDerivedPath>>> tryGetConcreteDrvGoal(GoalPtr waitee)
{ {
auto * odg = dynamic_cast<CreateDerivationAndRealiseGoal *>(&*waitee); auto * odg = dynamic_cast<CreateDerivationAndRealiseGoal *>(&*waitee);
if (!odg) return nullptr; if (!odg) return std::nullopt;
return &*odg->concreteDrvGoal; return {{
std::cref(*odg->concreteDrvGoal),
std::cref(*odg->drvReq),
}};
} }
} }

View file

@ -49,7 +49,8 @@ typedef std::chrono::time_point<std::chrono::steady_clock> steady_time_point;
* we have made the function, written in `worker.cc` where all the goal * we have made the function, written in `worker.cc` where all the goal
* types are visible, and use it instead. * types are visible, and use it instead.
*/ */
const DerivationGoal * tryGetConcreteDrvGoal(GoalPtr waitee);
std::optional<std::pair<std::reference_wrapper<const DerivationGoal>, std::reference_wrapper<const SingleDerivedPath>>> tryGetConcreteDrvGoal(GoalPtr waitee);
/** /**
* A mapping used to remember for each child process to what goal it * A mapping used to remember for each child process to what goal it

View file

@ -136,7 +136,7 @@ StorePath writeDerivation(Store & store,
const Derivation & drv, RepairFlag repair, bool readOnly) const Derivation & drv, RepairFlag repair, bool readOnly)
{ {
auto references = drv.inputSrcs; auto references = drv.inputSrcs;
for (auto & i : drv.inputDrvs) for (auto & i : drv.inputDrvs.map)
references.insert(i.first); references.insert(i.first);
/* Note that the outputs of a derivation are *not* references /* Note that the outputs of a derivation are *not* references
(that can be missing (of course) and should not necessarily be (that can be missing (of course) and should not necessarily be
@ -154,8 +154,9 @@ static void expect(std::istream & str, std::string_view s)
{ {
char s2[s.size()]; char s2[s.size()];
str.read(s2, s.size()); str.read(s2, s.size());
if (std::string(s2, s.size()) != s) std::string_view s2View { s2, s.size() };
throw FormatError("expected string '%1%'", s); if (s2View != s)
throw FormatError("expected string '%s', got '%s'", s, s2View);
} }
@ -207,23 +208,27 @@ static bool endOfList(std::istream & str)
static StringSet parseStrings(std::istream & str, bool arePaths) static StringSet parseStrings(std::istream & str, bool arePaths)
{ {
StringSet res; StringSet res;
expect(str, "[");
while (!endOfList(str)) while (!endOfList(str))
res.insert(arePaths ? parsePath(str) : parseString(str)); res.insert(arePaths ? parsePath(str) : parseString(str));
return res; return res;
} }
static DerivationOutput parseDerivationOutput(const Store & store, static DerivationOutput parseDerivationOutput(
std::string_view pathS, std::string_view hashAlgo, std::string_view hashS) const Store & store,
std::string_view pathS, std::string_view hashAlgo, std::string_view hashS,
const ExperimentalFeatureSettings & xpSettings)
{ {
if (hashAlgo != "") { if (hashAlgo != "") {
ContentAddressMethod method = ContentAddressMethod::parsePrefix(hashAlgo); ContentAddressMethod method = ContentAddressMethod::parsePrefix(hashAlgo);
if (method == TextIngestionMethod {}) if (method == TextIngestionMethod {})
experimentalFeatureSettings.require(Xp::DynamicDerivations); xpSettings.require(Xp::DynamicDerivations);
const auto hashType = parseHashType(hashAlgo); const auto hashType = parseHashType(hashAlgo);
if (hashS == "impure") { if (hashS == "impure") {
experimentalFeatureSettings.require(Xp::ImpureDerivations); xpSettings.require(Xp::ImpureDerivations);
assert(pathS == ""); if (pathS != "")
throw FormatError("impure derivation output should not specify output path");
return DerivationOutput::Impure { return DerivationOutput::Impure {
.method = std::move(method), .method = std::move(method),
.hashType = std::move(hashType), .hashType = std::move(hashType),
@ -238,8 +243,9 @@ static DerivationOutput parseDerivationOutput(const Store & store,
}, },
}; };
} else { } else {
experimentalFeatureSettings.require(Xp::CaDerivations); xpSettings.require(Xp::CaDerivations);
assert(pathS == ""); if (pathS != "")
throw FormatError("content-addressed derivation output should not specify output path");
return DerivationOutput::CAFloating { return DerivationOutput::CAFloating {
.method = std::move(method), .method = std::move(method),
.hashType = std::move(hashType), .hashType = std::move(hashType),
@ -256,29 +262,116 @@ static DerivationOutput parseDerivationOutput(const Store & store,
} }
} }
static DerivationOutput parseDerivationOutput(const Store & store, std::istringstream & str) static DerivationOutput parseDerivationOutput(
const Store & store, std::istringstream & str,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings)
{ {
expect(str, ","); const auto pathS = parseString(str); expect(str, ","); const auto pathS = parseString(str);
expect(str, ","); const auto hashAlgo = parseString(str); expect(str, ","); const auto hashAlgo = parseString(str);
expect(str, ","); const auto hash = parseString(str); expect(str, ","); const auto hash = parseString(str);
expect(str, ")"); expect(str, ")");
return parseDerivationOutput(store, pathS, hashAlgo, hash); return parseDerivationOutput(store, pathS, hashAlgo, hash, xpSettings);
}
/**
* All ATerm Derivation format versions currently known.
*
* Unknown versions are rejected at the parsing stage.
*/
enum struct DerivationATermVersion {
/**
* Older unversioned form
*/
Traditional,
/**
* Newer versioned form; only this version so far.
*/
DynamicDerivations,
};
static DerivedPathMap<StringSet>::ChildNode parseDerivedPathMapNode(
const Store & store,
std::istringstream & str,
DerivationATermVersion version)
{
DerivedPathMap<StringSet>::ChildNode node;
auto parseNonDynamic = [&]() {
node.value = parseStrings(str, false);
};
// Older derivation should never use new form, but newer
// derivaiton can use old form.
switch (version) {
case DerivationATermVersion::Traditional:
parseNonDynamic();
break;
case DerivationATermVersion::DynamicDerivations:
switch (str.peek()) {
case '[':
parseNonDynamic();
break;
case '(':
expect(str, "(");
node.value = parseStrings(str, false);
expect(str, ",[");
while (!endOfList(str)) {
expect(str, "(");
auto outputName = parseString(str);
expect(str, ",");
node.childMap.insert_or_assign(outputName, parseDerivedPathMapNode(store, str, version));
expect(str, ")");
}
expect(str, ")");
break;
default:
throw FormatError("invalid inputDrvs entry in derivation");
}
break;
default:
// invalid format, not a parse error but internal error
assert(false);
}
return node;
} }
Derivation parseDerivation(const Store & store, std::string && s, std::string_view name) Derivation parseDerivation(
const Store & store, std::string && s, std::string_view name,
const ExperimentalFeatureSettings & xpSettings)
{ {
Derivation drv; Derivation drv;
drv.name = name; drv.name = name;
std::istringstream str(std::move(s)); std::istringstream str(std::move(s));
expect(str, "Derive(["); expect(str, "D");
DerivationATermVersion version;
switch (str.peek()) {
case 'e':
expect(str, "erive(");
version = DerivationATermVersion::Traditional;
break;
case 'r':
expect(str, "rvWithVersion(");
auto versionS = parseString(str);
if (versionS == "xp-dyn-drv") {
// Only verison we have so far
version = DerivationATermVersion::DynamicDerivations;
xpSettings.require(Xp::DynamicDerivations);
} else {
throw FormatError("Unknown derivation ATerm format version '%s'", versionS);
}
expect(str, ",");
break;
}
/* Parse the list of outputs. */ /* Parse the list of outputs. */
expect(str, "[");
while (!endOfList(str)) { while (!endOfList(str)) {
expect(str, "("); std::string id = parseString(str); expect(str, "("); std::string id = parseString(str);
auto output = parseDerivationOutput(store, str); auto output = parseDerivationOutput(store, str, xpSettings);
drv.outputs.emplace(std::move(id), std::move(output)); drv.outputs.emplace(std::move(id), std::move(output));
} }
@ -287,12 +380,12 @@ Derivation parseDerivation(const Store & store, std::string && s, std::string_vi
while (!endOfList(str)) { while (!endOfList(str)) {
expect(str, "("); expect(str, "(");
Path drvPath = parsePath(str); Path drvPath = parsePath(str);
expect(str, ",["); expect(str, ",");
drv.inputDrvs.insert_or_assign(store.parseStorePath(drvPath), parseStrings(str, false)); drv.inputDrvs.map.insert_or_assign(store.parseStorePath(drvPath), parseDerivedPathMapNode(store, str, version));
expect(str, ")"); expect(str, ")");
} }
expect(str, ",["); drv.inputSrcs = store.parseStorePathSet(parseStrings(str, true)); expect(str, ","); drv.inputSrcs = store.parseStorePathSet(parseStrings(str, true));
expect(str, ","); drv.platform = parseString(str); expect(str, ","); drv.platform = parseString(str);
expect(str, ","); drv.builder = parseString(str); expect(str, ","); drv.builder = parseString(str);
@ -376,14 +469,67 @@ static void printUnquotedStrings(std::string & res, ForwardIterator i, ForwardIt
} }
static void unparseDerivedPathMapNode(const Store & store, std::string & s, const DerivedPathMap<StringSet>::ChildNode & node)
{
s += ',';
if (node.childMap.empty()) {
printUnquotedStrings(s, node.value.begin(), node.value.end());
} else {
s += "(";
printUnquotedStrings(s, node.value.begin(), node.value.end());
s += ",[";
bool first = true;
for (auto & [outputName, childNode] : node.childMap) {
if (first) first = false; else s += ',';
s += '('; printUnquotedString(s, outputName);
unparseDerivedPathMapNode(store, s, childNode);
s += ')';
}
s += "])";
}
}
/**
* Does the derivation have a dependency on the output of a dynamic
* derivation?
*
* In other words, does it on the output of derivation that is itself an
* ouput of a derivation? This corresponds to a dependency that is an
* inductive derived path with more than one layer of
* `DerivedPath::Built`.
*/
static bool hasDynamicDrvDep(const Derivation & drv)
{
return
std::find_if(
drv.inputDrvs.map.begin(),
drv.inputDrvs.map.end(),
[](auto & kv) { return !kv.second.childMap.empty(); })
!= drv.inputDrvs.map.end();
}
std::string Derivation::unparse(const Store & store, bool maskOutputs, std::string Derivation::unparse(const Store & store, bool maskOutputs,
std::map<std::string, StringSet> * actualInputs) const DerivedPathMap<StringSet>::ChildNode::Map * actualInputs) const
{ {
std::string s; std::string s;
s.reserve(65536); s.reserve(65536);
s += "Derive([";
/* Use older unversioned form if possible, for wider compat. Use
newer form only if we need it, which we do for
`Xp::DynamicDerivations`. */
if (hasDynamicDrvDep(*this)) {
s += "DrvWithVersion(";
// Only version we have so far
printUnquotedString(s, "xp-dyn-drv");
s += ",";
} else {
s += "Derive(";
}
bool first = true; bool first = true;
s += "[";
for (auto & i : outputs) { for (auto & i : outputs) {
if (first) first = false; else s += ','; if (first) first = false; else s += ',';
s += '('; printUnquotedString(s, i.first); s += '('; printUnquotedString(s, i.first);
@ -421,17 +567,17 @@ std::string Derivation::unparse(const Store & store, bool maskOutputs,
s += "],["; s += "],[";
first = true; first = true;
if (actualInputs) { if (actualInputs) {
for (auto & i : *actualInputs) { for (auto & [drvHashModulo, childMap] : *actualInputs) {
if (first) first = false; else s += ','; if (first) first = false; else s += ',';
s += '('; printUnquotedString(s, i.first); s += '('; printUnquotedString(s, drvHashModulo);
s += ','; printUnquotedStrings(s, i.second.begin(), i.second.end()); unparseDerivedPathMapNode(store, s, childMap);
s += ')'; s += ')';
} }
} else { } else {
for (auto & i : inputDrvs) { for (auto & [drvPath, childMap] : inputDrvs.map) {
if (first) first = false; else s += ','; if (first) first = false; else s += ',';
s += '('; printUnquotedString(s, store.printStorePath(i.first)); s += '('; printUnquotedString(s, store.printStorePath(drvPath));
s += ','; printUnquotedStrings(s, i.second.begin(), i.second.end()); unparseDerivedPathMapNode(store, s, childMap);
s += ')'; s += ')';
} }
} }
@ -665,18 +811,16 @@ DrvHash hashDerivationModulo(Store & store, const Derivation & drv, bool maskOut
} }
}, drv.type().raw); }, drv.type().raw);
std::map<std::string, StringSet> inputs2; DerivedPathMap<StringSet>::ChildNode::Map inputs2;
for (auto & [drvPath, inputOutputs0] : drv.inputDrvs) { for (auto & [drvPath, node] : drv.inputDrvs.map) {
// Avoid lambda capture restriction with standard / Clang
auto & inputOutputs = inputOutputs0;
const auto & res = pathDerivationModulo(store, drvPath); const auto & res = pathDerivationModulo(store, drvPath);
if (res.kind == DrvHash::Kind::Deferred) if (res.kind == DrvHash::Kind::Deferred)
kind = DrvHash::Kind::Deferred; kind = DrvHash::Kind::Deferred;
for (auto & outputName : inputOutputs) { for (auto & outputName : node.value) {
const auto h = get(res.hashes, outputName); const auto h = get(res.hashes, outputName);
if (!h) if (!h)
throw Error("no hash for output '%s' of derivation '%s'", outputName, drv.name); throw Error("no hash for output '%s' of derivation '%s'", outputName, drv.name);
inputs2[h->to_string(Base16, false)].insert(outputName); inputs2[h->to_string(Base16, false)].value.insert(outputName);
} }
} }
@ -706,7 +850,7 @@ static DerivationOutput readDerivationOutput(Source & in, const Store & store)
const auto hashAlgo = readString(in); const auto hashAlgo = readString(in);
const auto hash = readString(in); const auto hash = readString(in);
return parseDerivationOutput(store, pathS, hashAlgo, hash); return parseDerivationOutput(store, pathS, hashAlgo, hash, experimentalFeatureSettings);
} }
StringSet BasicDerivation::outputNames() const StringSet BasicDerivation::outputNames() const
@ -821,6 +965,8 @@ std::string hashPlaceholder(const OutputNameView outputName)
static void rewriteDerivation(Store & store, BasicDerivation & drv, const StringMap & rewrites) static void rewriteDerivation(Store & store, BasicDerivation & drv, const StringMap & rewrites)
{ {
debug("Rewriting the derivation");
for (auto & rewrite : rewrites) { for (auto & rewrite : rewrites) {
debug("rewriting %s as %s", rewrite.first, rewrite.second); debug("rewriting %s as %s", rewrite.first, rewrite.second);
} }
@ -859,14 +1005,70 @@ std::optional<BasicDerivation> Derivation::tryResolve(Store & store) const
{ {
std::map<std::pair<StorePath, std::string>, StorePath> inputDrvOutputs; std::map<std::pair<StorePath, std::string>, StorePath> inputDrvOutputs;
for (auto & input : inputDrvs) std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accum;
for (auto & [outputName, outputPath] : store.queryPartialDerivationOutputMap(input.first)) accum = [&](auto & inputDrv, auto & node) {
if (outputPath) for (auto & [outputName, outputPath] : store.queryPartialDerivationOutputMap(inputDrv)) {
inputDrvOutputs.insert_or_assign({input.first, outputName}, *outputPath); if (outputPath) {
inputDrvOutputs.insert_or_assign({inputDrv, outputName}, *outputPath);
if (auto p = get(node.childMap, outputName))
accum(*outputPath, *p);
}
}
};
for (auto & [inputDrv, node] : inputDrvs.map)
accum(inputDrv, node);
return tryResolve(store, inputDrvOutputs); return tryResolve(store, inputDrvOutputs);
} }
static bool tryResolveInput(
Store & store, StorePathSet & inputSrcs, StringMap & inputRewrites,
const DownstreamPlaceholder * placeholderOpt,
const StorePath & inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode,
const std::map<std::pair<StorePath, std::string>, StorePath> & inputDrvOutputs)
{
auto getOutput = [&](const std::string & outputName) {
auto * actualPathOpt = get(inputDrvOutputs, { inputDrv, outputName });
if (!actualPathOpt)
warn("output %s of input %s missing, aborting the resolving",
outputName,
store.printStorePath(inputDrv)
);
return actualPathOpt;
};
auto getPlaceholder = [&](const std::string & outputName) {
return placeholderOpt
? DownstreamPlaceholder::unknownDerivation(*placeholderOpt, outputName)
: DownstreamPlaceholder::unknownCaOutput(inputDrv, outputName);
};
for (auto & outputName : inputNode.value) {
auto actualPathOpt = getOutput(outputName);
if (!actualPathOpt) return false;
auto actualPath = *actualPathOpt;
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
inputRewrites.emplace(
getPlaceholder(outputName).render(),
store.printStorePath(actualPath));
}
inputSrcs.insert(std::move(actualPath));
}
for (auto & [outputName, childNode] : inputNode.childMap) {
auto actualPathOpt = getOutput(outputName);
if (!actualPathOpt) return false;
auto actualPath = *actualPathOpt;
auto nextPlaceholder = getPlaceholder(outputName);
if (!tryResolveInput(store, inputSrcs, inputRewrites,
&nextPlaceholder, actualPath, childNode,
inputDrvOutputs))
return false;
}
return true;
}
std::optional<BasicDerivation> Derivation::tryResolve( std::optional<BasicDerivation> Derivation::tryResolve(
Store & store, Store & store,
const std::map<std::pair<StorePath, std::string>, StorePath> & inputDrvOutputs) const const std::map<std::pair<StorePath, std::string>, StorePath> & inputDrvOutputs) const
@ -876,23 +1078,10 @@ std::optional<BasicDerivation> Derivation::tryResolve(
// Input paths that we'll want to rewrite in the derivation // Input paths that we'll want to rewrite in the derivation
StringMap inputRewrites; StringMap inputRewrites;
for (auto & [inputDrv, inputOutputs] : inputDrvs) { for (auto & [inputDrv, inputNode] : inputDrvs.map)
for (auto & outputName : inputOutputs) { if (!tryResolveInput(store, resolved.inputSrcs, inputRewrites,
if (auto actualPath = get(inputDrvOutputs, { inputDrv, outputName })) { nullptr, inputDrv, inputNode, inputDrvOutputs))
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) { return std::nullopt;
inputRewrites.emplace(
DownstreamPlaceholder::unknownCaOutput(inputDrv, outputName).render(),
store.printStorePath(*actualPath));
}
resolved.inputSrcs.insert(*actualPath);
} else {
warn("output '%s' of input '%s' missing, aborting the resolving",
outputName,
store.printStorePath(inputDrv));
return {};
}
}
}
rewriteDerivation(store, resolved, inputRewrites); rewriteDerivation(store, resolved, inputRewrites);
@ -1080,11 +1269,26 @@ nlohmann::json Derivation::toJSON(const Store & store) const
inputsList.emplace_back(store.printStorePath(input)); inputsList.emplace_back(store.printStorePath(input));
} }
{
std::function<nlohmann::json(const DerivedPathMap<StringSet>::ChildNode &)> doInput;
doInput = [&](const auto & inputNode) {
auto value = nlohmann::json::object();
value["outputs"] = inputNode.value;
{
auto next = nlohmann::json::object();
for (auto & [outputId, childNode] : inputNode.childMap)
next[outputId] = doInput(childNode);
value["dynamicOutputs"] = std::move(next);
}
return value;
};
{ {
auto& inputDrvsObj = res["inputDrvs"]; auto& inputDrvsObj = res["inputDrvs"];
inputDrvsObj = nlohmann::json ::object(); inputDrvsObj = nlohmann::json::object();
for (auto & input : inputDrvs) for (auto & [inputDrv, inputNode] : inputDrvs.map) {
inputDrvsObj[store.printStorePath(input.first)] = input.second; inputDrvsObj[store.printStorePath(inputDrv)] = doInput(inputNode);
}
}
} }
res["system"] = platform; res["system"] = platform;
@ -1098,7 +1302,8 @@ nlohmann::json Derivation::toJSON(const Store & store) const
Derivation Derivation::fromJSON( Derivation Derivation::fromJSON(
const Store & store, const Store & store,
const nlohmann::json & json) const nlohmann::json & json,
const ExperimentalFeatureSettings & xpSettings)
{ {
using nlohmann::detail::value_t; using nlohmann::detail::value_t;
@ -1130,12 +1335,21 @@ Derivation Derivation::fromJSON(
} }
try { try {
auto & inputDrvsObj = ensureType(valueAt(json, "inputDrvs"), value_t::object); std::function<DerivedPathMap<StringSet>::ChildNode(const nlohmann::json &)> doInput;
for (auto & [inputDrvPath, inputOutputs] : inputDrvsObj.items()) { doInput = [&](const auto & json) {
ensureType(inputOutputs, value_t::array); DerivedPathMap<StringSet>::ChildNode node;
res.inputDrvs[store.parseStorePath(inputDrvPath)] = node.value = static_cast<const StringSet &>(
static_cast<const StringSet &>(inputOutputs); ensureType(valueAt(json, "outputs"), value_t::array));
for (auto & [outputId, childNode] : ensureType(valueAt(json, "dynamicOutputs"), value_t::object).items()) {
xpSettings.require(Xp::DynamicDerivations);
node.childMap[outputId] = doInput(childNode);
} }
return node;
};
auto & inputDrvsObj = ensureType(valueAt(json, "inputDrvs"), value_t::object);
for (auto & [inputDrvPath, inputOutputs] : inputDrvsObj.items())
res.inputDrvs.map[store.parseStorePath(inputDrvPath)] =
doInput(inputOutputs);
} catch (Error & e) { } catch (Error & e) {
e.addTrace({}, "while reading key 'inputDrvs'"); e.addTrace({}, "while reading key 'inputDrvs'");
throw; throw;

View file

@ -6,7 +6,7 @@
#include "hash.hh" #include "hash.hh"
#include "content-address.hh" #include "content-address.hh"
#include "repair-flag.hh" #include "repair-flag.hh"
#include "derived-path.hh" #include "derived-path-map.hh"
#include "sync.hh" #include "sync.hh"
#include "comparator.hh" #include "comparator.hh"
#include "variant-wrapper.hh" #include "variant-wrapper.hh"
@ -323,13 +323,13 @@ struct Derivation : BasicDerivation
/** /**
* inputs that are sub-derivations * inputs that are sub-derivations
*/ */
DerivationInputs inputDrvs; DerivedPathMap<std::set<OutputName>> inputDrvs;
/** /**
* Print a derivation. * Print a derivation.
*/ */
std::string unparse(const Store & store, bool maskOutputs, std::string unparse(const Store & store, bool maskOutputs,
std::map<std::string, StringSet> * actualInputs = nullptr) const; DerivedPathMap<StringSet>::ChildNode::Map * actualInputs = nullptr) const;
/** /**
* Return the underlying basic derivation but with these changes: * Return the underlying basic derivation but with these changes:
@ -368,7 +368,8 @@ struct Derivation : BasicDerivation
nlohmann::json toJSON(const Store & store) const; nlohmann::json toJSON(const Store & store) const;
static Derivation fromJSON( static Derivation fromJSON(
const Store & store, const Store & store,
const nlohmann::json & json); const nlohmann::json & json,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
GENERATE_CMP(Derivation, GENERATE_CMP(Derivation,
static_cast<const BasicDerivation &>(*me), static_cast<const BasicDerivation &>(*me),
@ -389,7 +390,11 @@ StorePath writeDerivation(Store & store,
/** /**
* Read a derivation from a file. * Read a derivation from a file.
*/ */
Derivation parseDerivation(const Store & store, std::string && s, std::string_view name); Derivation parseDerivation(
const Store & store,
std::string && s,
std::string_view name,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
/** /**
* \todo Remove. * \todo Remove.

View file

@ -21,6 +21,32 @@ typename DerivedPathMap<V>::ChildNode & DerivedPathMap<V>::ensureSlot(const Sing
return initIter(k); return initIter(k);
} }
template<typename V>
typename DerivedPathMap<V>::ChildNode * DerivedPathMap<V>::findSlot(const SingleDerivedPath & k)
{
std::function<ChildNode *(const SingleDerivedPath & )> initIter;
initIter = [&](const auto & k) {
return std::visit(overloaded {
[&](const SingleDerivedPath::Opaque & bo) {
auto it = map.find(bo.path);
return it != map.end()
? &it->second
: nullptr;
},
[&](const SingleDerivedPath::Built & bfd) {
auto * n = initIter(*bfd.drvPath);
if (!n) return (ChildNode *)nullptr;
auto it = n->childMap.find(bfd.output);
return it != n->childMap.end()
? &it->second
: nullptr;
},
}, k.raw());
};
return initIter(k);
}
} }
// instantiations // instantiations
@ -30,4 +56,17 @@ namespace nix {
template struct DerivedPathMap<std::weak_ptr<CreateDerivationAndRealiseGoal>>; template struct DerivedPathMap<std::weak_ptr<CreateDerivationAndRealiseGoal>>;
} GENERATE_CMP_EXT(
template<>,
DerivedPathMap<std::set<std::string>>::ChildNode,
me->value,
me->childMap);
GENERATE_CMP_EXT(
template<>,
DerivedPathMap<std::set<std::string>>,
me->map);
template struct DerivedPathMap<std::set<std::string>>;
};

View file

@ -48,6 +48,8 @@ struct DerivedPathMap {
* The map of the root node. * The map of the root node.
*/ */
Map childMap; Map childMap;
DECLARE_CMP(ChildNode);
}; };
/** /**
@ -60,6 +62,8 @@ struct DerivedPathMap {
*/ */
Map map; Map map;
DECLARE_CMP(DerivedPathMap);
/** /**
* Find the node for `k`, creating it if needed. * Find the node for `k`, creating it if needed.
* *
@ -68,6 +72,27 @@ struct DerivedPathMap {
* by changing this node. * by changing this node.
*/ */
ChildNode & ensureSlot(const SingleDerivedPath & k); ChildNode & ensureSlot(const SingleDerivedPath & k);
/**
* Like `ensureSlot` but does not create the slot if it doesn't exist.
*
* Read the entire description of `ensureSlot` to understand an
* important caveat here that "have slot" does *not* imply "key is
* set in map". To ensure a key is set one would need to get the
* child node (with `findSlot` or `ensureSlot`) *and* check the
* `ChildNode::value`.
*/
ChildNode * findSlot(const SingleDerivedPath & k);
}; };
DECLARE_CMP_EXT(
template<>,
DerivedPathMap<std::set<std::string>>::,
DerivedPathMap<std::set<std::string>>);
DECLARE_CMP_EXT(
template<>,
DerivedPathMap<std::set<std::string>>::ChildNode::,
DerivedPathMap<std::set<std::string>>::ChildNode);
} }

View file

@ -554,7 +554,7 @@ public:
R"( R"(
This option determines the maximum size of the `tmpfs` filesystem This option determines the maximum size of the `tmpfs` filesystem
mounted on `/dev/shm` in Linux sandboxes. For the format, see the mounted on `/dev/shm` in Linux sandboxes. For the format, see the
description of the `size` option of `tmpfs` in mount8. The default description of the `size` option of `tmpfs` in mount(8). The default
is `50%`. is `50%`.
)"}; )"};

View file

@ -125,14 +125,26 @@ void Store::queryMissing(const std::vector<DerivedPath> & targets,
std::function<void(DerivedPath)> doPath; std::function<void(DerivedPath)> doPath;
std::function<void(ref<SingleDerivedPath>, const DerivedPathMap<StringSet>::ChildNode &)> enqueueDerivedPaths;
enqueueDerivedPaths = [&](ref<SingleDerivedPath> inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
if (!inputNode.value.empty())
pool.enqueue(std::bind(doPath, DerivedPath::Built { inputDrv, inputNode.value }));
for (const auto & [outputName, childNode] : inputNode.childMap)
enqueueDerivedPaths(
make_ref<SingleDerivedPath>(SingleDerivedPath::Built { inputDrv, outputName }),
childNode);
};
auto mustBuildDrv = [&](const StorePath & drvPath, const Derivation & drv) { auto mustBuildDrv = [&](const StorePath & drvPath, const Derivation & drv) {
{ {
auto state(state_.lock()); auto state(state_.lock());
state->willBuild.insert(drvPath); state->willBuild.insert(drvPath);
} }
for (auto & i : drv.inputDrvs) for (const auto & [inputDrv, inputNode] : drv.inputDrvs.map) {
pool.enqueue(std::bind(doPath, DerivedPath::Built { makeConstantStorePathRef(i.first), i.second })); enqueueDerivedPaths(makeConstantStorePathRef(inputDrv), inputNode);
}
}; };
auto checkOutput = [&]( auto checkOutput = [&](
@ -322,10 +334,13 @@ std::map<DrvOutput, StorePath> drvOutputReferences(
{ {
std::set<Realisation> inputRealisations; std::set<Realisation> inputRealisations;
for (const auto & [inputDrv, outputNames] : drv.inputDrvs) { std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accumRealisations;
const auto outputHashes =
accumRealisations = [&](const StorePath & inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
if (!inputNode.value.empty()) {
auto outputHashes =
staticOutputHashes(store, store.readDerivation(inputDrv)); staticOutputHashes(store, store.readDerivation(inputDrv));
for (const auto & outputName : outputNames) { for (const auto & outputName : inputNode.value) {
auto outputHash = get(outputHashes, outputName); auto outputHash = get(outputHashes, outputName);
if (!outputHash) if (!outputHash)
throw Error( throw Error(
@ -335,11 +350,25 @@ std::map<DrvOutput, StorePath> drvOutputReferences(
DrvOutput{*outputHash, outputName}); DrvOutput{*outputHash, outputName});
if (!thisRealisation) if (!thisRealisation)
throw Error( throw Error(
"output '%s' of derivation '%s' isn't built", outputName, "output '%s' of derivation '%s' isnt built", outputName,
store.printStorePath(inputDrv)); store.printStorePath(inputDrv));
inputRealisations.insert(*thisRealisation); inputRealisations.insert(*thisRealisation);
} }
} }
if (!inputNode.value.empty()) {
auto d = makeConstantStorePathRef(inputDrv);
for (const auto & [outputName, childNode] : inputNode.childMap) {
SingleDerivedPath next = SingleDerivedPath::Built { d, outputName };
accumRealisations(
// TODO deep resolutions for dynamic derivations, issue #8947, would go here.
resolveDerivedPath(store, next),
childNode);
}
}
};
for (const auto & [inputDrv, inputNode] : drv.inputDrvs.map)
accumRealisations(inputDrv, inputNode);
auto info = store.queryPathInfo(outputPath); auto info = store.queryPathInfo(outputPath);

View file

@ -172,7 +172,24 @@ void RemoteStore::ConnectionHandle::processStderr(Sink * sink, Source * source,
auto ex = handle->processStderr(sink, source, flush); auto ex = handle->processStderr(sink, source, flush);
if (ex) { if (ex) {
daemonException = true; daemonException = true;
try {
std::rethrow_exception(ex); std::rethrow_exception(ex);
} catch (const Error & e) {
// Nix versions before #4628 did not have an adequate behavior for reporting that the derivation format was upgraded.
// To avoid having to add compatibility logic in many places, we expect to catch almost all occurrences of the
// old incomprehensible error here, so that we can explain to users what's going on when their daemon is
// older than #4628 (2023).
if (experimentalFeatureSettings.isEnabled(Xp::DynamicDerivations) &&
GET_PROTOCOL_MINOR(handle->daemonVersion) <= 35)
{
auto m = e.msg();
if (m.find("parsing derivation") != std::string::npos &&
m.find("expected string") != std::string::npos &&
m.find("Derive([") != std::string::npos)
throw Error("%s, this might be because the daemon is too old to understand dependencies on dynamic derivations. Check to see if the raw dervation is in the form '%s'", std::move(m), "DrvWithVersion(..)");
}
throw;
}
} }
} }

View file

@ -42,6 +42,26 @@ class ImpureDerivationTest : public DerivationTest
} }
}; };
TEST_F(DerivationTest, BadATerm_version) {
ASSERT_THROW(
parseDerivation(
*store,
R"(DrvWithVersion("invalid-version",[],[("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv",["cat","dog"])],["/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"],"wasm-sel4","foo",["bar","baz"],[("BIG_BAD","WOLF")]))",
"whatever",
mockXpSettings),
FormatError);
}
TEST_F(DynDerivationTest, BadATerm_oldVersionDynDeps) {
ASSERT_THROW(
parseDerivation(
*store,
R"(Derive([],[("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv",(["cat","dog"],[("cat",["kitten"]),("goose",["gosling"])]))],["/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"],"wasm-sel4","foo",["bar","baz"],[("BIG_BAD","WOLF")]))",
"dyn-dep-derivation",
mockXpSettings),
FormatError);
}
#define TEST_JSON(FIXTURE, NAME, STR, VAL, DRV_NAME, OUTPUT_NAME) \ #define TEST_JSON(FIXTURE, NAME, STR, VAL, DRV_NAME, OUTPUT_NAME) \
TEST_F(FIXTURE, DerivationOutput_ ## NAME ## _to_json) { \ TEST_F(FIXTURE, DerivationOutput_ ## NAME ## _to_json) { \
using nlohmann::literals::operator "" _json; \ using nlohmann::literals::operator "" _json; \
@ -143,60 +163,63 @@ TEST_JSON(ImpureDerivationTest, impure,
#undef TEST_JSON #undef TEST_JSON
#define TEST_JSON(NAME, STR, VAL, DRV_NAME) \ #define TEST_JSON(FIXTURE, NAME, STR, VAL) \
TEST_F(DerivationTest, Derivation_ ## NAME ## _to_json) { \ TEST_F(FIXTURE, Derivation_ ## NAME ## _to_json) { \
using nlohmann::literals::operator "" _json; \ using nlohmann::literals::operator "" _json; \
ASSERT_EQ( \ ASSERT_EQ( \
STR ## _json, \ STR ## _json, \
(Derivation { VAL }).toJSON(*store)); \ (VAL).toJSON(*store)); \
} \ } \
\ \
TEST_F(DerivationTest, Derivation_ ## NAME ## _from_json) { \ TEST_F(FIXTURE, Derivation_ ## NAME ## _from_json) { \
using nlohmann::literals::operator "" _json; \ using nlohmann::literals::operator "" _json; \
ASSERT_EQ( \ ASSERT_EQ( \
Derivation { VAL }, \ (VAL), \
Derivation::fromJSON( \ Derivation::fromJSON( \
*store, \ *store, \
STR ## _json)); \ STR ## _json, \
mockXpSettings)); \
} }
TEST_JSON(simple, #define TEST_ATERM(FIXTURE, NAME, STR, VAL, DRV_NAME) \
R"({ TEST_F(FIXTURE, Derivation_ ## NAME ## _to_aterm) { \
"name": "my-derivation", ASSERT_EQ( \
"inputSrcs": [ STR, \
"/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1" (VAL).unparse(*store, false)); \
], } \
"inputDrvs": { \
"/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv": [ TEST_F(FIXTURE, Derivation_ ## NAME ## _from_aterm) { \
"cat", auto parsed = parseDerivation( \
"dog" *store, \
] STR, \
}, DRV_NAME, \
"system": "wasm-sel4", mockXpSettings); \
"builder": "foo", ASSERT_EQ( \
"args": [ (VAL).toJSON(*store), \
"bar", parsed.toJSON(*store)); \
"baz" ASSERT_EQ( \
], (VAL), \
"env": { parsed); \
"BIG_BAD": "WOLF" }
},
"outputs": {} Derivation makeSimpleDrv(const Store & store) {
})",
({
Derivation drv; Derivation drv;
drv.name = "my-derivation"; drv.name = "simple-derivation";
drv.inputSrcs = { drv.inputSrcs = {
store->parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"), store.parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"),
}; };
drv.inputDrvs = { drv.inputDrvs = {
.map = {
{ {
store->parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv"), store.parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv"),
{ {
.value = {
"cat", "cat",
"dog", "dog",
}, },
} },
},
},
}; };
drv.platform = "wasm-sel4"; drv.platform = "wasm-sel4";
drv.builder = "foo"; drv.builder = "foo";
@ -210,10 +233,137 @@ TEST_JSON(simple,
"WOLF", "WOLF",
}, },
}; };
drv; return drv;
}), }
"drv-name")
TEST_JSON(DerivationTest, simple,
R"({
"name": "simple-derivation",
"inputSrcs": [
"/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"
],
"inputDrvs": {
"/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv": {
"dynamicOutputs": {},
"outputs": [
"cat",
"dog"
]
}
},
"system": "wasm-sel4",
"builder": "foo",
"args": [
"bar",
"baz"
],
"env": {
"BIG_BAD": "WOLF"
},
"outputs": {}
})",
makeSimpleDrv(*store))
TEST_ATERM(DerivationTest, simple,
R"(Derive([],[("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv",["cat","dog"])],["/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"],"wasm-sel4","foo",["bar","baz"],[("BIG_BAD","WOLF")]))",
makeSimpleDrv(*store),
"simple-derivation")
Derivation makeDynDepDerivation(const Store & store) {
Derivation drv;
drv.name = "dyn-dep-derivation";
drv.inputSrcs = {
store.parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"),
};
drv.inputDrvs = {
.map = {
{
store.parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv"),
DerivedPathMap<StringSet>::ChildNode {
.value = {
"cat",
"dog",
},
.childMap = {
{
"cat",
DerivedPathMap<StringSet>::ChildNode {
.value = {
"kitten",
},
},
},
{
"goose",
DerivedPathMap<StringSet>::ChildNode {
.value = {
"gosling",
},
},
},
},
},
},
},
};
drv.platform = "wasm-sel4";
drv.builder = "foo";
drv.args = {
"bar",
"baz",
};
drv.env = {
{
"BIG_BAD",
"WOLF",
},
};
return drv;
}
TEST_JSON(DynDerivationTest, dynDerivationDeps,
R"({
"name": "dyn-dep-derivation",
"inputSrcs": [
"/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"
],
"inputDrvs": {
"/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv": {
"dynamicOutputs": {
"cat": {
"dynamicOutputs": {},
"outputs": ["kitten"]
},
"goose": {
"dynamicOutputs": {},
"outputs": ["gosling"]
}
},
"outputs": [
"cat",
"dog"
]
}
},
"system": "wasm-sel4",
"builder": "foo",
"args": [
"bar",
"baz"
],
"env": {
"BIG_BAD": "WOLF"
},
"outputs": {}
})",
makeDynDepDerivation(*store))
TEST_ATERM(DynDerivationTest, dynDerivationDeps,
R"(DrvWithVersion("xp-dyn-drv",[],[("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv",(["cat","dog"],[("cat",["kitten"]),("goose",["gosling"])]))],["/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"],"wasm-sel4","foo",["bar","baz"],[("BIG_BAD","WOLF")]))",
makeDynDepDerivation(*store),
"dyn-dep-derivation")
#undef TEST_JSON #undef TEST_JSON
#undef TEST_ATERM
} }

View file

@ -1,21 +1,48 @@
#pragma once #pragma once
///@file ///@file
#define DECLARE_ONE_CMP(PRE, QUAL, COMPARATOR, MY_TYPE) \
PRE bool QUAL operator COMPARATOR(const MY_TYPE & other) const;
#define DECLARE_EQUAL(prefix, qualification, my_type) \
DECLARE_ONE_CMP(prefix, qualification, ==, my_type)
#define DECLARE_LEQ(prefix, qualification, my_type) \
DECLARE_ONE_CMP(prefix, qualification, <, my_type)
#define DECLARE_NEQ(prefix, qualification, my_type) \
DECLARE_ONE_CMP(prefix, qualification, !=, my_type)
#define GENERATE_ONE_CMP(PRE, QUAL, COMPARATOR, MY_TYPE, ...) \
PRE bool QUAL operator COMPARATOR(const MY_TYPE & other) const { \
__VA_OPT__(const MY_TYPE * me = this;) \
auto fields1 = std::make_tuple( __VA_ARGS__ ); \
__VA_OPT__(me = &other;) \
auto fields2 = std::make_tuple( __VA_ARGS__ ); \
return fields1 COMPARATOR fields2; \
}
#define GENERATE_EQUAL(prefix, qualification, my_type, args...) \
GENERATE_ONE_CMP(prefix, qualification, ==, my_type, args)
#define GENERATE_LEQ(prefix, qualification, my_type, args...) \
GENERATE_ONE_CMP(prefix, qualification, <, my_type, args)
#define GENERATE_NEQ(prefix, qualification, my_type, args...) \
GENERATE_ONE_CMP(prefix, qualification, !=, my_type, args)
/** /**
* Declare comparison methods without defining them. * Declare comparison methods without defining them.
*/ */
#define DECLARE_ONE_CMP(COMPARATOR, MY_TYPE) \
bool operator COMPARATOR(const MY_TYPE & other) const;
#define DECLARE_EQUAL(my_type) \
DECLARE_ONE_CMP(==, my_type)
#define DECLARE_LEQ(my_type) \
DECLARE_ONE_CMP(<, my_type)
#define DECLARE_NEQ(my_type) \
DECLARE_ONE_CMP(!=, my_type)
#define DECLARE_CMP(my_type) \ #define DECLARE_CMP(my_type) \
DECLARE_EQUAL(my_type) \ DECLARE_EQUAL(,,my_type) \
DECLARE_LEQ(my_type) \ DECLARE_LEQ(,,my_type) \
DECLARE_NEQ(my_type) DECLARE_NEQ(,,my_type)
/**
* @param prefix This is for something before each declaration like
* `template<classname Foo>`.
*
* @param my_type the type are defining operators for.
*/
#define DECLARE_CMP_EXT(prefix, qualification, my_type) \
DECLARE_EQUAL(prefix, qualification, my_type) \
DECLARE_LEQ(prefix, qualification, my_type) \
DECLARE_NEQ(prefix, qualification, my_type)
/** /**
* Awful hacky generation of the comparison operators by doing a lexicographic * Awful hacky generation of the comparison operators by doing a lexicographic
@ -33,18 +60,18 @@
* } * }
* ``` * ```
*/ */
#define GENERATE_ONE_CMP(COMPARATOR, MY_TYPE, ...) \
bool operator COMPARATOR(const MY_TYPE& other) const { \
__VA_OPT__(const MY_TYPE* me = this;) \
auto fields1 = std::make_tuple( __VA_ARGS__ ); \
__VA_OPT__(me = &other;) \
auto fields2 = std::make_tuple( __VA_ARGS__ ); \
return fields1 COMPARATOR fields2; \
}
#define GENERATE_EQUAL(args...) GENERATE_ONE_CMP(==, args)
#define GENERATE_LEQ(args...) GENERATE_ONE_CMP(<, args)
#define GENERATE_NEQ(args...) GENERATE_ONE_CMP(!=, args)
#define GENERATE_CMP(args...) \ #define GENERATE_CMP(args...) \
GENERATE_EQUAL(args) \ GENERATE_EQUAL(,,args) \
GENERATE_LEQ(args) \ GENERATE_LEQ(,,args) \
GENERATE_NEQ(args) GENERATE_NEQ(,,args)
/**
* @param prefix This is for something before each declaration like
* `template<classname Foo>`.
*
* @param my_type the type are defining operators for.
*/
#define GENERATE_CMP_EXT(prefix, my_type, args...) \
GENERATE_EQUAL(prefix, my_type ::, my_type, args) \
GENERATE_LEQ(prefix, my_type ::, my_type, args) \
GENERATE_NEQ(prefix, my_type ::, my_type, args)

View file

@ -220,8 +220,8 @@ struct JSONLogger : Logger {
json["level"] = lvl; json["level"] = lvl;
json["type"] = type; json["type"] = type;
json["text"] = s; json["text"] = s;
json["parent"] = parent;
addFields(json, fields); addFields(json, fields);
// FIXME: handle parent
write(json); write(json);
} }

View file

@ -60,7 +60,7 @@ void initLibUtil() {
bool caught = false; bool caught = false;
try { try {
throwExceptionSelfCheck(); throwExceptionSelfCheck();
} catch (nix::Error _e) { } catch (const nix::Error & _e) {
caught = true; caught = true;
} }
// This is not actually the main point of this check, but let's make sure anyway: // This is not actually the main point of this check, but let's make sure anyway:

View file

@ -406,8 +406,22 @@ static void main_nix_build(int argc, char * * argv)
} }
} }
std::function<void(ref<SingleDerivedPath>, const DerivedPathMap<StringSet>::ChildNode &)> accumDerivedPath;
accumDerivedPath = [&](ref<SingleDerivedPath> inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
if (!inputNode.value.empty())
pathsToBuild.push_back(DerivedPath::Built {
.drvPath = inputDrv,
.outputs = OutputsSpec::Names { inputNode.value },
});
for (const auto & [outputName, childNode] : inputNode.childMap)
accumDerivedPath(
make_ref<SingleDerivedPath>(SingleDerivedPath::Built { inputDrv, outputName }),
childNode);
};
// Build or fetch all dependencies of the derivation. // Build or fetch all dependencies of the derivation.
for (const auto & [inputDrv0, inputOutputs] : drv.inputDrvs) { for (const auto & [inputDrv0, inputNode] : drv.inputDrvs.map) {
// To get around lambda capturing restrictions in the // To get around lambda capturing restrictions in the
// standard. // standard.
const auto & inputDrv = inputDrv0; const auto & inputDrv = inputDrv0;
@ -416,10 +430,7 @@ static void main_nix_build(int argc, char * * argv)
return !std::regex_search(store->printStorePath(inputDrv), std::regex(exclude)); return !std::regex_search(store->printStorePath(inputDrv), std::regex(exclude));
})) }))
{ {
pathsToBuild.push_back(DerivedPath::Built { accumDerivedPath(makeConstantStorePathRef(inputDrv), inputNode);
.drvPath = makeConstantStorePathRef(inputDrv),
.outputs = OutputsSpec::Names { inputOutputs },
});
pathsToCopy.insert(inputDrv); pathsToCopy.insert(inputDrv);
} }
} }
@ -482,13 +493,21 @@ static void main_nix_build(int argc, char * * argv)
if (env.count("__json")) { if (env.count("__json")) {
StorePathSet inputs; StorePathSet inputs;
for (auto & [depDrvPath, wantedDepOutputs] : drv.inputDrvs) {
auto outputs = evalStore->queryPartialDerivationOutputMap(depDrvPath); std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accumInputClosure;
for (auto & i : wantedDepOutputs) {
accumInputClosure = [&](const StorePath & inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
auto outputs = evalStore->queryPartialDerivationOutputMap(inputDrv);
for (auto & i : inputNode.value) {
auto o = outputs.at(i); auto o = outputs.at(i);
store->computeFSClosure(*o, inputs); store->computeFSClosure(*o, inputs);
} }
} for (const auto & [outputName, childNode] : inputNode.childMap)
accumInputClosure(*outputs.at(outputName), childNode);
};
for (const auto & [inputDrv, inputNode] : drv.inputDrvs.map)
accumInputClosure(inputDrv, inputNode);
ParsedDerivation parsedDrv(drvInfo.requireDrvPath(), drv); ParsedDerivation parsedDrv(drvInfo.requireDrvPath(), drv);

View file

@ -283,7 +283,7 @@ static void opQuery(Strings opFlags, Strings opArgs)
{ {
enum QueryType enum QueryType
{ qOutputs, qRequisites, qReferences, qReferrers { qOutputs, qRequisites, qReferences, qReferrers
, qReferrersClosure, qDeriver, qBinding, qHash, qSize , qReferrersClosure, qDeriver, qValidDerivers, qBinding, qHash, qSize
, qTree, qGraph, qGraphML, qResolve, qRoots }; , qTree, qGraph, qGraphML, qResolve, qRoots };
std::optional<QueryType> query; std::optional<QueryType> query;
bool useOutput = false; bool useOutput = false;
@ -299,6 +299,7 @@ static void opQuery(Strings opFlags, Strings opArgs)
else if (i == "--referrers" || i == "--referers") query = qReferrers; else if (i == "--referrers" || i == "--referers") query = qReferrers;
else if (i == "--referrers-closure" || i == "--referers-closure") query = qReferrersClosure; else if (i == "--referrers-closure" || i == "--referers-closure") query = qReferrersClosure;
else if (i == "--deriver" || i == "-d") query = qDeriver; else if (i == "--deriver" || i == "-d") query = qDeriver;
else if (i == "--valid-derivers") query = qValidDerivers;
else if (i == "--binding" || i == "-b") { else if (i == "--binding" || i == "-b") {
if (opArgs.size() == 0) if (opArgs.size() == 0)
throw UsageError("expected binding name"); throw UsageError("expected binding name");
@ -372,6 +373,21 @@ static void opQuery(Strings opFlags, Strings opArgs)
} }
break; break;
case qValidDerivers: {
StorePathSet result;
for (auto & i : opArgs) {
auto derivers = store->queryValidDerivers(store->followLinksToStorePath(i));
for (const auto &i: derivers) {
result.insert(i);
}
}
auto sorted = store->topoSortPaths(result);
for (StorePaths::reverse_iterator i = sorted.rbegin();
i != sorted.rend(); ++i)
cout << fmt("%s\n", store->printStorePath(*i));
break;
}
case qBinding: case qBinding:
for (auto & i : opArgs) { for (auto & i : opArgs) {
auto path = useDeriver(store->followLinksToStorePath(i)); auto path = useDeriver(store->followLinksToStorePath(i));

View file

@ -20,15 +20,22 @@ StringPairs resolveRewrites(
const std::vector<BuiltPathWithResult> & dependencies) const std::vector<BuiltPathWithResult> & dependencies)
{ {
StringPairs res; StringPairs res;
for (auto & dep : dependencies) if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
if (auto drvDep = std::get_if<BuiltPathBuilt>(&dep.path)) for (auto & dep : dependencies) {
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) if (auto drvDep = std::get_if<BuiltPathBuilt>(&dep.path)) {
for (auto & [ outputName, outputPath ] : drvDep->outputs) for (auto & [ outputName, outputPath ] : drvDep->outputs) {
res.emplace( res.emplace(
DownstreamPlaceholder::unknownCaOutput( DownstreamPlaceholder::fromSingleDerivedPathBuilt(
drvDep->drvPath->outPath(), outputName).render(), SingleDerivedPath::Built {
.drvPath = make_ref<SingleDerivedPath>(drvDep->drvPath->discardOutputPath()),
.output = outputName,
}).render(),
store.printStorePath(outputPath) store.printStorePath(outputPath)
); );
}
}
}
}
return res; return res;
} }

View file

@ -69,7 +69,7 @@ R""(
* Run a series of script commands: * Run a series of script commands:
```console ```console
# nix develop --command bash --command "mkdir build && cmake .. && make" # nix develop --command bash -c "mkdir build && cmake .. && make"
``` ```
# Description # Description

View file

@ -26,7 +26,7 @@ R""(
* Run multiple commands in a shell environment: * Run multiple commands in a shell environment:
```console ```console
# nix shell nixpkgs#gnumake --command sh --command "cd src && make" # nix shell nixpkgs#gnumake --command sh -c "cd src && make"
``` ```
* Run GNU Hello in a chroot store: * Run GNU Hello in a chroot store:

View file

@ -2,7 +2,7 @@ with import ./config.nix;
rec { rec {
dep = import ./dependencies.nix; dep = import ./dependencies.nix {};
makeTest = nr: args: mkDerivation ({ makeTest = nr: args: mkDerivation ({
name = "check-refs-" + toString nr; name = "check-refs-" + toString nr;

View file

@ -1,3 +1,4 @@
{ hashInvalidator ? "" }:
with import ./config.nix; with import ./config.nix;
let { let {
@ -21,6 +22,17 @@ let {
''; '';
}; };
fod_input = mkDerivation {
name = "fod-input";
buildCommand = ''
echo ${hashInvalidator}
echo FOD > $out
'';
outputHashMode = "flat";
outputHashAlgo = "sha256";
outputHash = "1dq9p0hnm1y75q2x40fws5887bq1r840hzdxak0a9djbwvx0b16d";
};
body = mkDerivation { body = mkDerivation {
name = "dependencies-top"; name = "dependencies-top";
builder = ./dependencies.builder0.sh + "/FOOBAR/../."; builder = ./dependencies.builder0.sh + "/FOOBAR/../.";
@ -29,6 +41,7 @@ let {
input1_drv = input1; input1_drv = input1;
input2_drv = input2; input2_drv = input2;
input0_drv = input0; input0_drv = input0;
fod_input_drv = fod_input;
meta.description = "Random test package"; meta.description = "Random test package";
}; };

View file

@ -53,3 +53,20 @@ nix-store -q --referrers-closure "$input2OutPath" | grep "$outPath"
# Check that the derivers are set properly. # Check that the derivers are set properly.
test $(nix-store -q --deriver "$outPath") = "$drvPath" test $(nix-store -q --deriver "$outPath") = "$drvPath"
nix-store -q --deriver "$input2OutPath" | grepQuiet -- "-input-2.drv" nix-store -q --deriver "$input2OutPath" | grepQuiet -- "-input-2.drv"
# --valid-derivers returns the currently single valid .drv file
test "$(nix-store -q --valid-derivers "$outPath")" = "$drvPath"
# instantiate a different drv with the same output
drvPath2=$(nix-instantiate dependencies.nix --argstr hashInvalidator yay)
# now --valid-derivers returns both
test "$(nix-store -q --valid-derivers "$outPath" | sort)" = "$(sort <<< "$drvPath"$'\n'"$drvPath2")"
# check that nix-store --valid-derivers only returns existing drv
nix-store --delete "$drvPath"
test "$(nix-store -q --valid-derivers "$outPath")" = "$drvPath2"
# check that --valid-derivers returns nothing when there are no valid derivers
nix-store --delete "$drvPath2"
test -z "$(nix-store -q --valid-derivers "$outPath")"

View file

@ -6,4 +6,6 @@ out1=$(nix-build ./text-hashed-output.nix -A hello --no-out-link)
clearStore clearStore
expectStderr 1 nix-build ./text-hashed-output.nix -A wrapper --no-out-link | grepQuiet "Dependencies on the outputs of dynamic derivations are not yet supported" out2=$(nix-build ./text-hashed-output.nix -A wrapper --no-out-link)
diff -r $out1 $out2

View file

@ -13,14 +13,14 @@ nix --experimental-features 'nix-command' eval --impure --expr \
# the future so it does work, but there are some design questions to # the future so it does work, but there are some design questions to
# resolve first. Adding a test so we don't liberalise it by accident. # resolve first. Adding a test so we don't liberalise it by accident.
expectStderr 1 nix --experimental-features 'nix-command dynamic-derivations' eval --impure --expr \ expectStderr 1 nix --experimental-features 'nix-command dynamic-derivations' eval --impure --expr \
'builtins.outputOf (import ../dependencies.nix) "out"' \ 'builtins.outputOf (import ../dependencies.nix {}) "out"' \
| grepQuiet "value is a set while a string was expected" | grepQuiet "value is a set while a string was expected"
# Test that "DrvDeep" string contexts are not supported at this time # Test that "DrvDeep" string contexts are not supported at this time
# #
# Like the above, this is a restriction we could relax later. # Like the above, this is a restriction we could relax later.
expectStderr 1 nix --experimental-features 'nix-command dynamic-derivations' eval --impure --expr \ expectStderr 1 nix --experimental-features 'nix-command dynamic-derivations' eval --impure --expr \
'builtins.outputOf (import ../dependencies.nix).drvPath "out"' \ 'builtins.outputOf (import ../dependencies.nix {}).drvPath "out"' \
| grepQuiet "has a context which refers to a complete source and binary closure. This is not supported at this time" | grepQuiet "has a context which refers to a complete source and binary closure. This is not supported at this time"
# Test using `builtins.outputOf` with static derivations # Test using `builtins.outputOf` with static derivations

View file

@ -3,7 +3,8 @@ dyn-drv-tests := \
$(d)/recursive-mod-json.sh \ $(d)/recursive-mod-json.sh \
$(d)/build-built-drv.sh \ $(d)/build-built-drv.sh \
$(d)/eval-outputOf.sh \ $(d)/eval-outputOf.sh \
$(d)/dep-built-drv.sh $(d)/dep-built-drv.sh \
$(d)/old-daemon-error-hack.sh
install-tests-groups += dyn-drv install-tests-groups += dyn-drv

View file

@ -0,0 +1,20 @@
with import ./config.nix;
# A simple content-addressed derivation.
# The derivation can be arbitrarily modified by passing a different `seed`,
# but the output will always be the same
rec {
stub = mkDerivation {
name = "stub";
buildCommand = ''
echo stub > $out
'';
};
wrapper = mkDerivation {
name = "has-dynamic-drv-dep";
buildCommand = ''
exit 1 # we're not building this derivation
${builtins.outputOf stub.outPath "out"}
'';
};
}

View file

@ -0,0 +1,11 @@
# Purposely bypassing our usual common for this subgroup
source ../common.sh
# Need backend to support text-hashing too
isDaemonNewer "2.18.0pre20230906" && skipTest "Daemon is too new"
enableFeatures "ca-derivations dynamic-derivations"
restartDaemon
expectStderr 1 nix-instantiate --read-write-mode ./old-daemon-error-hack.nix | grepQuiet "the daemon is too old to understand dependencies on dynamic derivations"

View file

@ -17,13 +17,13 @@ rec {
foo."bar.runtimeGraph" = mkDerivation { foo."bar.runtimeGraph" = mkDerivation {
name = "dependencies"; name = "dependencies";
builder = builtins.toFile "build-graph-builder" "${printRefs}"; builder = builtins.toFile "build-graph-builder" "${printRefs}";
exportReferencesGraph = ["refs" (import ./dependencies.nix)]; exportReferencesGraph = ["refs" (import ./dependencies.nix {})];
}; };
foo."bar.buildGraph" = mkDerivation { foo."bar.buildGraph" = mkDerivation {
name = "dependencies"; name = "dependencies";
builder = builtins.toFile "build-graph-builder" "${printRefs}"; builder = builtins.toFile "build-graph-builder" "${printRefs}";
exportReferencesGraph = ["refs" (import ./dependencies.nix).drvPath]; exportReferencesGraph = ["refs" (import ./dependencies.nix {}).drvPath];
}; };
} }

View file

@ -1,4 +1,7 @@
builtins.pathExists (builtins.toPath ./lib.nix) builtins.pathExists (./lib.nix)
&& builtins.pathExists (builtins.toPath ./lib.nix)
&& builtins.pathExists (builtins.toString ./lib.nix)
&& !builtins.pathExists (builtins.toString ./lib.nix + "/")
&& builtins.pathExists (builtins.toPath (builtins.toString ./lib.nix)) && builtins.pathExists (builtins.toPath (builtins.toString ./lib.nix))
&& !builtins.pathExists (builtins.toPath (builtins.toString ./bla.nix)) && !builtins.pathExists (builtins.toPath (builtins.toString ./bla.nix))
&& builtins.pathExists ./lib.nix && builtins.pathExists ./lib.nix

View file

@ -24,7 +24,7 @@ fi
import ( import (
mkDerivation { mkDerivation {
name = "foo"; name = "foo";
bla = import ./dependencies.nix; bla = import ./dependencies.nix {};
buildCommand = " buildCommand = "
echo \\\"hi\\\" > $out echo \\\"hi\\\" > $out
"; ";