Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3e9204833 | ||
|
|
0ff190107f | ||
|
|
dffcc184d7 | ||
|
|
49f226e87b | ||
|
|
d308228497 | ||
|
|
dfbd030d3f | ||
|
|
83306bb841 | ||
|
|
4c3786dd40 | ||
|
|
0531f1299c | ||
|
|
df8d5e61ad | ||
|
|
30435e0559 | ||
|
|
31bdb2a8ff | ||
|
|
8e4cd2f537 | ||
|
|
1fe33c13d9 | ||
|
|
727cf59997 | ||
|
|
acba2c6f4f | ||
|
|
491aaaf116 | ||
|
|
862b3f41a4 | ||
|
|
28684af74b | ||
|
|
1c1f8b2343 | ||
|
|
a02e814b4b | ||
|
|
a75cf57702 | ||
|
|
605bd06ca4 | ||
|
|
84efd3ecf2 | ||
|
|
527e68ac3e | ||
|
|
2fce659c7d | ||
|
|
791d6cf433 | ||
|
|
3e0d9ff937 | ||
|
|
f629d81df0 | ||
|
|
32aed360b8 | ||
|
|
6cb17fd836 | ||
|
|
36bd92736f | ||
|
|
90159cb197 | ||
|
|
4cfeb8d1bb | ||
|
|
9cf3d3368e | ||
|
|
aab6419b63 | ||
|
|
832221650b | ||
|
|
2301d86f32 | ||
|
|
91e60321f6 | ||
|
|
28752fe288 |
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@@ -23,15 +23,9 @@ jobs:
|
||||
include:
|
||||
- scenario: on ubuntu
|
||||
runs-on: ubuntu-24.04
|
||||
system: x86_64-linux
|
||||
os: linux
|
||||
- scenario: on macos (aarch64)
|
||||
- scenario: on macos
|
||||
runs-on: macos-14
|
||||
system: aarch64-darwin
|
||||
os: darwin
|
||||
- scenario: on macos (x86_64)
|
||||
runs-on: macos-14
|
||||
system: x86_64-darwin
|
||||
os: darwin
|
||||
name: tests ${{ matrix.scenario }}
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
@@ -46,7 +40,6 @@ jobs:
|
||||
extra_nix_config: |
|
||||
sandbox = true
|
||||
max-jobs = 1
|
||||
system = ${{ matrix.system }}
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
# Since ubuntu 22.30, unprivileged usernamespaces are no longer allowed to map to the root user:
|
||||
# https://ubuntu.com/blog/ubuntu-23-10-restricted-unprivileged-user-namespaces
|
||||
@@ -65,7 +58,6 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# No x86_64-darwin (yet?) because of poor performance and similarity to aarch64-darwin
|
||||
include:
|
||||
- scenario: on ubuntu
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
11
.mergify.yml
11
.mergify.yml
@@ -106,14 +106,3 @@ pull_request_rules:
|
||||
labels:
|
||||
- automatic backport
|
||||
- merge-queue
|
||||
|
||||
- name: backport patches to 2.26
|
||||
conditions:
|
||||
- label=backport 2.26-maintenance
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- "2.26-maintenance"
|
||||
labels:
|
||||
- automatic backport
|
||||
- merge-queue
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[book]
|
||||
title = "Nix @version@ Reference Manual"
|
||||
title = "Nix Reference Manual"
|
||||
src = "source"
|
||||
|
||||
[output.html]
|
||||
@@ -83,7 +83,6 @@ manual = custom_target(
|
||||
'''
|
||||
@0@ @INPUT0@ @CURRENT_SOURCE_DIR@ > @DEPFILE@
|
||||
@0@ @INPUT1@ summary @2@ < @CURRENT_SOURCE_DIR@/source/SUMMARY.md.in > @2@/source/SUMMARY.md
|
||||
sed -e 's|@version@|@3@|g' < @INPUT2@ > @2@/book.toml
|
||||
rsync -r --include='*.md' @CURRENT_SOURCE_DIR@/ @2@/
|
||||
(cd @2@; RUST_LOG=warn @1@ build -d @2@ 3>&2 2>&1 1>&3) | { grep -Fv "because fragment resolution isn't implemented" || :; } 3>&2 2>&1 1>&3
|
||||
rm -rf @2@/manual
|
||||
@@ -93,13 +92,12 @@ manual = custom_target(
|
||||
python.full_path(),
|
||||
mdbook.full_path(),
|
||||
meson.current_build_dir(),
|
||||
meson.project_version(),
|
||||
),
|
||||
],
|
||||
input : [
|
||||
generate_manual_deps,
|
||||
'substitute.py',
|
||||
'book.toml.in',
|
||||
'book.toml',
|
||||
'anchors.jq',
|
||||
'custom.css',
|
||||
nix3_cli_files,
|
||||
|
||||
@@ -346,9 +346,6 @@ const redirects = {
|
||||
"scoping-rules": "scoping.html",
|
||||
"string-literal": "string-literals.html",
|
||||
},
|
||||
"language/derivations.md": {
|
||||
"builder-execution": "store/drv/building.md#builder-execution",
|
||||
},
|
||||
"installation/installing-binary.html": {
|
||||
"linux": "uninstall.html#linux",
|
||||
"macos": "uninstall.html#macos",
|
||||
@@ -375,7 +372,6 @@ const redirects = {
|
||||
"glossary.html": {
|
||||
"gloss-local-store": "store/types/local-store.html",
|
||||
"gloss-chroot-store": "store/types/local-store.html",
|
||||
"gloss-content-addressed-derivation": "#gloss-content-addressing-derivation",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
synopsis: Set FD_CLOEXEC on sockets created by curl
|
||||
issues: []
|
||||
prs: [12439]
|
||||
---
|
||||
|
||||
|
||||
Curl creates sockets without setting FD_CLOEXEC/SOCK_CLOEXEC, this can cause connections to remain open forever when using commands like `nix shell`
|
||||
|
||||
This change sets the FD_CLOEXEC flag using a CURLOPT_SOCKOPTFUNCTION callback.
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
synopsis: "Git LFS support"
|
||||
prs: [10153, 12468]
|
||||
---
|
||||
|
||||
The Git fetcher now supports Large File Storage (LFS). This can be enabled by passing the attribute `lfs = true` to the fetcher, e.g.
|
||||
```console
|
||||
nix flake prefetch 'git+ssh://git@github.com/Apress/repo-with-large-file-storage.git?lfs=1'
|
||||
```
|
||||
|
||||
A flake can also declare that it requires lfs to be enabled:
|
||||
```
|
||||
{
|
||||
inputs.self.lfs = true;
|
||||
}
|
||||
```
|
||||
|
||||
Author: [**@b-camacho**](https://github.com/b-camacho), [**@kip93**](https://github.com/kip93)
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
synopsis: "`inputs.self.submodules` flake attribute"
|
||||
prs: [12421]
|
||||
---
|
||||
|
||||
Flakes in Git repositories can now declare that they need Git submodules to be enabled:
|
||||
```
|
||||
{
|
||||
inputs.self.submodules = true;
|
||||
}
|
||||
```
|
||||
Thus, it's no longer needed for the caller of the flake to pass `submodules = true`.
|
||||
@@ -22,8 +22,6 @@
|
||||
- [Store Object](store/store-object.md)
|
||||
- [Content-Addressing Store Objects](store/store-object/content-address.md)
|
||||
- [Store Path](store/store-path.md)
|
||||
- [Store Derivation and Deriving Path](store/drv.md)
|
||||
- [Building](store/building.md)
|
||||
- [Store Types](store/types/index.md)
|
||||
{{#include ./store/types/SUMMARY.md}}
|
||||
- [Nix Language](language/index.md)
|
||||
|
||||
@@ -69,7 +69,7 @@ It can also execute build plans to produce new data, which are made available to
|
||||
A build plan itself is a series of *build tasks*, together with their build inputs.
|
||||
|
||||
> **Important**
|
||||
> A build task in Nix is called [store derivation](@docroot@/glossary.md#gloss-store-derivation).
|
||||
> A build task in Nix is called [derivation](@docroot@/glossary.md#gloss-derivation).
|
||||
|
||||
Each build task has a special build input executed as *build instructions* in order to perform the build.
|
||||
The result of a build task can be input to another build task.
|
||||
|
||||
@@ -22,11 +22,11 @@ It is based on the current generation of the active [profile](@docroot@/command-
|
||||
|
||||
The arguments *args* map to store paths in a number of possible ways:
|
||||
|
||||
- By default, *args* is a set of names denoting derivations in the [default Nix expression].
|
||||
- By default, *args* is a set of [derivation] names denoting derivations in the [default Nix expression].
|
||||
These are [realised], and the resulting output paths are installed.
|
||||
Currently installed derivations with a name equal to the name of a derivation being added are removed unless the option `--preserve-installed` is specified.
|
||||
|
||||
[derivation expression]: @docroot@/glossary.md#gloss-derivation-expression
|
||||
[derivation]: @docroot@/glossary.md#gloss-derivation
|
||||
[default Nix expression]: @docroot@/command-ref/files/default-nix-expression.md
|
||||
[realised]: @docroot@/glossary.md#gloss-realise
|
||||
|
||||
@@ -66,11 +66,11 @@ The arguments *args* map to store paths in a number of possible ways:
|
||||
This can be used to override the priority of the derivations being installed.
|
||||
This is useful if *args* are [store paths], which don't have any priority information.
|
||||
|
||||
- If *args* are [store paths] that point to [store derivations][store derivation], then those store derivations are [realised], and the resulting output paths are installed.
|
||||
- If *args* are [store derivations](@docroot@/glossary.md#gloss-store-derivation), then these are [realised], and the resulting output paths are installed.
|
||||
|
||||
- If *args* are [store paths] that do not point to store derivations, then these are [realised] and installed.
|
||||
- If *args* are [store paths] that are not store derivations, then these are [realised] and installed.
|
||||
|
||||
- By default all [outputs](@docroot@/language/derivations.md#attr-outputs) are installed for each [store derivation].
|
||||
- By default all [outputs](@docroot@/language/derivations.md#attr-outputs) are installed for each [derivation].
|
||||
This can be overridden by adding a `meta.outputsToInstall` attribute on the derivation listing a subset of the output names.
|
||||
|
||||
Example:
|
||||
@@ -122,8 +122,6 @@ The arguments *args* map to store paths in a number of possible ways:
|
||||
manifest.nix
|
||||
```
|
||||
|
||||
[store derivation]: @docroot@/glossary.md#gloss-store-derivation
|
||||
|
||||
# Options
|
||||
|
||||
- `--prebuilt-only` / `-b`
|
||||
|
||||
@@ -125,10 +125,7 @@ derivation is shown unless `--no-name` is specified.
|
||||
|
||||
- `--drv-path`
|
||||
|
||||
Print the [store path] to the [store derivation].
|
||||
|
||||
[store path]: @docroot@/glossary.md#gloss-store-path
|
||||
[store derivation]: @docroot@/glossary.md#gloss-derivation
|
||||
Print the path of the [store derivation](@docroot@/glossary.md#gloss-store-derivation).
|
||||
|
||||
- `--out-path`
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ md5sum`.
|
||||
- `--type` *hashAlgo*
|
||||
|
||||
Use the specified cryptographic hash algorithm, which can be one of
|
||||
`blake3`, `md5`, `sha1`, `sha256`, and `sha512`.
|
||||
`md5`, `sha1`, `sha256`, and `sha512`.
|
||||
|
||||
- `--to-base16`
|
||||
|
||||
|
||||
@@ -42,8 +42,8 @@ standard input.
|
||||
- `--eval`
|
||||
|
||||
Just parse and evaluate the input files, and print the resulting
|
||||
values on standard output.
|
||||
Store derivations are not serialized and written to the store, but instead just hashed and discarded.
|
||||
values on standard output. No instantiation of store derivations
|
||||
takes place.
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
|
||||
@@ -42,7 +42,7 @@ the path of the downloaded file in the Nix store is also printed.
|
||||
- `--type` *hashAlgo*
|
||||
|
||||
Use the specified cryptographic hash algorithm,
|
||||
which can be one of `blake3`, `md5`, `sha1`, `sha256`, and `sha512`.
|
||||
which can be one of `md5`, `sha1`, `sha256`, and `sha512`.
|
||||
The default is `sha256`.
|
||||
|
||||
- `--print-path`
|
||||
|
||||
@@ -15,7 +15,7 @@ Each of *paths* is processed as follows:
|
||||
1. If it is not [valid], substitute the store derivation file itself.
|
||||
2. Realise its [output paths]:
|
||||
- Try to fetch from [substituters] the [store objects] associated with the output paths in the store derivation's [closure].
|
||||
- With [content-addressing derivations] (experimental):
|
||||
- With [content-addressed derivations] (experimental):
|
||||
Determine the output paths to realise by querying content-addressed realisation entries in the [Nix database].
|
||||
- For any store paths that cannot be substituted, produce the required store objects:
|
||||
1. Realise all outputs of the derivation's dependencies
|
||||
@@ -32,7 +32,7 @@ If no substitutes are available and no store derivation is given, realisation fa
|
||||
[store objects]: @docroot@/store/store-object.md
|
||||
[closure]: @docroot@/glossary.md#gloss-closure
|
||||
[substituters]: @docroot@/command-ref/conf-file.md#conf-substituters
|
||||
[content-addressing derivations]: @docroot@/development/experimental-features.md#xp-feature-ca-derivations
|
||||
[content-addressed derivations]: @docroot@/development/experimental-features.md#xp-feature-ca-derivations
|
||||
[Nix database]: @docroot@/glossary.md#gloss-nix-database
|
||||
|
||||
The resulting paths are printed on standard output.
|
||||
|
||||
@@ -28,7 +28,7 @@ $ nix-shell --attr devShells.x86_64-linux.native-clangStdenvPackages
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> You can use `native-ccacheStdenv` to drastically improve rebuild time.
|
||||
> You can use `native-ccacheStdenvPackages` to drastically improve rebuild time.
|
||||
> By default, [ccache](https://ccache.dev) keeps artifacts in `~/.cache/ccache/`.
|
||||
|
||||
To build Nix itself in this shell:
|
||||
@@ -79,7 +79,7 @@ This shell also adds `./outputs/bin/nix` to your `$PATH` so you can run `nix` im
|
||||
To get a shell with one of the other [supported compilation environments](#compilation-environments):
|
||||
|
||||
```console
|
||||
$ nix develop .#native-clangStdenv
|
||||
$ nix develop .#native-clangStdenvPackages
|
||||
```
|
||||
|
||||
> **Note**
|
||||
@@ -167,13 +167,11 @@ It is useful to perform multiple cross and native builds on the same source tree
|
||||
for example to ensure that better support for one platform doesn't break the build for another.
|
||||
Meson thankfully makes this very easy by confining all build products to the build directory --- one simple shares the source directory between multiple build directories, each of which contains the build for Nix to a different platform.
|
||||
|
||||
Here's how to do that:
|
||||
Nixpkgs's `configurePhase` always chooses `build` in the current directory as the name and location of the build.
|
||||
This makes having multiple build directories slightly more inconvenient.
|
||||
The good news is that Meson/Ninja seem to cope well with relocating the build directory after it is created.
|
||||
|
||||
1. Instruct Nixpkgs's infra where we want Meson to put its build directory
|
||||
|
||||
```bash
|
||||
mesonBuildDir=build-my-variant-name
|
||||
```
|
||||
Here's how to do that
|
||||
|
||||
1. Configure as usual
|
||||
|
||||
@@ -181,12 +179,24 @@ Here's how to do that:
|
||||
configurePhase
|
||||
```
|
||||
|
||||
2. Rename the build directory
|
||||
|
||||
```bash
|
||||
cd .. # since `configurePhase` cd'd inside
|
||||
mv build build-linux # or whatever name we want
|
||||
cd build-linux
|
||||
```
|
||||
|
||||
3. Build as usual
|
||||
|
||||
```bash
|
||||
buildPhase
|
||||
```
|
||||
|
||||
> **N.B.**
|
||||
> [`nixpkgs#335818`](https://github.com/NixOS/nixpkgs/issues/335818) tracks giving `mesonConfigurePhase` proper support for custom build directories.
|
||||
> When it is fixed, we can simplify these instructions and then remove this notice.
|
||||
|
||||
## System type
|
||||
|
||||
Nix uses a string with the following format to identify the *system type* or *platform* it runs on:
|
||||
@@ -251,8 +261,7 @@ See [supported compilation environments](#compilation-environments) and instruct
|
||||
To use the LSP with your editor, you will want a `compile_commands.json` file telling `clangd` how we are compiling the code.
|
||||
Meson's configure always produces this inside the build directory.
|
||||
|
||||
Configure your editor to use the `clangd` from the `.#native-clangStdenv` shell.
|
||||
You can do that either by running it inside the development shell, or by using [nix-direnv](https://github.com/nix-community/nix-direnv) and [the appropriate editor plugin](https://github.com/direnv/direnv/wiki#editor-integration).
|
||||
Configure your editor to use the `clangd` from the `.#native-clangStdenvPackages` shell. You can do that either by running it inside the development shell, or by using [nix-direnv](https://github.com/nix-community/nix-direnv) and [the appropriate editor plugin](https://github.com/direnv/direnv/wiki#editor-integration).
|
||||
|
||||
> **Note**
|
||||
>
|
||||
@@ -268,8 +277,6 @@ You may run the formatters as a one-off using:
|
||||
./maintainers/format.sh
|
||||
```
|
||||
|
||||
### Pre-commit hooks
|
||||
|
||||
If you'd like to run the formatters before every commit, install the hooks:
|
||||
|
||||
```
|
||||
@@ -284,30 +291,3 @@ If it fails, run `git add --patch` to approve the suggestions _and commit again_
|
||||
To refresh pre-commit hook's config file, do the following:
|
||||
1. Exit the development shell and start it again by running `nix develop`.
|
||||
2. If you also use the pre-commit hook, also run `pre-commit-hooks-install` again.
|
||||
|
||||
### VSCode
|
||||
|
||||
Insert the following json into your `.vscode/settings.json` file to configure `nixfmt`.
|
||||
This will be picked up by the _Format Document_ command, `"editor.formatOnSave"`, etc.
|
||||
|
||||
```json
|
||||
{
|
||||
"nix.formatterPath": "nixfmt",
|
||||
"nix.serverSettings": {
|
||||
"nixd": {
|
||||
"formatting": {
|
||||
"command": [
|
||||
"nixfmt"
|
||||
],
|
||||
},
|
||||
},
|
||||
"nil": {
|
||||
"formatting": {
|
||||
"command": [
|
||||
"nixfmt"
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
This section shows how to build and debug Nix with debug symbols enabled.
|
||||
|
||||
Additionally, see [Testing Nix](./testing.md) for further instructions on how to debug Nix in the context of a unit test or functional test.
|
||||
|
||||
## Building Nix with Debug Symbols
|
||||
|
||||
In the development shell, set the `mesonBuildType` environment variable to `debug` before configuring the build:
|
||||
@@ -15,15 +13,6 @@ In the development shell, set the `mesonBuildType` environment variable to `debu
|
||||
Then, proceed to build Nix as described in [Building Nix](./building.md).
|
||||
This will build Nix with debug symbols, which are essential for effective debugging.
|
||||
|
||||
It is also possible to build without debugging for faster build:
|
||||
|
||||
```console
|
||||
[nix-shell]$ NIX_HARDENING_ENABLE=$(printLines $NIX_HARDENING_ENABLE | grep -v fortify)
|
||||
[nix-shell]$ export mesonBuildType=debug
|
||||
```
|
||||
|
||||
(The first line is needed because `fortify` hardening requires at least some optimization.)
|
||||
|
||||
## Debugging the Nix Binary
|
||||
|
||||
Obtain your preferred debugger within the development shell:
|
||||
|
||||
@@ -87,11 +87,7 @@ A environment variables that Google Test accepts are also worth knowing:
|
||||
|
||||
This is used to avoid logging passing tests.
|
||||
|
||||
3. [`GTEST_BREAK_ON_FAILURE`](https://google.github.io/googletest/advanced.html#turning-assertion-failures-into-break-points)
|
||||
|
||||
This is used to create a debugger breakpoint when an assertion failure occurs.
|
||||
|
||||
Putting the first two together, one might run
|
||||
Putting the two together, one might run
|
||||
|
||||
```bash
|
||||
GTEST_BRIEF=1 GTEST_FILTER='ErrorTraceTest.*' meson test nix-expr-tests -v
|
||||
@@ -99,22 +95,6 @@ GTEST_BRIEF=1 GTEST_FILTER='ErrorTraceTest.*' meson test nix-expr-tests -v
|
||||
|
||||
for short but comprensive output.
|
||||
|
||||
### Debugging tests
|
||||
|
||||
For debugging, it is useful to combine the third option above with Meson's [`--gdb`](https://mesonbuild.com/Unit-tests.html#other-test-options) flag:
|
||||
|
||||
```bash
|
||||
GTEST_BRIEF=1 GTEST_FILTER='Group.my-failing-test' meson test nix-expr-tests --gdb
|
||||
```
|
||||
|
||||
This will:
|
||||
|
||||
1. Run the unit test with GDB
|
||||
|
||||
2. Run just `Group.my-failing-test`
|
||||
|
||||
3. Stop the program when the test fails, allowing the user to then issue arbitrary commands to GDB.
|
||||
|
||||
### Characterisation testing { #characaterisation-testing-unit }
|
||||
|
||||
See [functional characterisation testing](#characterisation-testing-functional) for a broader discussion of characterisation testing.
|
||||
@@ -164,7 +144,7 @@ $ checkPhase
|
||||
|
||||
Sometimes it is useful to group related tests so they can be easily run together without running the entire test suite.
|
||||
Each test group is in a subdirectory of `tests`.
|
||||
For example, `tests/functional/ca/meson.build` defines a `ca` test group for content-addressing derivation outputs.
|
||||
For example, `tests/functional/ca/meson.build` defines a `ca` test group for content-addressed derivation outputs.
|
||||
|
||||
That test group can be run like this:
|
||||
|
||||
@@ -233,10 +213,10 @@ edit it like so:
|
||||
bar
|
||||
```
|
||||
|
||||
Then, running the test with [`--interactive`](https://mesonbuild.com/Unit-tests.html#other-test-options) will prevent Meson from hijacking the terminal so you can drop you into GDB once the script reaches that point:
|
||||
Then, running the test with `./mk/debug-test.sh` will drop you into GDB once the script reaches that point:
|
||||
|
||||
```shell-session
|
||||
$ meson test ${testName} --interactive
|
||||
$ ./mk/debug-test.sh tests/functional/${testName}.sh
|
||||
...
|
||||
+ gdb blash blub
|
||||
GNU gdb (GDB) 12.1
|
||||
|
||||
@@ -13,41 +13,37 @@
|
||||
|
||||
- [Content-Addressing File System Objects](@docroot@/store/file-system-object/content-address.md)
|
||||
- [Content-Addressing Store Objects](@docroot@/store/store-object/content-address.md)
|
||||
- [content-addressing derivation](#gloss-content-addressing-derivation)
|
||||
- [content-addressed derivation](#gloss-content-addressed-derivation)
|
||||
|
||||
Software Heritage's writing on [*Intrinsic and Extrinsic identifiers*](https://www.softwareheritage.org/2020/07/09/intrinsic-vs-extrinsic-identifiers) is also a good introduction to the value of content-addressing over other referencing schemes.
|
||||
|
||||
Besides content addressing, the Nix store also uses [input addressing](#gloss-input-addressed-store-object).
|
||||
|
||||
- [derivation]{#gloss-derivation}
|
||||
|
||||
A description of a build task. The result of a derivation is a
|
||||
store object. Derivations declared in Nix expressions are specified
|
||||
using the [`derivation` primitive](./language/derivations.md). These are
|
||||
translated into low-level *store derivations* (implicitly by
|
||||
`nix-build`, or explicitly by `nix-instantiate`).
|
||||
|
||||
[derivation]: #gloss-derivation
|
||||
|
||||
- [store derivation]{#gloss-store-derivation}
|
||||
|
||||
A single build task.
|
||||
See [Store Derivation](@docroot@/store/drv.md#store-derivation) for details.
|
||||
A [derivation] represented as a `.drv` file in the [store].
|
||||
It has a [store path], like any [store object].
|
||||
It is the [instantiated][instantiate] form of a derivation.
|
||||
|
||||
Example: `/nix/store/g946hcz4c8mdvq2g8vxx42z51qb71rvp-git-2.38.1.drv`
|
||||
|
||||
See [`nix derivation show`](./command-ref/new-cli/nix3-derivation-show.md) (experimental) for displaying the contents of store derivations.
|
||||
|
||||
[store derivation]: #gloss-store-derivation
|
||||
|
||||
- [derivation path]{#gloss-derivation-path}
|
||||
|
||||
A [store path] which uniquely identifies a [store derivation].
|
||||
|
||||
See [Referencing Store Derivations](@docroot@/store/drv.md#derivation-path) for details.
|
||||
|
||||
Not to be confused with [deriving path].
|
||||
|
||||
[derivation path]: #gloss-derivation-path
|
||||
|
||||
- [derivation expression]{#gloss-derivation-expression}
|
||||
|
||||
A description of a [store derivation] in the Nix language.
|
||||
The output(s) of a derivation are store objects.
|
||||
Derivations are typically specified in Nix expressions using the [`derivation` primitive](./language/derivations.md).
|
||||
These are translated into store layer *derivations* (implicitly by `nix-env` and `nix-build`, or explicitly by `nix-instantiate`).
|
||||
|
||||
[derivation expression]: #gloss-derivation-expression
|
||||
|
||||
- [instantiate]{#gloss-instantiate}, instantiation
|
||||
|
||||
Translate a [derivation expression] into a [store derivation].
|
||||
Save an evaluated [derivation] as a [store derivation] in the Nix [store].
|
||||
|
||||
See [`nix-instantiate`](./command-ref/nix-instantiate.md), which produces a store derivation from a Nix expression that evaluates to a derivation.
|
||||
|
||||
@@ -59,7 +55,7 @@
|
||||
|
||||
This can be achieved by:
|
||||
- Fetching a pre-built [store object] from a [substituter]
|
||||
- Running the [`builder`](@docroot@/language/derivations.md#attr-builder) executable as specified in the corresponding [store derivation]
|
||||
- Running the [`builder`](@docroot@/language/derivations.md#attr-builder) executable as specified in the corresponding [derivation]
|
||||
- Delegating to a [remote machine](@docroot@/command-ref/conf-file.md#conf-builders) and retrieving the outputs
|
||||
<!-- TODO: link [running] to build process page, #8888 -->
|
||||
|
||||
@@ -69,7 +65,7 @@
|
||||
|
||||
[realise]: #gloss-realise
|
||||
|
||||
- [content-addressing derivation]{#gloss-content-addressing-derivation}
|
||||
- [content-addressed derivation]{#gloss-content-addressed-derivation}
|
||||
|
||||
A derivation which has the
|
||||
[`__contentAddressed`](./language/advanced-attributes.md#adv-attr-__contentAddressed)
|
||||
@@ -77,7 +73,7 @@
|
||||
|
||||
- [fixed-output derivation]{#gloss-fixed-output-derivation} (FOD)
|
||||
|
||||
A [store derivation] where a cryptographic hash of the [output] is determined in advance using the [`outputHash`](./language/advanced-attributes.md#adv-attr-outputHash) attribute, and where the [`builder`](@docroot@/language/derivations.md#attr-builder) executable has access to the network.
|
||||
A [derivation] where a cryptographic hash of the [output] is determined in advance using the [`outputHash`](./language/advanced-attributes.md#adv-attr-outputHash) attribute, and where the [`builder`](@docroot@/language/derivations.md#attr-builder) executable has access to the network.
|
||||
|
||||
- [store]{#gloss-store}
|
||||
|
||||
@@ -134,7 +130,7 @@
|
||||
- [input-addressed store object]{#gloss-input-addressed-store-object}
|
||||
|
||||
A store object produced by building a
|
||||
non-[content-addressed](#gloss-content-addressing-derivation),
|
||||
non-[content-addressed](#gloss-content-addressed-derivation),
|
||||
non-[fixed-output](#gloss-fixed-output-derivation)
|
||||
derivation.
|
||||
|
||||
@@ -142,7 +138,7 @@
|
||||
|
||||
A [store object] which is [content-addressed](#gloss-content-address),
|
||||
i.e. whose [store path] is determined by its contents.
|
||||
This includes derivations, the outputs of [content-addressing derivations](#gloss-content-addressing-derivation), and the outputs of [fixed-output derivations](#gloss-fixed-output-derivation).
|
||||
This includes derivations, the outputs of [content-addressed derivations](#gloss-content-addressed-derivation), and the outputs of [fixed-output derivations](#gloss-fixed-output-derivation).
|
||||
|
||||
See [Content-Addressing Store Objects](@docroot@/store/store-object/content-address.md) for details.
|
||||
|
||||
@@ -192,7 +188,7 @@
|
||||
>
|
||||
> The contents of a `.nix` file form a Nix expression.
|
||||
|
||||
Nix expressions specify [derivation expressions][derivation expression], which are [instantiated][instantiate] into the Nix store as [store derivations][store derivation].
|
||||
Nix expressions specify [derivations][derivation], which are [instantiated][instantiate] into the Nix store as [store derivations][store derivation].
|
||||
These derivations can then be [realised][realise] to produce [outputs][output].
|
||||
|
||||
> **Example**
|
||||
@@ -234,14 +230,14 @@
|
||||
|
||||
- [output]{#gloss-output}
|
||||
|
||||
A [store object] produced by a [store derivation].
|
||||
A [store object] produced by a [derivation].
|
||||
See [the `outputs` argument to the `derivation` function](@docroot@/language/derivations.md#attr-outputs) for details.
|
||||
|
||||
[output]: #gloss-output
|
||||
|
||||
- [output path]{#gloss-output-path}
|
||||
|
||||
The [store path] to the [output] of a [store derivation].
|
||||
The [store path] to the [output] of a [derivation].
|
||||
|
||||
[output path]: #gloss-output-path
|
||||
|
||||
@@ -250,11 +246,14 @@
|
||||
|
||||
- [deriving path]{#gloss-deriving-path}
|
||||
|
||||
Deriving paths are a way to refer to [store objects][store object] that might not yet be [realised][realise].
|
||||
Deriving paths are a way to refer to [store objects][store object] that ar not yet [realised][realise].
|
||||
This is necessary because, in general and particularly for [content-addressed derivations][content-addressed derivation], the [output path] of an [output] is not known in advance.
|
||||
There are two forms:
|
||||
|
||||
See [Deriving Path](./store/drv.md#deriving-path) for details.
|
||||
- *constant*: just a [store path]
|
||||
It can be made [valid][validity] by copying it into the store: from the evaluator, command line interface or another store.
|
||||
|
||||
Not to be confused with [derivation path].
|
||||
- *output*: a pair of a [store path] to a [derivation] and an [output] name.
|
||||
|
||||
- [deriver]{#gloss-deriver}
|
||||
|
||||
|
||||
@@ -192,7 +192,7 @@ Derivations can declare some infrequently used optional attributes.
|
||||
The [`convertHash`](@docroot@/language/builtins.md#builtins-convertHash) function shows how to convert between different encodings, and the [`nix-hash` command](../command-ref/nix-hash.md) has information about obtaining the hash for some contents, as well as converting to and from encodings.
|
||||
|
||||
The `outputHashAlgo` attribute specifies the hash algorithm used to compute the hash.
|
||||
It can currently be `"blake3", "sha1"`, `"sha256"`, `"sha512"`, or `null`.
|
||||
It can currently be `"sha1"`, `"sha256"`, `"sha512"`, or `null`.
|
||||
`outputHashAlgo` can only be `null` when `outputHash` follows the SRI format.
|
||||
|
||||
The `outputHashMode` attribute determines how the hash is computed.
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
# Derivations
|
||||
|
||||
The most important built-in function is `derivation`, which is used to describe a single store-layer [store derivation].
|
||||
Consult the [store chapter](@docroot@/store/drv.md) for what a store derivation is;
|
||||
this section just concerns how to create one from the Nix language.
|
||||
The most important built-in function is `derivation`, which is used to describe a single derivation:
|
||||
a specification for running an executable on precisely defined input files to repeatably produce output files at uniquely determined file system paths.
|
||||
|
||||
This builtin function takes as input an attribute set, the attributes of which specify the inputs to the process.
|
||||
It takes as input an attribute set, the attributes of which specify the inputs to the process.
|
||||
It outputs an attribute set, and produces a [store derivation] as a side effect of evaluation.
|
||||
|
||||
[store derivation]: @docroot@/glossary.md#gloss-store-derivation
|
||||
@@ -16,7 +15,7 @@ It outputs an attribute set, and produces a [store derivation] as a side effect
|
||||
- [`name`]{#attr-name} ([String](@docroot@/language/types.md#type-string))
|
||||
|
||||
A symbolic name for the derivation.
|
||||
See [derivation outputs](@docroot@/store/drv.md#outputs) for what this is affects.
|
||||
It is added to the [store path] of the corresponding [store derivation] as well as to its [output paths](@docroot@/glossary.md#gloss-output-path).
|
||||
|
||||
[store path]: @docroot@/store/store-path.md
|
||||
|
||||
@@ -29,12 +28,17 @@ It outputs an attribute set, and produces a [store derivation] as a side effect
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> The derivation's path will be `/nix/store/<hash>-hello.drv`.
|
||||
> The store derivation's path will be `/nix/store/<hash>-hello.drv`.
|
||||
> The [output](#attr-outputs) paths will be of the form `/nix/store/<hash>-hello[-<output>]`
|
||||
|
||||
- [`system`]{#attr-system} ([String](@docroot@/language/types.md#type-string))
|
||||
|
||||
See [system](@docroot@/store/drv.md#system).
|
||||
The system type on which the [`builder`](#attr-builder) executable is meant to be run.
|
||||
|
||||
A necessary condition for Nix to build derivations locally is that the `system` attribute matches the current [`system` configuration option].
|
||||
It can automatically [build on other platforms](@docroot@/language/derivations.md#attr-builder) by forwarding build requests to other machines.
|
||||
|
||||
[`system` configuration option]: @docroot@/command-ref/conf-file.md#conf-system
|
||||
|
||||
> **Example**
|
||||
>
|
||||
@@ -64,7 +68,7 @@ It outputs an attribute set, and produces a [store derivation] as a side effect
|
||||
|
||||
- [`builder`]{#attr-builder} ([Path](@docroot@/language/types.md#type-path) | [String](@docroot@/language/types.md#type-string))
|
||||
|
||||
See [builder](@docroot@/store/drv.md#builder).
|
||||
Path to an executable that will perform the build.
|
||||
|
||||
> **Example**
|
||||
>
|
||||
@@ -113,7 +117,7 @@ It outputs an attribute set, and produces a [store derivation] as a side effect
|
||||
|
||||
Default: `[ ]`
|
||||
|
||||
See [args](@docroot@/store/drv.md#args).
|
||||
Command-line arguments to be passed to the [`builder`](#attr-builder) executable.
|
||||
|
||||
> **Example**
|
||||
>
|
||||
@@ -235,3 +239,77 @@ It outputs an attribute set, and produces a [store derivation] as a side effect
|
||||
passed as an empty string.
|
||||
|
||||
<!-- FIXME: add a section on output attributes -->
|
||||
|
||||
## Builder execution
|
||||
|
||||
The [`builder`](#attr-builder) is executed as follows:
|
||||
|
||||
- A temporary directory is created under the directory specified by
|
||||
`TMPDIR` (default `/tmp`) where the build will take place. The
|
||||
current directory is changed to this directory.
|
||||
|
||||
- The environment is cleared and set to the derivation attributes, as
|
||||
specified above.
|
||||
|
||||
- In addition, the following variables are set:
|
||||
|
||||
- `NIX_BUILD_TOP` contains the path of the temporary directory for
|
||||
this build.
|
||||
|
||||
- Also, `TMPDIR`, `TEMPDIR`, `TMP`, `TEMP` are set to point to the
|
||||
temporary directory. This is to prevent the builder from
|
||||
accidentally writing temporary files anywhere else. Doing so
|
||||
might cause interference by other processes.
|
||||
|
||||
- `PATH` is set to `/path-not-set` to prevent shells from
|
||||
initialising it to their built-in default value.
|
||||
|
||||
- `HOME` is set to `/homeless-shelter` to prevent programs from
|
||||
using `/etc/passwd` or the like to find the user's home
|
||||
directory, which could cause impurity. Usually, when `HOME` is
|
||||
set, it is used as the location of the home directory, even if
|
||||
it points to a non-existent path.
|
||||
|
||||
- `NIX_STORE` is set to the path of the top-level Nix store
|
||||
directory (typically, `/nix/store`).
|
||||
|
||||
- `NIX_ATTRS_JSON_FILE` & `NIX_ATTRS_SH_FILE` if `__structuredAttrs`
|
||||
is set to `true` for the derivation. A detailed explanation of this
|
||||
behavior can be found in the
|
||||
[section about structured attrs](./advanced-attributes.md#adv-attr-structuredAttrs).
|
||||
|
||||
- For each output declared in `outputs`, the corresponding
|
||||
environment variable is set to point to the intended path in the
|
||||
Nix store for that output. Each output path is a concatenation
|
||||
of the cryptographic hash of all build inputs, the `name`
|
||||
attribute and the output name. (The output name is omitted if
|
||||
it’s `out`.)
|
||||
|
||||
- If an output path already exists, it is removed. Also, locks are
|
||||
acquired to prevent multiple Nix instances from performing the same
|
||||
build at the same time.
|
||||
|
||||
- A log of the combined standard output and error is written to
|
||||
`/nix/var/log/nix`.
|
||||
|
||||
- The builder is executed with the arguments specified by the
|
||||
attribute `args`. If it exits with exit code 0, it is considered to
|
||||
have succeeded.
|
||||
|
||||
- The temporary directory is removed (unless the `-K` option was
|
||||
specified).
|
||||
|
||||
- If the build was successful, Nix scans each output path for
|
||||
references to input paths by looking for the hash parts of the input
|
||||
paths. Since these are potential runtime dependencies, Nix registers
|
||||
them as dependencies of the output paths.
|
||||
|
||||
- After the build, Nix sets the last-modified timestamp on all files
|
||||
in the build result to 1 (00:00:01 1/1/1970 UTC), sets the group to
|
||||
the default group, and sets the mode of the file to 0444 or 0555
|
||||
(i.e., read-only, with execute permission enabled if the file was
|
||||
originally executable). Note that possible `setuid` and `setgid`
|
||||
bits are cleared. Setuid and setgid programs are not currently
|
||||
supported by Nix. This is because the Nix archives used in
|
||||
deployment have no concept of ownership information, and because it
|
||||
makes the build result dependent on the user performing the build.
|
||||
|
||||
@@ -71,9 +71,8 @@ Boxes are data structures, arrow labels are transformations.
|
||||
| evaluate | | |
|
||||
| | | | |
|
||||
| V | | |
|
||||
| .------------. | | |
|
||||
| | derivation | | | .------------------. |
|
||||
| | expression |----|-instantiate-|->| store derivation | |
|
||||
| .------------. | | .------------------. |
|
||||
| | derivation |----|-instantiate-|->| store derivation | |
|
||||
| '------------' | | '------------------' |
|
||||
| | | | |
|
||||
| | | realise |
|
||||
|
||||
@@ -22,9 +22,9 @@ Rather than writing
|
||||
"--with-freetype2-library=" + freetype + "/lib"
|
||||
```
|
||||
|
||||
(where `freetype` is a [derivation expression]), you can instead write
|
||||
(where `freetype` is a [derivation]), you can instead write
|
||||
|
||||
[derivation expression]: @docroot@/glossary.md#gloss-derivation-expression
|
||||
[derivation]: @docroot@/glossary.md#gloss-derivation
|
||||
|
||||
```nix
|
||||
"--with-freetype2-library=${freetype}/lib"
|
||||
@@ -148,7 +148,7 @@ An expression that is interpolated must evaluate to one of the following:
|
||||
- `__toString` must be a function that takes the attribute set itself and returns a string
|
||||
- `outPath` must be a string
|
||||
|
||||
This includes [derivation expressions](./derivations.md) or [flake inputs](@docroot@/command-ref/new-cli/nix3-flake.md#flake-inputs) (experimental).
|
||||
This includes [derivations](./derivations.md) or [flake inputs](@docroot@/command-ref/new-cli/nix3-flake.md#flake-inputs) (experimental).
|
||||
|
||||
A string interpolates to itself.
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# Derivation "ATerm" file format
|
||||
|
||||
For historical reasons, [store derivations][store derivation] are stored on-disk in [ATerm](https://homepages.cwi.nl/~daybuild/daily-books/technology/aterm-guide/aterm-guide.html) format.
|
||||
|
||||
## The ATerm format used
|
||||
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:
|
||||
|
||||
@@ -19,20 +17,3 @@ Derivations are serialised in one of the following formats:
|
||||
The only `version-string`s that are in use today are for [experimental features](@docroot@/development/experimental-features.md):
|
||||
|
||||
- `"xp-dyn-drv"` for the [`dynamic-derivations`](@docroot@/development/experimental-features.md#xp-feature-dynamic-derivations) experimental feature.
|
||||
|
||||
## Use for encoding to store object
|
||||
|
||||
When derivation is encoded to a [store object] we make the following choices:
|
||||
|
||||
- The store path name is the derivation name with `.drv` suffixed at the end
|
||||
|
||||
Indeed, the ATerm format above does *not* contain the name of the derivation, on the assumption that a store path will also be provided out-of-band.
|
||||
|
||||
- The derivation is content-addressed using the ["Text" method] of content-addressing derivations
|
||||
|
||||
Currently we always encode derivations to store object using the ATerm format (and the previous two choices),
|
||||
but we reserve the option to encode new sorts of derivations differently in the future.
|
||||
|
||||
[store derivation]: @docroot@/glossary.md#gloss-store-derivation
|
||||
[store object]: @docroot@/glossary.md#gloss-store-object
|
||||
["Text" method]: @docroot@/store/store-object/content-address.md#method-text
|
||||
|
||||
@@ -38,7 +38,6 @@ is a JSON object with the following fields:
|
||||
For an output which will be [content addresed], the name of the hash algorithm used.
|
||||
Valid algorithm strings are:
|
||||
|
||||
- `blake3`
|
||||
- `md5`
|
||||
- `sha1`
|
||||
- `sha256`
|
||||
|
||||
@@ -41,10 +41,10 @@ In other words, the same store object residing in different store could have dif
|
||||
|
||||
* `deriver`:
|
||||
|
||||
If known, the path to the [store derivation] from which this store object was produced.
|
||||
If known, the path to the [derivation] from which this store object was produced.
|
||||
Otherwise `null`.
|
||||
|
||||
[store derivation]: @docroot@/glossary.md#gloss-store-derivation
|
||||
[derivation]: @docroot@/glossary.md#gloss-store-derivation
|
||||
|
||||
* `registrationTime` (optional):
|
||||
|
||||
|
||||
@@ -39,29 +39,29 @@ Nix 0.8 has the following improvements:
|
||||
notion of “closure store expressions” is gone (and so is the notion
|
||||
of “successors”); the file system references of a store path are now
|
||||
just stored in the database.
|
||||
|
||||
|
||||
For instance, given any store path, you can query its closure:
|
||||
|
||||
|
||||
$ nix-store -qR $(which firefox)
|
||||
... lots of paths ...
|
||||
|
||||
|
||||
Also, Nix now remembers for each store path the derivation that
|
||||
built it (the “deriver”):
|
||||
|
||||
|
||||
$ nix-store -qR $(which firefox)
|
||||
/nix/store/4b0jx7vq80l9aqcnkszxhymsf1ffa5jd-firefox-1.0.1.drv
|
||||
|
||||
|
||||
So to see the build-time dependencies, you can do
|
||||
|
||||
|
||||
$ nix-store -qR $(nix-store -qd $(which firefox))
|
||||
|
||||
|
||||
or, in a nicer format:
|
||||
|
||||
|
||||
$ nix-store -q --tree $(nix-store -qd $(which firefox))
|
||||
|
||||
|
||||
File system references are also stored in reverse. For instance, you
|
||||
can query all paths that directly or indirectly use a certain Glibc:
|
||||
|
||||
|
||||
$ nix-store -q --referrers-closure \
|
||||
/nix/store/8lz9yc6zgmc0vlqmn2ipcpkjlmbi51vv-glibc-2.3.4
|
||||
|
||||
@@ -92,28 +92,28 @@ Nix 0.8 has the following improvements:
|
||||
- `nix-channel` has new operations `--list` and `--remove`.
|
||||
|
||||
- New ways of installing components into user environments:
|
||||
|
||||
|
||||
- Copy from another user environment:
|
||||
|
||||
|
||||
$ nix-env -i --from-profile .../other-profile firefox
|
||||
|
||||
|
||||
- Install a store derivation directly (bypassing the Nix
|
||||
expression language entirely):
|
||||
|
||||
|
||||
$ nix-env -i /nix/store/z58v41v21xd3...-aterm-2.3.1.drv
|
||||
|
||||
|
||||
(This is used to implement `nix-install-package`, which is
|
||||
therefore immune to evolution in the Nix expression language.)
|
||||
|
||||
|
||||
- Install an already built store path directly:
|
||||
|
||||
|
||||
$ nix-env -i /nix/store/hsyj5pbn0d9i...-aterm-2.3.1
|
||||
|
||||
|
||||
- Install the result of a Nix expression specified as a
|
||||
command-line argument:
|
||||
|
||||
|
||||
$ nix-env -f .../i686-linux.nix -i -E 'x: x.firefoxWrapper'
|
||||
|
||||
|
||||
The difference with the normal installation mode is that `-E`
|
||||
does not use the `name` attributes of derivations. Therefore,
|
||||
this can be used to disambiguate multiple derivations with the
|
||||
@@ -127,7 +127,7 @@ Nix 0.8 has the following improvements:
|
||||
- Implemented a concurrent garbage collector. It is now always safe to
|
||||
run the garbage collector, even if other Nix operations are
|
||||
happening simultaneously.
|
||||
|
||||
|
||||
However, there can still be GC races if you use `nix-instantiate`
|
||||
and `nix-store
|
||||
--realise` directly to build things. To prevent races, use the
|
||||
@@ -147,13 +147,13 @@ Nix 0.8 has the following improvements:
|
||||
|
||||
- The behaviour of the garbage collector can be changed globally by
|
||||
setting options in `/nix/etc/nix/nix.conf`.
|
||||
|
||||
|
||||
- `gc-keep-derivations` specifies whether deriver links should be
|
||||
followed when searching for live paths.
|
||||
|
||||
|
||||
- `gc-keep-outputs` specifies whether outputs of derivations
|
||||
should be followed when searching for live paths.
|
||||
|
||||
|
||||
- `env-keep-derivations` specifies whether user environments
|
||||
should store the paths of derivations when they are added (thus
|
||||
keeping the derivations alive).
|
||||
|
||||
@@ -8,13 +8,13 @@ The following incompatible changes have been made:
|
||||
It has been superseded by the binary cache substituter mechanism
|
||||
since several years. As a result, the following programs have been
|
||||
removed:
|
||||
|
||||
|
||||
- `nix-pull`
|
||||
|
||||
|
||||
- `nix-generate-patches`
|
||||
|
||||
|
||||
- `bsdiff`
|
||||
|
||||
|
||||
- `bspatch`
|
||||
|
||||
- The “copy from other stores” substituter mechanism
|
||||
@@ -58,26 +58,26 @@ This release has the following new features:
|
||||
`nix-build`, `nix-shell -p`, `nix-env -qa`, `nix-instantiate
|
||||
--eval`, `nix-push` and `nix-copy-closure`. It has the following
|
||||
major features:
|
||||
|
||||
|
||||
- Unlike the legacy commands, it has a consistent way to refer to
|
||||
packages and package-like arguments (like store paths). For
|
||||
example, the following commands all copy the GNU Hello package
|
||||
to a remote machine:
|
||||
|
||||
|
||||
nix copy --to ssh://machine nixpkgs.hello
|
||||
|
||||
|
||||
nix copy --to ssh://machine /nix/store/0i2jd68mp5g6h2sa5k9c85rb80sn8hi9-hello-2.10
|
||||
|
||||
|
||||
nix copy --to ssh://machine '(with import <nixpkgs> {}; hello)'
|
||||
|
||||
|
||||
By contrast, `nix-copy-closure` only accepted store paths as
|
||||
arguments.
|
||||
|
||||
|
||||
- It is self-documenting: `--help` shows all available
|
||||
command-line arguments. If `--help` is given after a subcommand,
|
||||
it shows examples for that subcommand. `nix
|
||||
--help-config` shows all configuration options.
|
||||
|
||||
|
||||
- It is much less verbose. By default, it displays a single-line
|
||||
progress indicator that shows how many packages are left to be
|
||||
built or downloaded, and (if there are running builds) the most
|
||||
@@ -85,7 +85,7 @@ This release has the following new features:
|
||||
last few lines of builder output. The full build log can be
|
||||
retrieved using `nix
|
||||
log`.
|
||||
|
||||
|
||||
- It
|
||||
[provides](https://github.com/NixOS/nix/commit/b8283773bd64d7da6859ed520ee19867742a03ba)
|
||||
all `nix.conf` configuration options as command line flags. For
|
||||
@@ -93,122 +93,122 @@ This release has the following new features:
|
||||
http-connections 100` you can write `--http-connections 100`.
|
||||
Boolean options can be written as `--foo` or `--no-foo` (e.g.
|
||||
`--no-auto-optimise-store`).
|
||||
|
||||
|
||||
- Many subcommands have a `--json` flag to write results to stdout
|
||||
in JSON format.
|
||||
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
>
|
||||
> Please note that the `nix` command is a work in progress and the
|
||||
> interface is subject to change.
|
||||
|
||||
|
||||
It provides the following high-level (“porcelain”) subcommands:
|
||||
|
||||
|
||||
- `nix build` is a replacement for `nix-build`.
|
||||
|
||||
|
||||
- `nix run` executes a command in an environment in which the
|
||||
specified packages are available. It is (roughly) a replacement
|
||||
for `nix-shell
|
||||
-p`. Unlike that command, it does not execute the command in a
|
||||
shell, and has a flag (`-c`) that specifies the unquoted command
|
||||
line to be executed.
|
||||
|
||||
|
||||
It is particularly useful in conjunction with chroot stores,
|
||||
allowing Linux users who do not have permission to install Nix
|
||||
in `/nix/store` to still use binary substitutes that assume
|
||||
`/nix/store`. For example,
|
||||
|
||||
|
||||
nix run --store ~/my-nix nixpkgs.hello -c hello --greeting 'Hi everybody!'
|
||||
|
||||
|
||||
downloads (or if not substitutes are available, builds) the GNU
|
||||
Hello package into `~/my-nix/nix/store`, then runs `hello` in a
|
||||
mount namespace where `~/my-nix/nix/store` is mounted onto
|
||||
`/nix/store`.
|
||||
|
||||
|
||||
- `nix search` replaces `nix-env
|
||||
-qa`. It searches the available packages for occurrences of a
|
||||
search string in the attribute name, package name or
|
||||
description. Unlike `nix-env -qa`, it has a cache to speed up
|
||||
subsequent searches.
|
||||
|
||||
|
||||
- `nix copy` copies paths between arbitrary Nix stores,
|
||||
generalising `nix-copy-closure` and `nix-push`.
|
||||
|
||||
|
||||
- `nix repl` replaces the external program `nix-repl`. It provides
|
||||
an interactive environment for evaluating and building Nix
|
||||
expressions. Note that it uses `linenoise-ng` instead of GNU
|
||||
Readline.
|
||||
|
||||
|
||||
- `nix upgrade-nix` upgrades Nix to the latest stable version.
|
||||
This requires that Nix is installed in a profile. (Thus it won’t
|
||||
work on NixOS, or if it’s installed outside of the Nix store.)
|
||||
|
||||
|
||||
- `nix verify` checks whether store paths are unmodified and/or
|
||||
“trusted” (see below). It replaces `nix-store --verify` and
|
||||
`nix-store
|
||||
--verify-path`.
|
||||
|
||||
|
||||
- `nix log` shows the build log of a package or path. If the
|
||||
build log is not available locally, it will try to obtain it
|
||||
from the configured substituters (such as
|
||||
[cache.nixos.org](https://cache.nixos.org/), which now
|
||||
provides build logs).
|
||||
|
||||
|
||||
- `nix edit` opens the source code of a package in your editor.
|
||||
|
||||
|
||||
- `nix eval` replaces `nix-instantiate --eval`.
|
||||
|
||||
|
||||
- `nix
|
||||
why-depends` shows why one store path has another in its
|
||||
closure. This is primarily useful to finding the causes of
|
||||
closure bloat. For example,
|
||||
|
||||
|
||||
nix why-depends nixpkgs.vlc nixpkgs.libdrm.dev
|
||||
|
||||
|
||||
shows a chain of files and fragments of file contents that cause
|
||||
the VLC package to have the “dev” output of `libdrm` in its
|
||||
closure — an undesirable situation.
|
||||
|
||||
|
||||
- `nix path-info` shows information about store paths, replacing
|
||||
`nix-store -q`. A useful feature is the option `--closure-size`
|
||||
(`-S`). For example, the following command show the closure
|
||||
sizes of every path in the current NixOS system closure, sorted
|
||||
by size:
|
||||
|
||||
|
||||
nix path-info -rS /run/current-system | sort -nk2
|
||||
|
||||
|
||||
- `nix optimise-store` replaces `nix-store --optimise`. The main
|
||||
difference is that it has a progress indicator.
|
||||
|
||||
|
||||
A number of low-level (“plumbing”) commands are also available:
|
||||
|
||||
|
||||
- `nix ls-store` and `nix
|
||||
ls-nar` list the contents of a store path or NAR file. The
|
||||
former is primarily useful in conjunction with remote stores,
|
||||
e.g.
|
||||
|
||||
|
||||
nix ls-store --store https://cache.nixos.org/ -lR /nix/store/0i2jd68mp5g6h2sa5k9c85rb80sn8hi9-hello-2.10
|
||||
|
||||
|
||||
lists the contents of path in a binary cache.
|
||||
|
||||
|
||||
- `nix cat-store` and `nix
|
||||
cat-nar` allow extracting a file from a store path or NAR file.
|
||||
|
||||
|
||||
- `nix dump-path` writes the contents of a store path to stdout in
|
||||
NAR format. This replaces `nix-store --dump`.
|
||||
|
||||
|
||||
- `nix
|
||||
show-derivation` displays a store derivation in JSON format.
|
||||
This is an alternative to `pp-aterm`.
|
||||
|
||||
|
||||
- `nix
|
||||
add-to-store` replaces `nix-store
|
||||
--add`.
|
||||
|
||||
|
||||
- `nix sign-paths` signs store paths.
|
||||
|
||||
|
||||
- `nix copy-sigs` copies signatures from one store to another.
|
||||
|
||||
|
||||
- `nix show-config` shows all configuration options and their
|
||||
current values.
|
||||
|
||||
@@ -224,11 +224,11 @@ This release has the following new features:
|
||||
`nix-copy-closure`, `nix-push` and substitution are all instances
|
||||
of the general notion of copying paths between different kinds of
|
||||
Nix stores.
|
||||
|
||||
|
||||
Stores are specified using an URI-like syntax, e.g.
|
||||
<https://cache.nixos.org/> or <ssh://machine>. The following store
|
||||
types are supported:
|
||||
|
||||
|
||||
- `LocalStore` (stori URI `local` or an absolute path) and the
|
||||
misnamed `RemoteStore` (`daemon`) provide access to a local Nix
|
||||
store, the latter via the Nix daemon. You can use `auto` or the
|
||||
@@ -236,63 +236,63 @@ This release has the following new features:
|
||||
whether you have write permission to the Nix store. It is no
|
||||
longer necessary to set the `NIX_REMOTE` environment variable to
|
||||
use the Nix daemon.
|
||||
|
||||
|
||||
As noted above, `LocalStore` now supports chroot builds,
|
||||
allowing the “physical” location of the Nix store (e.g.
|
||||
`/home/alice/nix/store`) to differ from its “logical” location
|
||||
(typically `/nix/store`). This allows non-root users to use Nix
|
||||
while still getting the benefits from prebuilt binaries from
|
||||
[cache.nixos.org](https://cache.nixos.org/).
|
||||
|
||||
|
||||
- `BinaryCacheStore` is the abstract superclass of all binary
|
||||
cache stores. It supports writing build logs and NAR content
|
||||
listings in JSON format.
|
||||
|
||||
|
||||
- `HttpBinaryCacheStore` (`http://`, `https://`) supports binary
|
||||
caches via HTTP or HTTPS. If the server supports `PUT` requests,
|
||||
it supports uploading store paths via commands such as `nix
|
||||
copy`.
|
||||
|
||||
|
||||
- `LocalBinaryCacheStore` (`file://`) supports binary caches in
|
||||
the local filesystem.
|
||||
|
||||
|
||||
- `S3BinaryCacheStore` (`s3://`) supports binary caches stored in
|
||||
Amazon S3, if enabled at compile time.
|
||||
|
||||
|
||||
- `LegacySSHStore` (`ssh://`) is used to implement remote builds
|
||||
and `nix-copy-closure`.
|
||||
|
||||
|
||||
- `SSHStore` (`ssh-ng://`) supports arbitrary Nix operations on a
|
||||
remote machine via the same protocol used by `nix-daemon`.
|
||||
|
||||
- Security has been improved in various ways:
|
||||
|
||||
|
||||
- Nix now stores signatures for local store paths. When paths are
|
||||
copied between stores (e.g., copied from a binary cache to a
|
||||
local store), signatures are propagated.
|
||||
|
||||
|
||||
Locally-built paths are signed automatically using the secret
|
||||
keys specified by the `secret-key-files` store option.
|
||||
Secret/public key pairs can be generated using `nix-store
|
||||
--generate-binary-cache-key`.
|
||||
|
||||
|
||||
In addition, locally-built store paths are marked as “ultimately
|
||||
trusted”, but this bit is not propagated when paths are copied
|
||||
between stores.
|
||||
|
||||
|
||||
- Content-addressable store paths no longer require signatures —
|
||||
they can be imported into a store by unprivileged users even if
|
||||
they lack signatures.
|
||||
|
||||
|
||||
- The command `nix verify` checks whether the specified paths are
|
||||
trusted, i.e., have a certain number of trusted signatures, are
|
||||
ultimately trusted, or are content-addressed.
|
||||
|
||||
|
||||
- Substitutions from binary caches
|
||||
[now](https://github.com/NixOS/nix/commit/ecbc3fedd3d5bdc5a0e1a0a51b29062f2874ac8b)
|
||||
require signatures by default. This was already the case on
|
||||
NixOS.
|
||||
|
||||
|
||||
- In Linux sandbox builds, we
|
||||
[now](https://github.com/NixOS/nix/commit/eba840c8a13b465ace90172ff76a0db2899ab11b)
|
||||
use `/build` instead of `/tmp` as the temporary build directory.
|
||||
@@ -309,7 +309,7 @@ This release has the following new features:
|
||||
hash or commit hash is specified. For example, calls to
|
||||
`builtins.fetchGit` are only allowed if a `rev` attribute is
|
||||
specified.
|
||||
|
||||
|
||||
The goal of this feature is to enable true reproducibility and
|
||||
traceability of builds (including NixOS system configurations) at
|
||||
the evaluation level. For example, in the future, `nixos-rebuild`
|
||||
@@ -367,21 +367,21 @@ This release has the following new features:
|
||||
log will be shown if a build fails.
|
||||
|
||||
- Networking has been improved:
|
||||
|
||||
|
||||
- HTTP/2 is now supported. This makes binary cache lookups [much
|
||||
more
|
||||
efficient](https://github.com/NixOS/nix/commit/90ad02bf626b885a5dd8967894e2eafc953bdf92).
|
||||
|
||||
|
||||
- We now retry downloads on many HTTP errors, making binary caches
|
||||
substituters more resilient to temporary failures.
|
||||
|
||||
|
||||
- HTTP credentials can now be configured via the standard `netrc`
|
||||
mechanism.
|
||||
|
||||
|
||||
- If S3 support is enabled at compile time, <s3://> URIs are
|
||||
[supported](https://github.com/NixOS/nix/commit/9ff9c3f2f80ba4108e9c945bbfda2c64735f987b)
|
||||
in all places where Nix allows URIs.
|
||||
|
||||
|
||||
- Brotli compression is now supported. In particular,
|
||||
[cache.nixos.org](https://cache.nixos.org/) build logs are now compressed
|
||||
using Brotli.
|
||||
@@ -431,9 +431,9 @@ The Nix language has the following new features:
|
||||
- Derivation attributes can now reference the outputs of the
|
||||
derivation using the `placeholder` builtin function. For example,
|
||||
the attribute
|
||||
|
||||
|
||||
configureFlags = "--prefix=${placeholder "out"} --includedir=${placeholder "dev"}";
|
||||
|
||||
|
||||
will cause the `configureFlags` environment variable to contain the
|
||||
actual store paths corresponding to the `out` and `dev` outputs.
|
||||
|
||||
@@ -444,7 +444,7 @@ The following builtin functions are new or extended:
|
||||
Nixpkgs, which fetches at build time and cannot be used to fetch Nix
|
||||
expressions during evaluation. A typical use case is to import
|
||||
external NixOS modules from your configuration, e.g.
|
||||
|
||||
|
||||
imports = [ (builtins.fetchGit https://github.com/edolstra/dwarffs + "/module.nix") ];
|
||||
|
||||
- Similarly, `builtins.fetchMercurial` allows you to fetch Mercurial
|
||||
@@ -485,7 +485,7 @@ The Nix build environment has the following changes:
|
||||
builder via the file `.attrs.json` in the builder’s temporary
|
||||
directory. This obviates the need for `passAsFile` since JSON files
|
||||
have no size restrictions, unlike process environments.
|
||||
|
||||
|
||||
[As a convenience to Bash
|
||||
builders](https://github.com/NixOS/nix/commit/2d5b1b24bf70a498e4c0b378704cfdb6471cc699),
|
||||
Nix writes a script named `.attrs.sh` to the builder’s directory
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
# Building
|
||||
|
||||
## Normalizing derivation inputs
|
||||
|
||||
- Each input must be [realised] prior to building the derivation in question.
|
||||
|
||||
[realised]: @docroot@/glossary.md#gloss-realise
|
||||
|
||||
- Once this is done, the derivation is *normalized*, replacing each input deriving path with its store path, which we now know from realising the input.
|
||||
|
||||
## Builder Execution
|
||||
|
||||
The [`builder`](./drv.md#builder) is executed as follows:
|
||||
|
||||
- A temporary directory is created under the directory specified by
|
||||
`TMPDIR` (default `/tmp`) where the build will take place. The
|
||||
current directory is changed to this directory.
|
||||
|
||||
- The environment is cleared and set to the derivation attributes, as
|
||||
specified above.
|
||||
|
||||
- In addition, the following variables are set:
|
||||
|
||||
- `NIX_BUILD_TOP` contains the path of the temporary directory for
|
||||
this build.
|
||||
|
||||
- Also, `TMPDIR`, `TEMPDIR`, `TMP`, `TEMP` are set to point to the
|
||||
temporary directory. This is to prevent the builder from
|
||||
accidentally writing temporary files anywhere else. Doing so
|
||||
might cause interference by other processes.
|
||||
|
||||
- `PATH` is set to `/path-not-set` to prevent shells from
|
||||
initialising it to their built-in default value.
|
||||
|
||||
- `HOME` is set to `/homeless-shelter` to prevent programs from
|
||||
using `/etc/passwd` or the like to find the user's home
|
||||
directory, which could cause impurity. Usually, when `HOME` is
|
||||
set, it is used as the location of the home directory, even if
|
||||
it points to a non-existent path.
|
||||
|
||||
- `NIX_STORE` is set to the path of the top-level Nix store
|
||||
directory (typically, `/nix/store`).
|
||||
|
||||
- `NIX_ATTRS_JSON_FILE` & `NIX_ATTRS_SH_FILE` if `__structuredAttrs`
|
||||
is set to `true` for the derivation. A detailed explanation of this
|
||||
behavior can be found in the
|
||||
[section about structured attrs](@docroot@/language/advanced-attributes.md#adv-attr-structuredAttrs).
|
||||
|
||||
- For each output declared in `outputs`, the corresponding
|
||||
environment variable is set to point to the intended path in the
|
||||
Nix store for that output. Each output path is a concatenation
|
||||
of the cryptographic hash of all build inputs, the `name`
|
||||
attribute and the output name. (The output name is omitted if
|
||||
it’s `out`.)
|
||||
|
||||
- If an output path already exists, it is removed. Also, locks are
|
||||
acquired to prevent multiple Nix instances from performing the same
|
||||
build at the same time.
|
||||
|
||||
- A log of the combined standard output and error is written to
|
||||
`/nix/var/log/nix`.
|
||||
|
||||
- The builder is executed with the arguments specified by the
|
||||
attribute `args`. If it exits with exit code 0, it is considered to
|
||||
have succeeded.
|
||||
|
||||
- The temporary directory is removed (unless the `-K` option was
|
||||
specified).
|
||||
|
||||
## Processing outputs
|
||||
|
||||
If the builder exited successfully, the following steps happen in order to turn the output directories left behind by the builder into proper store objects:
|
||||
|
||||
- **Normalize the file permissions**
|
||||
|
||||
Nix sets the last-modified timestamp on all files
|
||||
in the build result to 1 (00:00:01 1/1/1970 UTC), sets the group to
|
||||
the default group, and sets the mode of the file to 0444 or 0555
|
||||
(i.e., read-only, with execute permission enabled if the file was
|
||||
originally executable). Any possible `setuid` and `setgid`
|
||||
bits are cleared.
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> Setuid and setgid programs are not currently supported by Nix.
|
||||
> This is because the Nix archives used in deployment have no concept of ownership information,
|
||||
> and because it makes the build result dependent on the user performing the build.
|
||||
|
||||
- **Calculate the references**
|
||||
|
||||
Nix scans each output path for
|
||||
references to input paths by looking for the hash parts of the input
|
||||
paths. Since these are potential runtime dependencies, Nix registers
|
||||
them as dependencies of the output paths.
|
||||
|
||||
Nix also scans for references to other outputs' paths in the same way, because outputs are allowed to refer to each other.
|
||||
If the outputs' references to each other form a cycle, this is an error, because the references of store objects much be acyclic.
|
||||
@@ -1,310 +0,0 @@
|
||||
# Store Derivation and Deriving Path
|
||||
|
||||
Besides functioning as a [content addressed store] the Nix store layer works as a [build system].
|
||||
Other system (like Git or IPFS) also store and transfer immutable data, but they don't concern themselves with *how* that data was created.
|
||||
|
||||
This is where Nix distinguishes itself.
|
||||
*Derivations* represent individual build steps, and *deriving paths* are needed to refer to the *outputs* of those build steps before they are built.
|
||||
<!-- The two concepts need to be introduced together because, as described below, each depends on the other. -->
|
||||
|
||||
## Store Derivation {#store-derivation}
|
||||
|
||||
A derivation is a specification for running an executable on precisely defined input files to repeatably produce output files at uniquely determined file system paths.
|
||||
|
||||
A derivation consists of:
|
||||
|
||||
- A name
|
||||
|
||||
- A set of [*inputs*][inputs], a set of [deriving paths][deriving path]
|
||||
|
||||
- A map of [*outputs*][outputs], from names to other data
|
||||
|
||||
- The ["system" type][system] (e.g. `x86_64-linux`) where the executable is to run.
|
||||
|
||||
- The [process creation fields]: to spawn the arbitrary process which will perform the build step.
|
||||
|
||||
[store derivation]: #store-derivation
|
||||
[inputs]: #inputs
|
||||
[input]: #inputs
|
||||
[outputs]: #outputs
|
||||
[output]: #outputs
|
||||
[process creation fields]: #process-creation-fields
|
||||
[builder]: #builder
|
||||
[args]: #args
|
||||
[env]: #env
|
||||
[system]: #system
|
||||
|
||||
### Referencing derivations {#derivation-path}
|
||||
|
||||
Derivations are always referred to by the [store path] of the store object they are encoded to.
|
||||
See the [encoding section](#derivation-encoding) for more details on how this encoding works, and thus what exactly what store path we would end up with for a given derivation.
|
||||
|
||||
The store path of the store object which encodes a derivation is often called a *derivation path* for brevity.
|
||||
|
||||
## Deriving path {#deriving-path}
|
||||
|
||||
Deriving paths are a way to refer to [store objects][store object] that may or may not yet be [realised][realise].
|
||||
There are two forms:
|
||||
|
||||
- [*constant*]{#deriving-path-constant}: just a [store path].
|
||||
It can be made [valid][validity] by copying it into the store: from the evaluator, command line interface or another store.
|
||||
|
||||
- [*output*]{#deriving-path-output}: a pair of a [store path] to a [store derivation] and an [output] name.
|
||||
|
||||
In pseudo code:
|
||||
|
||||
```typescript
|
||||
type OutputName = String;
|
||||
|
||||
type ConstantPath = {
|
||||
path: StorePath;
|
||||
};
|
||||
|
||||
type OutputPath = {
|
||||
drvPath: StorePath;
|
||||
output: OutputName;
|
||||
};
|
||||
|
||||
type DerivingPath = ConstantPath | OutputPath;
|
||||
```
|
||||
|
||||
Deriving paths are necessary because, in general and particularly for [content-addressing derivations][content-addressing derivation], the [store path] of an [output] is not known in advance.
|
||||
We can use an output deriving path to refer to such an out, instead of the store path which we do not yet know.
|
||||
|
||||
[deriving path]: #deriving-path
|
||||
[validity]: @docroot@/glossary.md#gloss-validity
|
||||
|
||||
## Parts of a derivation
|
||||
|
||||
A derivation is constructed from the parts documented in the following subsections.
|
||||
|
||||
### Inputs {#inputs}
|
||||
|
||||
The inputs are a set of [deriving paths][deriving path], refering to all store objects needed in order to perform this build step.
|
||||
|
||||
The [process creation fields] will presumably include many [store paths][store path]:
|
||||
|
||||
- The path to the executable normally starts with a store path
|
||||
- The arguments and environment variables likely contain many other store paths.
|
||||
|
||||
But rather than somehow scanning all the other fields for inputs, Nix requires that all inputs be explicitly collected in the inputs field. It is instead the responsibility of the creator of a derivation (e.g. the evaluator) to ensure that every store object referenced in another field (e.g. referenced by store path) is included in this inputs field.
|
||||
|
||||
### Outputs {#outputs}
|
||||
|
||||
The outputs are the derivations are the [store objects][store object] it is obligated to produce.
|
||||
|
||||
Outputs are assigned names, and also consistent of other information based on the type of derivation.
|
||||
|
||||
Output names can be any string which is also a valid [store path] name.
|
||||
The store path of the output store object (also called an [output path] for short), has a name based on the derivation name and the output name.
|
||||
In the general case, store paths have name `derivationName + "-" + outputName`.
|
||||
However, an output named "out" has a store path with name is just the derivation name.
|
||||
This is to allow derivations with a single output to avoid a superfluous `"-${outputName}"` in their single output's name when no disambiguation is needed.
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> A derivation is named `hello`, and has two outputs, `out`, and `dev`
|
||||
>
|
||||
> - The derivation's path will be: `/nix/store/<hash>-hello.drv`.
|
||||
>
|
||||
> - The store path of `out` will be: `/nix/store/<hash>-hello`.
|
||||
>
|
||||
> - The store path of `dev` will be: `/nix/store/<hash>-hello-dev`.
|
||||
|
||||
### System {#system}
|
||||
|
||||
The system type on which the [`builder`](#attr-builder) executable is meant to be run.
|
||||
|
||||
A necessary condition for Nix to schedule a given derivation on some Nix instance is for the "system" of that derivation to match that instance's [`system` configuration option].
|
||||
|
||||
By putting the `system` in each derivation, Nix allows *heterogenous* build plans, where not all steps can be run on the same machine or same sort of machine.
|
||||
Nix can schedule builds such that it automatically builds on other platforms by [forwarding build requests](@docroot@/advanced-topics/distributed-builds.md) to other Nix instances.
|
||||
|
||||
[`system` configuration option]: @docroot@/command-ref/conf-file.md#conf-system
|
||||
|
||||
[content-addressing derivation]: @docroot@/glossary.md#gloss-content-addressing-derivation
|
||||
[realise]: @docroot@/glossary.md#gloss-realise
|
||||
[store object]: @docroot@/store/store-object.md
|
||||
[store path]: @docroot@/store/store-path.md
|
||||
|
||||
### Process creation fields {#process-creation-fields}
|
||||
|
||||
These are the three fields which describe how to spawn the process which (along with any of its own child processes) will perform the build.
|
||||
You may note that this has everything needed for an `execve` system call.
|
||||
|
||||
#### Builder {#builder}
|
||||
|
||||
This is the path to an executable that will perform the build and produce the [outputs].
|
||||
|
||||
#### Arguments {#args}
|
||||
|
||||
Command-line arguments to be passed to the [`builder`](#builder) executable.
|
||||
|
||||
Note that these are the arguments after the first argument.
|
||||
The first argument passed to the `builder` will be the value of `builder`, as per the usual convention on Unix.
|
||||
See [Wikipedia](https://en.wikipedia.org/wiki/Argv) for details.
|
||||
|
||||
#### Environment Variables {#env}
|
||||
|
||||
Environment variables which will be passed to the [builder](#builder) executable.
|
||||
|
||||
### Placeholders
|
||||
|
||||
Placeholders are opaque values used within the [process creation fields] to [store objects] for which we don't yet know [store path]s.
|
||||
They are strings in the form `/<hash>` that are embedded anywhere within the strings of those fields, and we are [considering](https://github.com/NixOS/nix/issues/12361) to add store-path-like placeholders.
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> Output Deriving Path exist to solve the same problem as placeholders --- that is, referring to store objects for which we don't yet know a store path.
|
||||
> They also have a string syntax with `^`, [described in the encoding section](#deriving-path-encoding).
|
||||
> We could use that syntax instead of `/<hash>` for placeholders, but its human-legibility would cause problems.
|
||||
|
||||
There are two types of placeholder, corresponding to the two cases where this problem arises:
|
||||
|
||||
- [Output placeholder]{#output-placeholder}:
|
||||
|
||||
This is a placeholder for a derivation's own output.
|
||||
|
||||
- [Input placeholder]{#input-placeholder}:
|
||||
|
||||
This is a placeholder to a derivation's non-constant [input],
|
||||
i.e. an input that is an [output derived path].
|
||||
|
||||
> **Explanation**
|
||||
>
|
||||
> In general, we need to realise [realise] a [store object] in order to be sure to have a store object for it.
|
||||
> But for these two cases this is either impossible or impractical:
|
||||
>
|
||||
> - In the output case this is impossible:
|
||||
>
|
||||
> We cannot build the output until we have a correct derivation, and we cannot have a correct derivation (without using placeholders) until we have the output path.
|
||||
>
|
||||
> - In the input case this is impractical:
|
||||
>
|
||||
> If we always build a dependency first, and then refer to its output by store path, we would lose the ability for a derivation graph to describe an entire build plan consisting of multiple build steps.
|
||||
|
||||
## Encoding
|
||||
|
||||
### Derivation {#derivation-encoding}
|
||||
|
||||
There are two formats, documented separately:
|
||||
|
||||
- The legacy ["ATerm" format](@docroot@/protocols/derivation-aterm.md)
|
||||
|
||||
- The experimental, currently under development and changing [JSON format](@docroot@/protocols/json/derivation.md)
|
||||
|
||||
Every derivation has a canonical choice of encoding used to serialize it to a store object.
|
||||
This ensures that there is a canonical [store path] used to refer to the derivation, as described in [Referencing derivations](#derivation-path).
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> Currently, the canonical encoding for every derivation is the "ATerm" format,
|
||||
> but this is subject to change for types derivations which are not yet stable.
|
||||
|
||||
Regardless of the format used, when serializing a derivation to a store object, that store object will be content-addressed.
|
||||
|
||||
In the common case, the inputs to store objects are either:
|
||||
|
||||
- [constant deriving paths](#deriving-path-constant) for content-addressed source objects, which are "initial inputs" rather than the outputs of some other derivation
|
||||
|
||||
- the outputs of other derivations
|
||||
|
||||
If those other derivations *also* abide by this common case (and likewise for transitive inputs), then the entire closure of the serialized derivation will be content-addressed.
|
||||
|
||||
### Deriving Path {#deriving-path-encoding}
|
||||
|
||||
- *constant*
|
||||
|
||||
Constant deriving paths are encoded simply as the underlying store path is.
|
||||
Thus, we see that every encoded store path is also a valid encoded (constant) deriving path.
|
||||
|
||||
- *output*
|
||||
|
||||
Output deriving paths are encoded by
|
||||
|
||||
- encoding of a store path referring to a derivation
|
||||
|
||||
- a `^` separator (or `!` in some legacy contexts)
|
||||
|
||||
- the name of an output of the previously referred derivation
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> ```
|
||||
> /nix/store/lxrn8v5aamkikg6agxwdqd1jz7746wz4-firefox-98.0.2.drv^out
|
||||
> ```
|
||||
>
|
||||
> This parses like so:
|
||||
>
|
||||
> ```
|
||||
> /nix/store/lxrn8v5aamkikg6agxwdqd1jz7746wz4-firefox-98.0.2.drv^out
|
||||
> |------------------------------------------------------------| |-|
|
||||
> store path (usual encoding) output name
|
||||
> |--|
|
||||
> note the ".drv"
|
||||
> ```
|
||||
|
||||
## Extending the model to be higher-order
|
||||
|
||||
**Experimental feature**: [`dynamic-derivations`](@docroot@/development/experimental-features.md#xp-feature-dynamic-derivations)
|
||||
|
||||
So far, we have used store paths to refer to derivations.
|
||||
That works because we've implicitly assumed that all derivations are created *statically* --- created by some mechanism out of band, and then manually inserted into the store.
|
||||
But what if derivations could also be created dynamically within Nix?
|
||||
In other words, what if derivations could be the outputs of other derivations?
|
||||
|
||||
:::{.note}
|
||||
In the parlance of "Build Systems à la carte", we are generalizing the Nix store layer to be a "Monadic" instead of "Applicative" build system.
|
||||
:::
|
||||
|
||||
How should we refer to such derivations?
|
||||
A deriving path works, the same as how we refer to other derivation outputs.
|
||||
But what about a dynamic derivations output?
|
||||
(i.e. how do we refer to the output of an output of a derivation?)
|
||||
For that we need to generalize the definition of deriving path, replacing the store path used to refer to the derivation with a nested deriving path:
|
||||
|
||||
```diff
|
||||
type OutputPath = {
|
||||
- drvPath: StorePath;
|
||||
+ drvPath: DerivingPath;
|
||||
output: OutputName;
|
||||
};
|
||||
```
|
||||
|
||||
Now, the `drvPath` field of `OutputPath` is itself a `DerivingPath` instead of a `StorePath`.
|
||||
|
||||
With that change, here is updated definition:
|
||||
|
||||
```typescript
|
||||
type OutputName = String;
|
||||
|
||||
type ConstantPath = {
|
||||
path: StorePath;
|
||||
};
|
||||
|
||||
type OutputPath = {
|
||||
drvPath: DerivingPath;
|
||||
output: OutputName;
|
||||
};
|
||||
|
||||
type DerivingPath = ConstantPath | OutputPath;
|
||||
```
|
||||
|
||||
Under this extended model, `DerivingPath`s are thus inductively built up from a root `ConstantPath`, wrapped with zero or more outer `OutputPath`s.
|
||||
|
||||
### Encoding {#deriving-path-encoding}
|
||||
|
||||
The encoding is adjusted in the natural way, encoding the `drv` field recursively using the same deriving path encoding.
|
||||
The result of this is that it is possible to have a chain of `^<output-name>` at the end of the final string, as opposed to just a single one.
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> ```
|
||||
> /nix/store/lxrn8v5aamkikg6agxwdqd1jz7746wz4-firefox-98.0.2.drv^foo.drv^bar.drv^out
|
||||
> |----------------------------------------------------------------------------| |-|
|
||||
> inner deriving path (usual encoding) output name
|
||||
> |--------------------------------------------------------------------| |-----|
|
||||
> even more inner deriving path (usual encoding) output name
|
||||
> |------------------------------------------------------------| |-----|
|
||||
> innermost constant store path (usual encoding) output name
|
||||
> ```
|
||||
114
flake.nix
114
flake.nix
@@ -32,7 +32,7 @@
|
||||
let
|
||||
inherit (nixpkgs) lib;
|
||||
|
||||
officialRelease = false;
|
||||
officialRelease = true;
|
||||
|
||||
linux32BitSystems = [ "i686-linux" ];
|
||||
linux64BitSystems = [
|
||||
@@ -80,7 +80,14 @@
|
||||
|
||||
forAllCrossSystems = lib.genAttrs crossSystems;
|
||||
|
||||
forAllStdenvs = lib.genAttrs stdenvs;
|
||||
forAllStdenvs =
|
||||
f:
|
||||
lib.listToAttrs (
|
||||
map (stdenvName: {
|
||||
name = "${stdenvName}Packages";
|
||||
value = f stdenvName;
|
||||
}) stdenvs
|
||||
);
|
||||
|
||||
# We don't apply flake-parts to the whole flake so that non-development attributes
|
||||
# load without fetching any development inputs.
|
||||
@@ -99,38 +106,42 @@
|
||||
system:
|
||||
let
|
||||
make-pkgs =
|
||||
crossSystem:
|
||||
forAllStdenvs (
|
||||
stdenv:
|
||||
import nixpkgs {
|
||||
localSystem = {
|
||||
inherit system;
|
||||
};
|
||||
crossSystem =
|
||||
if crossSystem == null then
|
||||
null
|
||||
else
|
||||
{
|
||||
config = crossSystem;
|
||||
}
|
||||
// lib.optionalAttrs (crossSystem == "x86_64-unknown-freebsd13") {
|
||||
useLLVM = true;
|
||||
};
|
||||
overlays = [
|
||||
(overlayFor (pkgs: pkgs.${stdenv}))
|
||||
];
|
||||
}
|
||||
);
|
||||
crossSystem: stdenv:
|
||||
import nixpkgs {
|
||||
localSystem = {
|
||||
inherit system;
|
||||
};
|
||||
crossSystem =
|
||||
if crossSystem == null then
|
||||
null
|
||||
else
|
||||
{
|
||||
config = crossSystem;
|
||||
}
|
||||
// lib.optionalAttrs (crossSystem == "x86_64-unknown-freebsd13") {
|
||||
useLLVM = true;
|
||||
};
|
||||
overlays = [
|
||||
(overlayFor (p: p.${stdenv}))
|
||||
];
|
||||
};
|
||||
stdenvs = forAllStdenvs (make-pkgs null);
|
||||
native = stdenvs.stdenvPackages;
|
||||
in
|
||||
rec {
|
||||
nativeForStdenv = make-pkgs null;
|
||||
crossForStdenv = forAllCrossSystems make-pkgs;
|
||||
# Alias for convenience
|
||||
native = nativeForStdenv.stdenv;
|
||||
cross = forAllCrossSystems (crossSystem: crossForStdenv.${crossSystem}.stdenv);
|
||||
{
|
||||
inherit stdenvs native;
|
||||
static = native.pkgsStatic;
|
||||
llvm = native.pkgsLLVM;
|
||||
cross = forAllCrossSystems (crossSystem: make-pkgs crossSystem "stdenv");
|
||||
}
|
||||
);
|
||||
|
||||
binaryTarball =
|
||||
nix: pkgs:
|
||||
pkgs.callPackage ./scripts/binary-tarball.nix {
|
||||
inherit nix;
|
||||
};
|
||||
|
||||
overlayFor =
|
||||
getStdenv: final: prev:
|
||||
let
|
||||
@@ -154,7 +165,6 @@
|
||||
f = import ./packaging/components.nix {
|
||||
inherit (final) lib;
|
||||
inherit officialRelease;
|
||||
pkgs = final;
|
||||
src = self;
|
||||
};
|
||||
};
|
||||
@@ -200,6 +210,7 @@
|
||||
hydraJobs = import ./packaging/hydra.nix {
|
||||
inherit
|
||||
inputs
|
||||
binaryTarball
|
||||
forAllCrossSystems
|
||||
forAllSystems
|
||||
lib
|
||||
@@ -224,30 +235,6 @@
|
||||
LANG=C.UTF-8 ${pkgs.changelog-d}/bin/changelog-d ${./doc/manual/rl-next} >$out
|
||||
'';
|
||||
repl-completion = nixpkgsFor.${system}.native.callPackage ./tests/repl-completion.nix { };
|
||||
|
||||
/**
|
||||
Checks for our packaging expressions.
|
||||
This shouldn't build anything significant; just check that things
|
||||
(including derivations) are _set up_ correctly.
|
||||
*/
|
||||
packaging-overriding =
|
||||
let
|
||||
pkgs = nixpkgsFor.${system}.native;
|
||||
nix = self.packages.${system}.nix;
|
||||
in
|
||||
assert (nix.appendPatches [ pkgs.emptyFile ]).libs.nix-util.src.patches == [ pkgs.emptyFile ];
|
||||
if pkgs.stdenv.buildPlatform.isDarwin then
|
||||
lib.warn "packaging-overriding check currently disabled because of a permissions issue on macOS" pkgs.emptyFile
|
||||
else
|
||||
# If this fails, something might be wrong with how we've wired the scope,
|
||||
# or something could be broken in Nixpkgs.
|
||||
pkgs.testers.testEqualContents {
|
||||
assertion = "trivial patch does not change source contents";
|
||||
expected = "${./.}";
|
||||
actual =
|
||||
# Same for all components; nix-util is an arbitrary pick
|
||||
(nix.appendPatches [ pkgs.emptyFile ]).libs.nix-util.src;
|
||||
};
|
||||
}
|
||||
// (lib.optionalAttrs (builtins.elem system linux64BitSystems)) {
|
||||
dockerImage = self.hydraJobs.dockerImage.${system};
|
||||
@@ -269,7 +256,7 @@
|
||||
# TODO: enable static builds for darwin, blocked on:
|
||||
# https://github.com/NixOS/nixpkgs/issues/320448
|
||||
# TODO: disabled to speed up GHA CI.
|
||||
#"static-" = nixpkgsFor.${system}.native.pkgsStatic;
|
||||
#"static-" = nixpkgsFor.${system}.static;
|
||||
}
|
||||
)
|
||||
(
|
||||
@@ -357,8 +344,8 @@
|
||||
{
|
||||
# These attributes go right into `packages.<system>`.
|
||||
"${pkgName}" = nixpkgsFor.${system}.native.nixComponents.${pkgName};
|
||||
"${pkgName}-static" = nixpkgsFor.${system}.native.pkgsStatic.nixComponents.${pkgName};
|
||||
"${pkgName}-llvm" = nixpkgsFor.${system}.native.pkgsLLVM.nixComponents.${pkgName};
|
||||
"${pkgName}-static" = nixpkgsFor.${system}.static.nixComponents.${pkgName};
|
||||
"${pkgName}-llvm" = nixpkgsFor.${system}.llvm.nixComponents.${pkgName};
|
||||
}
|
||||
// lib.optionalAttrs supportsCross (
|
||||
flatMapAttrs (lib.genAttrs crossSystems (_: { })) (
|
||||
@@ -376,7 +363,7 @@
|
||||
{
|
||||
# These attributes go right into `packages.<system>`.
|
||||
"${pkgName}-${stdenvName}" =
|
||||
nixpkgsFor.${system}.nativeForStdenv.${stdenvName}.nixComponents.${pkgName};
|
||||
nixpkgsFor.${system}.stdenvs."${stdenvName}Packages".nixComponents.${pkgName};
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -411,7 +398,7 @@
|
||||
forAllStdenvs (
|
||||
stdenvName:
|
||||
makeShell {
|
||||
pkgs = nixpkgsFor.${system}.nativeForStdenv.${stdenvName};
|
||||
pkgs = nixpkgsFor.${system}.stdenvs."${stdenvName}Packages";
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -420,7 +407,7 @@
|
||||
forAllStdenvs (
|
||||
stdenvName:
|
||||
makeShell {
|
||||
pkgs = nixpkgsFor.${system}.nativeForStdenv.${stdenvName}.pkgsStatic;
|
||||
pkgs = nixpkgsFor.${system}.stdenvs."${stdenvName}Packages".pkgsStatic;
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -428,7 +415,7 @@
|
||||
forAllStdenvs (
|
||||
stdenvName:
|
||||
makeShell {
|
||||
pkgs = nixpkgsFor.${system}.nativeForStdenv.${stdenvName}.pkgsLLVM;
|
||||
pkgs = nixpkgsFor.${system}.stdenvs."${stdenvName}Packages".pkgsLLVM;
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -442,8 +429,7 @@
|
||||
)
|
||||
)
|
||||
// {
|
||||
native = self.devShells.${system}.native-stdenv;
|
||||
default = self.devShells.${system}.native;
|
||||
default = self.devShells.${system}.native-stdenvPackages;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -37,34 +37,6 @@
|
||||
fi
|
||||
''}";
|
||||
};
|
||||
nixfmt-rfc-style = {
|
||||
enable = true;
|
||||
excludes = [
|
||||
# Invalid
|
||||
''^tests/functional/lang/parse-.*\.nix$''
|
||||
|
||||
# Formatting-sensitive
|
||||
''^tests/functional/lang/eval-okay-curpos\.nix$''
|
||||
''^tests/functional/lang/.*comment.*\.nix$''
|
||||
''^tests/functional/lang/.*newline.*\.nix$''
|
||||
''^tests/functional/lang/.*eol.*\.nix$''
|
||||
|
||||
# Syntax tests
|
||||
''^tests/functional/shell.shebang\.nix$''
|
||||
''^tests/functional/lang/eval-okay-ind-string\.nix$''
|
||||
|
||||
# Not supported by nixfmt
|
||||
''^tests/functional/lang/eval-okay-deprecate-cursed-or\.nix$''
|
||||
''^tests/functional/lang/eval-okay-attrs5\.nix$''
|
||||
|
||||
# More syntax tests
|
||||
# These tests, or parts of them, should have been parse-* test cases.
|
||||
''^tests/functional/lang/eval-fail-eol-2\.nix$''
|
||||
''^tests/functional/lang/eval-fail-path-slash\.nix$''
|
||||
''^tests/functional/lang/eval-fail-toJSON-non-utf-8\.nix$''
|
||||
''^tests/functional/lang/eval-fail-set\.nix$''
|
||||
];
|
||||
};
|
||||
clang-format = {
|
||||
enable = true;
|
||||
# https://github.com/cachix/git-hooks.nix/pull/532
|
||||
@@ -694,6 +666,7 @@
|
||||
''^src/libutil-tests/data/git/check-data\.sh$''
|
||||
];
|
||||
};
|
||||
# TODO: nixfmt, https://github.com/NixOS/nixfmt/issues/153
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -144,9 +144,11 @@ release:
|
||||
|
||||
Make a pull request and auto-merge it.
|
||||
|
||||
* Create a backport label.
|
||||
* Create a milestone for the next release, move all unresolved issues
|
||||
from the previous milestone, and close the previous milestone. Set
|
||||
the date for the next milestone 6 weeks from now.
|
||||
|
||||
* Add the new backport label to `.mergify.yml`.
|
||||
* Create a backport label.
|
||||
|
||||
* Post an [announcement on Discourse](https://discourse.nixos.org/c/announcements/8), including the contents of
|
||||
`rl-$VERSION.md`.
|
||||
|
||||
@@ -42,7 +42,7 @@ my $flakeUrl = $evalInfo->{flake};
|
||||
my $flakeInfo = decode_json(`nix flake metadata --json "$flakeUrl"` or die) if $flakeUrl;
|
||||
my $nixRev = ($flakeInfo ? $flakeInfo->{revision} : $evalInfo->{jobsetevalinputs}->{nix}->{revision}) or die;
|
||||
|
||||
my $buildInfo = decode_json(fetch("$evalUrl/job/build.nix-everything.x86_64-linux", 'application/json'));
|
||||
my $buildInfo = decode_json(fetch("$evalUrl/job/build.nix.x86_64-linux", 'application/json'));
|
||||
#print Dumper($buildInfo);
|
||||
|
||||
my $releaseName = $buildInfo->{nixname};
|
||||
@@ -91,7 +91,7 @@ sub getStorePath {
|
||||
sub copyManual {
|
||||
my $manual;
|
||||
eval {
|
||||
$manual = getStorePath("manual");
|
||||
$manual = getStorePath("build.nix.x86_64-linux", "doc");
|
||||
};
|
||||
if ($@) {
|
||||
warn "$@";
|
||||
@@ -240,12 +240,12 @@ if ($haveDocker) {
|
||||
# Upload nix-fallback-paths.nix.
|
||||
write_file("$tmpDir/fallback-paths.nix",
|
||||
"{\n" .
|
||||
" x86_64-linux = \"" . getStorePath("build.nix-everything.x86_64-linux") . "\";\n" .
|
||||
" i686-linux = \"" . getStorePath("build.nix-everything.i686-linux") . "\";\n" .
|
||||
" aarch64-linux = \"" . getStorePath("build.nix-everything.aarch64-linux") . "\";\n" .
|
||||
" riscv64-linux = \"" . getStorePath("buildCross.nix-everything.riscv64-unknown-linux-gnu.x86_64-linux") . "\";\n" .
|
||||
" x86_64-darwin = \"" . getStorePath("build.nix-everything.x86_64-darwin") . "\";\n" .
|
||||
" aarch64-darwin = \"" . getStorePath("build.nix-everything.aarch64-darwin") . "\";\n" .
|
||||
" x86_64-linux = \"" . getStorePath("build.nix.x86_64-linux") . "\";\n" .
|
||||
" i686-linux = \"" . getStorePath("build.nix.i686-linux") . "\";\n" .
|
||||
" aarch64-linux = \"" . getStorePath("build.nix.aarch64-linux") . "\";\n" .
|
||||
" riscv64-linux = \"" . getStorePath("buildCross.nix.riscv64-unknown-linux-gnu.x86_64-linux") . "\";\n" .
|
||||
" x86_64-darwin = \"" . getStorePath("build.nix.x86_64-darwin") . "\";\n" .
|
||||
" aarch64-darwin = \"" . getStorePath("build.nix.aarch64-darwin") . "\";\n" .
|
||||
"}\n");
|
||||
|
||||
# Upload release files to S3.
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
if host_machine.system() == 'windows'
|
||||
# libexpr's primops creates a large object
|
||||
# Without the following flag, we'll get errors when cross-compiling to mingw32:
|
||||
# Fatal error: can't write 66 bytes to section .text of src/libexpr/libnixexpr.dll.p/primops.cc.obj: 'file too big'
|
||||
add_project_arguments([ '-Wa,-mbig-obj' ], language: 'cpp')
|
||||
endif
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
src,
|
||||
officialRelease,
|
||||
}:
|
||||
@@ -8,23 +7,7 @@
|
||||
scope:
|
||||
|
||||
let
|
||||
inherit (scope)
|
||||
callPackage
|
||||
;
|
||||
inherit
|
||||
(scope.callPackage (
|
||||
{ stdenv }:
|
||||
{
|
||||
inherit stdenv;
|
||||
}
|
||||
) { })
|
||||
stdenv
|
||||
;
|
||||
inherit (pkgs.buildPackages)
|
||||
meson
|
||||
ninja
|
||||
pkg-config
|
||||
;
|
||||
inherit (scope) callPackage;
|
||||
|
||||
baseVersion = lib.fileContents ../.version;
|
||||
|
||||
@@ -37,165 +20,6 @@ let
|
||||
}_${src.shortRev or "dirty"}";
|
||||
|
||||
fineVersion = baseVersion + fineVersionSuffix;
|
||||
|
||||
root = ../.;
|
||||
|
||||
# Indirection for Nixpkgs to override when package.nix files are vendored
|
||||
filesetToSource = lib.fileset.toSource;
|
||||
|
||||
/**
|
||||
Given a set of layers, create a mkDerivation-like function
|
||||
*/
|
||||
mkPackageBuilder =
|
||||
exts: userFn: stdenv.mkDerivation (lib.extends (lib.composeManyExtensions exts) userFn);
|
||||
|
||||
setVersionLayer = finalAttrs: prevAttrs: {
|
||||
preConfigure =
|
||||
prevAttrs.prevAttrs or ""
|
||||
+
|
||||
# Update the repo-global .version file.
|
||||
# Symlink ./.version points there, but by default only workDir is writable.
|
||||
''
|
||||
chmod u+w ./.version
|
||||
echo ${finalAttrs.version} > ./.version
|
||||
'';
|
||||
};
|
||||
|
||||
localSourceLayer =
|
||||
finalAttrs: prevAttrs:
|
||||
let
|
||||
workDirPath =
|
||||
# Ideally we'd pick finalAttrs.workDir, but for now `mkDerivation` has
|
||||
# the requirement that everything except passthru and meta must be
|
||||
# serialized by mkDerivation, which doesn't work for this.
|
||||
prevAttrs.workDir;
|
||||
|
||||
workDirSubpath = lib.path.removePrefix root workDirPath;
|
||||
sources =
|
||||
assert prevAttrs.fileset._type == "fileset";
|
||||
prevAttrs.fileset;
|
||||
src = lib.fileset.toSource {
|
||||
fileset = sources;
|
||||
inherit root;
|
||||
};
|
||||
|
||||
in
|
||||
{
|
||||
sourceRoot = "${src.name}/" + workDirSubpath;
|
||||
inherit src;
|
||||
|
||||
# Clear what `derivation` can't/shouldn't serialize; see prevAttrs.workDir.
|
||||
fileset = null;
|
||||
workDir = null;
|
||||
};
|
||||
|
||||
resolveRelPath = p: lib.path.removePrefix root p;
|
||||
|
||||
makeFetchedSourceLayer =
|
||||
finalScope: finalAttrs: prevAttrs:
|
||||
let
|
||||
workDirPath =
|
||||
# Ideally we'd pick finalAttrs.workDir, but for now `mkDerivation` has
|
||||
# the requirement that everything except passthru and meta must be
|
||||
# serialized by mkDerivation, which doesn't work for this.
|
||||
prevAttrs.workDir;
|
||||
|
||||
workDirSubpath = resolveRelPath workDirPath;
|
||||
|
||||
in
|
||||
{
|
||||
sourceRoot = "${finalScope.patchedSrc.name}/" + workDirSubpath;
|
||||
src = finalScope.patchedSrc;
|
||||
version =
|
||||
let
|
||||
n = lib.length finalScope.patches;
|
||||
in
|
||||
if n == 0 then finalAttrs.version else finalAttrs.version + "+${toString n}";
|
||||
|
||||
# Clear what `derivation` can't/shouldn't serialize; see prevAttrs.workDir.
|
||||
fileset = null;
|
||||
workDir = null;
|
||||
};
|
||||
|
||||
mesonLayer = finalAttrs: prevAttrs: {
|
||||
# NOTE:
|
||||
# As of https://github.com/NixOS/nixpkgs/blob/8baf8241cea0c7b30e0b8ae73474cb3de83c1a30/pkgs/by-name/me/meson/setup-hook.sh#L26,
|
||||
# `mesonBuildType` defaults to `plain` if not specified. We want our Nix-built binaries to be optimized by default.
|
||||
# More on build types here: https://mesonbuild.com/Builtin-options.html#details-for-buildtype.
|
||||
mesonBuildType = "release";
|
||||
# NOTE:
|
||||
# Users who are debugging Nix builds are expected to set the environment variable `mesonBuildType`, per the
|
||||
# guidance in https://github.com/NixOS/nix/blob/8a3fc27f1b63a08ac983ee46435a56cf49ebaf4a/doc/manual/source/development/debugging.md?plain=1#L10.
|
||||
# For this reason, we don't want to refer to `finalAttrs.mesonBuildType` here, but rather use the environment variable.
|
||||
preConfigure =
|
||||
prevAttrs.preConfigure or ""
|
||||
+
|
||||
lib.optionalString
|
||||
(
|
||||
!stdenv.hostPlatform.isWindows
|
||||
# build failure
|
||||
&& !stdenv.hostPlatform.isStatic
|
||||
# LTO breaks exception handling on x86-64-darwin.
|
||||
&& stdenv.system != "x86_64-darwin"
|
||||
)
|
||||
''
|
||||
case "$mesonBuildType" in
|
||||
release|minsize) appendToVar mesonFlags "-Db_lto=true" ;;
|
||||
*) appendToVar mesonFlags "-Db_lto=false" ;;
|
||||
esac
|
||||
'';
|
||||
nativeBuildInputs = [
|
||||
meson
|
||||
ninja
|
||||
] ++ prevAttrs.nativeBuildInputs or [ ];
|
||||
mesonCheckFlags = prevAttrs.mesonCheckFlags or [ ] ++ [
|
||||
"--print-errorlogs"
|
||||
];
|
||||
};
|
||||
|
||||
mesonBuildLayer = finalAttrs: prevAttrs: {
|
||||
nativeBuildInputs = prevAttrs.nativeBuildInputs or [ ] ++ [
|
||||
pkg-config
|
||||
];
|
||||
separateDebugInfo = !stdenv.hostPlatform.isStatic;
|
||||
hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie";
|
||||
env =
|
||||
prevAttrs.env or { }
|
||||
// lib.optionalAttrs (
|
||||
stdenv.isLinux
|
||||
&& !(stdenv.hostPlatform.isStatic && stdenv.system == "aarch64-linux")
|
||||
&& !(stdenv.hostPlatform.useLLVM or false)
|
||||
) { LDFLAGS = "-fuse-ld=gold"; };
|
||||
};
|
||||
|
||||
mesonLibraryLayer = finalAttrs: prevAttrs: {
|
||||
outputs = prevAttrs.outputs or [ "out" ] ++ [ "dev" ];
|
||||
};
|
||||
|
||||
# Work around weird `--as-needed` linker behavior with BSD, see
|
||||
# https://github.com/mesonbuild/meson/issues/3593
|
||||
bsdNoLinkAsNeeded =
|
||||
finalAttrs: prevAttrs:
|
||||
lib.optionalAttrs stdenv.hostPlatform.isBSD {
|
||||
mesonFlags = [ (lib.mesonBool "b_asneeded" false) ] ++ prevAttrs.mesonFlags or [ ];
|
||||
};
|
||||
|
||||
miscGoodPractice = finalAttrs: prevAttrs: {
|
||||
strictDeps = prevAttrs.strictDeps or true;
|
||||
enableParallelBuilding = true;
|
||||
};
|
||||
|
||||
/**
|
||||
Append patches to the source layer.
|
||||
*/
|
||||
appendPatches =
|
||||
scope: patches:
|
||||
scope.overrideScope (
|
||||
finalScope: prevScope: {
|
||||
patches = prevScope.patches ++ patches;
|
||||
}
|
||||
);
|
||||
|
||||
in
|
||||
|
||||
# This becomes the pkgs.nixComponents attribute set
|
||||
@@ -203,110 +27,6 @@ in
|
||||
version = baseVersion + versionSuffix;
|
||||
inherit versionSuffix;
|
||||
|
||||
inherit filesetToSource;
|
||||
|
||||
/**
|
||||
A user-provided extension function to apply to each component derivation.
|
||||
*/
|
||||
mesonComponentOverrides = finalAttrs: prevAttrs: { };
|
||||
|
||||
/**
|
||||
An overridable derivation layer for handling the sources.
|
||||
*/
|
||||
sourceLayer = localSourceLayer;
|
||||
|
||||
/**
|
||||
Resolve a path value to either itself or a path in the `src`, depending
|
||||
whether `overrideSource` was called.
|
||||
*/
|
||||
resolvePath = p: p;
|
||||
|
||||
/**
|
||||
Apply an extension function (i.e. overlay-shaped) to all component derivations.
|
||||
*/
|
||||
overrideAllMesonComponents =
|
||||
f:
|
||||
scope.overrideScope (
|
||||
finalScope: prevScope: {
|
||||
mesonComponentOverrides = lib.composeExtensions scope.mesonComponentOverrides f;
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
Provide an alternate source. This allows the expressions to be vendored without copying the sources,
|
||||
but it does make the build non-granular; all components will use a complete source.
|
||||
|
||||
Packaging expressions will be ignored.
|
||||
*/
|
||||
overrideSource =
|
||||
src:
|
||||
scope.overrideScope (
|
||||
finalScope: prevScope: {
|
||||
sourceLayer = makeFetchedSourceLayer finalScope;
|
||||
/**
|
||||
Unpatched source for the build of Nix. Packaging expressions will be ignored.
|
||||
*/
|
||||
src = src;
|
||||
/**
|
||||
Patches for the whole Nix source. Changes to packaging expressions will be ignored.
|
||||
*/
|
||||
patches = [ ];
|
||||
/**
|
||||
Fetched and patched source to be used in component derivations.
|
||||
*/
|
||||
patchedSrc =
|
||||
if finalScope.patches == [ ] then
|
||||
src
|
||||
else
|
||||
pkgs.buildPackages.srcOnly (
|
||||
pkgs.buildPackages.stdenvNoCC.mkDerivation {
|
||||
name = "${finalScope.src.name or "nix-source"}-patched";
|
||||
inherit (finalScope) src patches;
|
||||
}
|
||||
);
|
||||
resolvePath = p: finalScope.patchedSrc + "/${resolveRelPath p}";
|
||||
appendPatches = appendPatches finalScope;
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
Append patches to be applied to the whole Nix source.
|
||||
This affects all components.
|
||||
|
||||
Changes to the packaging expressions will be ignored.
|
||||
*/
|
||||
appendPatches =
|
||||
patches:
|
||||
# switch to "fetched" source first, so that patches apply to the whole tree.
|
||||
(scope.overrideSource "${./..}").appendPatches patches;
|
||||
|
||||
mkMesonDerivation = mkPackageBuilder [
|
||||
miscGoodPractice
|
||||
scope.sourceLayer
|
||||
setVersionLayer
|
||||
mesonLayer
|
||||
scope.mesonComponentOverrides
|
||||
];
|
||||
mkMesonExecutable = mkPackageBuilder [
|
||||
miscGoodPractice
|
||||
bsdNoLinkAsNeeded
|
||||
scope.sourceLayer
|
||||
setVersionLayer
|
||||
mesonLayer
|
||||
mesonBuildLayer
|
||||
scope.mesonComponentOverrides
|
||||
];
|
||||
mkMesonLibrary = mkPackageBuilder [
|
||||
miscGoodPractice
|
||||
bsdNoLinkAsNeeded
|
||||
scope.sourceLayer
|
||||
mesonLayer
|
||||
setVersionLayer
|
||||
mesonBuildLayer
|
||||
mesonLibraryLayer
|
||||
scope.mesonComponentOverrides
|
||||
];
|
||||
|
||||
nix-util = callPackage ../src/libutil/package.nix { };
|
||||
nix-util-c = callPackage ../src/libutil-c/package.nix { };
|
||||
nix-util-test-support = callPackage ../src/libutil-test-support/package.nix { };
|
||||
@@ -346,33 +66,5 @@ in
|
||||
|
||||
nix-perl-bindings = callPackage ../src/perl/package.nix { };
|
||||
|
||||
nix-everything = callPackage ../packaging/everything.nix { } // {
|
||||
# Note: no `passthru.overrideAllMesonComponents`
|
||||
# This would propagate into `nix.overrideAttrs f`, but then discard
|
||||
# `f` when `.overrideAllMesonComponents` is used.
|
||||
# Both "methods" should be views on the same fixpoint overriding mechanism
|
||||
# for that to work. For now, we intentionally don't support the broken
|
||||
# two-fixpoint solution.
|
||||
/**
|
||||
Apply an extension function (i.e. overlay-shaped) to all component derivations, and return the nix package.
|
||||
*/
|
||||
overrideAllMesonComponents = f: (scope.overrideAllMesonComponents f).nix-everything;
|
||||
|
||||
/**
|
||||
Append patches to be applied to the whole Nix source.
|
||||
This affects all components.
|
||||
|
||||
Changes to the packaging expressions will be ignored.
|
||||
*/
|
||||
appendPatches = ps: (scope.appendPatches ps).nix-everything;
|
||||
|
||||
/**
|
||||
Provide an alternate source. This allows the expressions to be vendored without copying the sources,
|
||||
but it does make the build non-granular; all components will use a complete source.
|
||||
|
||||
Packaging expressions will be ignored.
|
||||
*/
|
||||
overrideSource = src: (scope.overrideSource src).nix-everything;
|
||||
|
||||
};
|
||||
nix-everything = callPackage ../packaging/everything.nix { };
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ in
|
||||
let
|
||||
inherit (pkgs) lib;
|
||||
|
||||
root = ../.;
|
||||
|
||||
stdenv = if prevStdenv.isDarwin && prevStdenv.isx86_64 then darwinStdenv else prevStdenv;
|
||||
|
||||
# Fix the following error with the default x86_64-darwin SDK:
|
||||
@@ -28,6 +30,113 @@ let
|
||||
# all the way back to 10.6.
|
||||
darwinStdenv = pkgs.overrideSDK prevStdenv { darwinMinVersion = "10.13"; };
|
||||
|
||||
# Nixpkgs implements this by returning a subpath into the fetched Nix sources.
|
||||
resolvePath = p: p;
|
||||
|
||||
# Indirection for Nixpkgs to override when package.nix files are vendored
|
||||
filesetToSource = lib.fileset.toSource;
|
||||
|
||||
/**
|
||||
Given a set of layers, create a mkDerivation-like function
|
||||
*/
|
||||
mkPackageBuilder =
|
||||
exts: userFn: stdenv.mkDerivation (lib.extends (lib.composeManyExtensions exts) userFn);
|
||||
|
||||
localSourceLayer =
|
||||
finalAttrs: prevAttrs:
|
||||
let
|
||||
workDirPath =
|
||||
# Ideally we'd pick finalAttrs.workDir, but for now `mkDerivation` has
|
||||
# the requirement that everything except passthru and meta must be
|
||||
# serialized by mkDerivation, which doesn't work for this.
|
||||
prevAttrs.workDir;
|
||||
|
||||
workDirSubpath = lib.path.removePrefix root workDirPath;
|
||||
sources =
|
||||
assert prevAttrs.fileset._type == "fileset";
|
||||
prevAttrs.fileset;
|
||||
src = lib.fileset.toSource {
|
||||
fileset = sources;
|
||||
inherit root;
|
||||
};
|
||||
|
||||
in
|
||||
{
|
||||
sourceRoot = "${src.name}/" + workDirSubpath;
|
||||
inherit src;
|
||||
|
||||
# Clear what `derivation` can't/shouldn't serialize; see prevAttrs.workDir.
|
||||
fileset = null;
|
||||
workDir = null;
|
||||
};
|
||||
|
||||
mesonLayer = finalAttrs: prevAttrs: {
|
||||
# NOTE:
|
||||
# As of https://github.com/NixOS/nixpkgs/blob/8baf8241cea0c7b30e0b8ae73474cb3de83c1a30/pkgs/by-name/me/meson/setup-hook.sh#L26,
|
||||
# `mesonBuildType` defaults to `plain` if not specified. We want our Nix-built binaries to be optimized by default.
|
||||
# More on build types here: https://mesonbuild.com/Builtin-options.html#details-for-buildtype.
|
||||
mesonBuildType = "release";
|
||||
# NOTE:
|
||||
# Users who are debugging Nix builds are expected to set the environment variable `mesonBuildType`, per the
|
||||
# guidance in https://github.com/NixOS/nix/blob/8a3fc27f1b63a08ac983ee46435a56cf49ebaf4a/doc/manual/source/development/debugging.md?plain=1#L10.
|
||||
# For this reason, we don't want to refer to `finalAttrs.mesonBuildType` here, but rather use the environment variable.
|
||||
preConfigure =
|
||||
prevAttrs.preConfigure or ""
|
||||
+
|
||||
lib.optionalString
|
||||
(
|
||||
!stdenv.hostPlatform.isWindows
|
||||
# build failure
|
||||
&& !stdenv.hostPlatform.isStatic
|
||||
# LTO breaks exception handling on x86-64-darwin.
|
||||
&& stdenv.system != "x86_64-darwin"
|
||||
)
|
||||
''
|
||||
case "$mesonBuildType" in
|
||||
release|minsize) appendToVar mesonFlags "-Db_lto=true" ;;
|
||||
*) appendToVar mesonFlags "-Db_lto=false" ;;
|
||||
esac
|
||||
'';
|
||||
nativeBuildInputs = [
|
||||
pkgs.buildPackages.meson
|
||||
pkgs.buildPackages.ninja
|
||||
] ++ prevAttrs.nativeBuildInputs or [ ];
|
||||
mesonCheckFlags = prevAttrs.mesonCheckFlags or [ ] ++ [
|
||||
"--print-errorlogs"
|
||||
];
|
||||
};
|
||||
|
||||
mesonBuildLayer = finalAttrs: prevAttrs: {
|
||||
nativeBuildInputs = prevAttrs.nativeBuildInputs or [ ] ++ [
|
||||
pkgs.buildPackages.pkg-config
|
||||
];
|
||||
separateDebugInfo = !stdenv.hostPlatform.isStatic;
|
||||
hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie";
|
||||
env =
|
||||
prevAttrs.env or { }
|
||||
// lib.optionalAttrs (
|
||||
stdenv.isLinux
|
||||
&& !(stdenv.hostPlatform.isStatic && stdenv.system == "aarch64-linux")
|
||||
&& !(stdenv.hostPlatform.useLLVM or false)
|
||||
) { LDFLAGS = "-fuse-ld=gold"; };
|
||||
};
|
||||
|
||||
mesonLibraryLayer = finalAttrs: prevAttrs: {
|
||||
outputs = prevAttrs.outputs or [ "out" ] ++ [ "dev" ];
|
||||
};
|
||||
|
||||
# Work around weird `--as-needed` linker behavior with BSD, see
|
||||
# https://github.com/mesonbuild/meson/issues/3593
|
||||
bsdNoLinkAsNeeded =
|
||||
finalAttrs: prevAttrs:
|
||||
lib.optionalAttrs stdenv.hostPlatform.isBSD {
|
||||
mesonFlags = [ (lib.mesonBool "b_asneeded" false) ] ++ prevAttrs.mesonFlags or [ ];
|
||||
};
|
||||
|
||||
miscGoodPractice = finalAttrs: prevAttrs: {
|
||||
strictDeps = prevAttrs.strictDeps or true;
|
||||
enableParallelBuilding = true;
|
||||
};
|
||||
in
|
||||
scope: {
|
||||
inherit stdenv;
|
||||
@@ -65,39 +174,56 @@ scope: {
|
||||
installPhase = lib.replaceStrings [ "--without-python" ] [ "" ] old.installPhase;
|
||||
});
|
||||
|
||||
libgit2 = pkgs.libgit2.overrideAttrs (
|
||||
attrs:
|
||||
{
|
||||
cmakeFlags = attrs.cmakeFlags or [ ] ++ [ "-DUSE_SSH=exec" ];
|
||||
}
|
||||
# libgit2: Nixpkgs 24.11 has < 1.9.0, which needs our patches
|
||||
// lib.optionalAttrs (!lib.versionAtLeast pkgs.libgit2.version "1.9.0") {
|
||||
nativeBuildInputs =
|
||||
attrs.nativeBuildInputs or [ ]
|
||||
# gitMinimal does not build on Windows. See packbuilder patch.
|
||||
++ lib.optionals (!stdenv.hostPlatform.isWindows) [
|
||||
# Needed for `git apply`; see `prePatch`
|
||||
pkgs.buildPackages.gitMinimal
|
||||
];
|
||||
# Only `git apply` can handle git binary patches
|
||||
prePatch =
|
||||
attrs.prePatch or ""
|
||||
+ lib.optionalString (!stdenv.hostPlatform.isWindows) ''
|
||||
patch() {
|
||||
git apply
|
||||
}
|
||||
'';
|
||||
patches =
|
||||
attrs.patches or [ ]
|
||||
++ [
|
||||
./patches/libgit2-mempack-thin-packfile.patch
|
||||
]
|
||||
# gitMinimal does not build on Windows, but fortunately this patch only
|
||||
# impacts interruptibility
|
||||
++ lib.optionals (!stdenv.hostPlatform.isWindows) [
|
||||
# binary patch; see `prePatch`
|
||||
./patches/libgit2-packbuilder-callback-interruptible.patch
|
||||
];
|
||||
}
|
||||
);
|
||||
libgit2 = pkgs.libgit2.overrideAttrs (attrs: {
|
||||
cmakeFlags = attrs.cmakeFlags or [ ] ++ [ "-DUSE_SSH=exec" ];
|
||||
nativeBuildInputs =
|
||||
attrs.nativeBuildInputs or [ ]
|
||||
# gitMinimal does not build on Windows. See packbuilder patch.
|
||||
++ lib.optionals (!stdenv.hostPlatform.isWindows) [
|
||||
# Needed for `git apply`; see `prePatch`
|
||||
pkgs.buildPackages.gitMinimal
|
||||
];
|
||||
# Only `git apply` can handle git binary patches
|
||||
prePatch =
|
||||
attrs.prePatch or ""
|
||||
+ lib.optionalString (!stdenv.hostPlatform.isWindows) ''
|
||||
patch() {
|
||||
git apply
|
||||
}
|
||||
'';
|
||||
patches =
|
||||
attrs.patches or [ ]
|
||||
++ [
|
||||
./patches/libgit2-mempack-thin-packfile.patch
|
||||
]
|
||||
# gitMinimal does not build on Windows, but fortunately this patch only
|
||||
# impacts interruptibility
|
||||
++ lib.optionals (!stdenv.hostPlatform.isWindows) [
|
||||
# binary patch; see `prePatch`
|
||||
./patches/libgit2-packbuilder-callback-interruptible.patch
|
||||
];
|
||||
});
|
||||
|
||||
inherit resolvePath filesetToSource;
|
||||
|
||||
mkMesonDerivation = mkPackageBuilder [
|
||||
miscGoodPractice
|
||||
localSourceLayer
|
||||
mesonLayer
|
||||
];
|
||||
mkMesonExecutable = mkPackageBuilder [
|
||||
miscGoodPractice
|
||||
bsdNoLinkAsNeeded
|
||||
localSourceLayer
|
||||
mesonLayer
|
||||
mesonBuildLayer
|
||||
];
|
||||
mkMesonLibrary = mkPackageBuilder [
|
||||
miscGoodPractice
|
||||
bsdNoLinkAsNeeded
|
||||
localSourceLayer
|
||||
mesonLayer
|
||||
mesonBuildLayer
|
||||
mesonLibraryLayer
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
{
|
||||
lib,
|
||||
devFlake,
|
||||
}:
|
||||
{ lib, devFlake }:
|
||||
|
||||
{ pkgs }:
|
||||
|
||||
@@ -116,7 +113,6 @@ pkgs.nixComponents.nix-util.overrideAttrs (
|
||||
pkgs.buildPackages.changelog-d
|
||||
modular.pre-commit.settings.package
|
||||
(pkgs.writeScriptBin "pre-commit-hooks-install" modular.pre-commit.settings.installationScript)
|
||||
pkgs.buildPackages.nixfmt-rfc-style
|
||||
]
|
||||
# TODO: Remove the darwin check once
|
||||
# https://github.com/NixOS/nixpkgs/pull/291814 is available
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
inputs,
|
||||
binaryTarball,
|
||||
forAllCrossSystems,
|
||||
forAllSystems,
|
||||
lib,
|
||||
@@ -13,7 +14,7 @@ let
|
||||
|
||||
installScriptFor =
|
||||
tarballs:
|
||||
nixpkgsFor.x86_64-linux.native.callPackage ./installer {
|
||||
nixpkgsFor.x86_64-linux.native.callPackage ../scripts/installer.nix {
|
||||
inherit tarballs;
|
||||
};
|
||||
|
||||
@@ -66,9 +67,7 @@ in
|
||||
|
||||
buildStatic = forAllPackages (
|
||||
pkgName:
|
||||
lib.genAttrs linux64BitSystems (
|
||||
system: nixpkgsFor.${system}.native.pkgsStatic.nixComponents.${pkgName}
|
||||
)
|
||||
lib.genAttrs linux64BitSystems (system: nixpkgsFor.${system}.static.nixComponents.${pkgName})
|
||||
);
|
||||
|
||||
buildCross = forAllPackages (
|
||||
@@ -130,13 +129,15 @@ in
|
||||
# with the closure of 'nix' package, and the second half of
|
||||
# the installation script.
|
||||
binaryTarball = forAllSystems (
|
||||
system: nixpkgsFor.${system}.native.callPackage ./binary-tarball.nix { }
|
||||
system: binaryTarball nixpkgsFor.${system}.native.nix nixpkgsFor.${system}.native
|
||||
);
|
||||
|
||||
binaryTarballCross = lib.genAttrs [ "x86_64-linux" ] (
|
||||
system:
|
||||
forAllCrossSystems (
|
||||
crossSystem: nixpkgsFor.${system}.cross.${crossSystem}.callPackage ./binary-tarball.nix { }
|
||||
crossSystem:
|
||||
binaryTarball nixpkgsFor.${system}.cross.${crossSystem}.nix
|
||||
nixpkgsFor.${system}.cross.${crossSystem}
|
||||
)
|
||||
);
|
||||
|
||||
@@ -159,7 +160,7 @@ in
|
||||
|
||||
installerScriptForGHA = forAllSystems (
|
||||
system:
|
||||
nixpkgsFor.${system}.native.callPackage ./installer {
|
||||
nixpkgsFor.${system}.native.callPackage ../scripts/installer.nix {
|
||||
tarballs = [ self.hydraJobs.binaryTarball.${system} ];
|
||||
}
|
||||
);
|
||||
@@ -185,8 +186,12 @@ in
|
||||
# System tests.
|
||||
tests =
|
||||
import ../tests/nixos {
|
||||
inherit lib nixpkgs nixpkgsFor;
|
||||
inherit (self.inputs) nixpkgs-23-11;
|
||||
inherit
|
||||
lib
|
||||
nixpkgs
|
||||
nixpkgsFor
|
||||
self
|
||||
;
|
||||
}
|
||||
// {
|
||||
|
||||
|
||||
@@ -26,18 +26,18 @@ in
|
||||
|
||||
runCommand "nix-binary-tarball-${version}" env ''
|
||||
cp ${installerClosureInfo}/registration $TMPDIR/reginfo
|
||||
cp ${../scripts/create-darwin-volume.sh} $TMPDIR/create-darwin-volume.sh
|
||||
substitute ${../scripts/install-nix-from-tarball.sh} $TMPDIR/install \
|
||||
cp ${./create-darwin-volume.sh} $TMPDIR/create-darwin-volume.sh
|
||||
substitute ${./install-nix-from-tarball.sh} $TMPDIR/install \
|
||||
--subst-var-by nix ${nix} \
|
||||
--subst-var-by cacert ${cacert}
|
||||
|
||||
substitute ${../scripts/install-darwin-multi-user.sh} $TMPDIR/install-darwin-multi-user.sh \
|
||||
substitute ${./install-darwin-multi-user.sh} $TMPDIR/install-darwin-multi-user.sh \
|
||||
--subst-var-by nix ${nix} \
|
||||
--subst-var-by cacert ${cacert}
|
||||
substitute ${../scripts/install-systemd-multi-user.sh} $TMPDIR/install-systemd-multi-user.sh \
|
||||
substitute ${./install-systemd-multi-user.sh} $TMPDIR/install-systemd-multi-user.sh \
|
||||
--subst-var-by nix ${nix} \
|
||||
--subst-var-by cacert ${cacert}
|
||||
substitute ${../scripts/install-multi-user.sh} $TMPDIR/install-multi-user \
|
||||
substitute ${./install-multi-user.sh} $TMPDIR/install-multi-user \
|
||||
--subst-var-by nix ${nix} \
|
||||
--subst-var-by cacert ${cacert}
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
# Only execute this file once per shell.
|
||||
if test -z "$HOME" || \
|
||||
test -n "$__ETC_PROFILE_NIX_SOURCED"
|
||||
exit
|
||||
end
|
||||
|
||||
set --global __ETC_PROFILE_NIX_SOURCED 1
|
||||
|
||||
# Local helpers
|
||||
|
||||
function add_path --argument-names new_path
|
||||
if type -q fish_add_path
|
||||
# fish 3.2.0 or newer
|
||||
@@ -20,51 +10,48 @@ function add_path --argument-names new_path
|
||||
end
|
||||
end
|
||||
|
||||
# Main configuration
|
||||
# Only execute this file once per shell.
|
||||
if test -n "$__ETC_PROFILE_NIX_SOURCED"
|
||||
exit
|
||||
end
|
||||
|
||||
# Set up the per-user profile.
|
||||
set __ETC_PROFILE_NIX_SOURCED 1
|
||||
|
||||
set --local NIX_LINK $HOME/.nix-profile
|
||||
|
||||
# Set up environment.
|
||||
# This part should be kept in sync with nixpkgs:nixos/modules/programs/environment.nix
|
||||
set --export NIX_PROFILES "@localstatedir@/nix/profiles/default $HOME/.nix-profile"
|
||||
|
||||
# Populate bash completions, .desktop files, etc
|
||||
if test -z "$XDG_DATA_DIRS"
|
||||
# According to XDG spec the default is /usr/local/share:/usr/share, don't set something that prevents that default
|
||||
set --export XDG_DATA_DIRS "/usr/local/share:/usr/share:$NIX_LINK/share:/nix/var/nix/profiles/default/share"
|
||||
set --export XDG_DATA_DIRS "/usr/local/share:/usr/share:/nix/var/nix/profiles/default/share"
|
||||
else
|
||||
set --export XDG_DATA_DIRS "$XDG_DATA_DIRS:$NIX_LINK/share:/nix/var/nix/profiles/default/share"
|
||||
set --export XDG_DATA_DIRS "$XDG_DATA_DIRS:/nix/var/nix/profiles/default/share"
|
||||
end
|
||||
|
||||
# Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work.
|
||||
if test -n "$NIX_SSL_CERT_FILE"
|
||||
: # Allow users to override the NIX_SSL_CERT_FILE
|
||||
: # Allow users to override the NIX_SSL_CERT_FILE
|
||||
else if test -e /etc/ssl/certs/ca-certificates.crt # NixOS, Ubuntu, Debian, Gentoo, Arch
|
||||
set --export NIX_SSL_CERT_FILE /etc/ssl/certs/ca-certificates.crt
|
||||
set --export NIX_SSL_CERT_FILE /etc/ssl/certs/ca-certificates.crt
|
||||
else if test -e /etc/ssl/ca-bundle.pem # openSUSE Tumbleweed
|
||||
set --export NIX_SSL_CERT_FILE /etc/ssl/ca-bundle.pem
|
||||
set --export NIX_SSL_CERT_FILE /etc/ssl/ca-bundle.pem
|
||||
else if test -e /etc/ssl/certs/ca-bundle.crt # Old NixOS
|
||||
set --export NIX_SSL_CERT_FILE /etc/ssl/certs/ca-bundle.crt
|
||||
set --export NIX_SSL_CERT_FILE /etc/ssl/certs/ca-bundle.crt
|
||||
else if test -e /etc/pki/tls/certs/ca-bundle.crt # Fedora, CentOS
|
||||
set --export NIX_SSL_CERT_FILE /etc/pki/tls/certs/ca-bundle.crt
|
||||
set --export NIX_SSL_CERT_FILE /etc/pki/tls/certs/ca-bundle.crt
|
||||
else if test -e "$NIX_LINK/etc/ssl/certs/ca-bundle.crt" # fall back to cacert in Nix profile
|
||||
set --export NIX_SSL_CERT_FILE "$NIX_LINK/etc/ssl/certs/ca-bundle.crt"
|
||||
set --export NIX_SSL_CERT_FILE "$NIX_LINK/etc/ssl/certs/ca-bundle.crt"
|
||||
else if test -e "$NIX_LINK/etc/ca-bundle.crt" # old cacert in Nix profile
|
||||
set --export NIX_SSL_CERT_FILE "$NIX_LINK/etc/ca-bundle.crt"
|
||||
set --export NIX_SSL_CERT_FILE "$NIX_LINK/etc/ca-bundle.crt"
|
||||
else
|
||||
# Fall back to what is in the nix profiles, favouring whatever is defined last.
|
||||
for i in (string split ' ' $NIX_PROFILES)
|
||||
if test -e "$i/etc/ssl/certs/ca-bundle.crt"
|
||||
set --export NIX_SSL_CERT_FILE "$i/etc/ssl/certs/ca-bundle.crt"
|
||||
end
|
||||
# Fall back to what is in the nix profiles, favouring whatever is defined last.
|
||||
for i in (string split ' ' $NIX_PROFILES)
|
||||
if test -e "$i/etc/ssl/certs/ca-bundle.crt"
|
||||
set --export NIX_SSL_CERT_FILE "$i/etc/ssl/certs/ca-bundle.crt"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
add_path "@localstatedir@/nix/profiles/default/bin"
|
||||
add_path "$NIX_LINK/bin"
|
||||
|
||||
# Cleanup
|
||||
add_path "$HOME/.nix-profile/bin"
|
||||
|
||||
functions -e add_path
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
# Only execute this file once per shell.
|
||||
if test -z "$HOME" || test -z "$USER" || \
|
||||
test -n "$__ETC_PROFILE_NIX_SOURCED"
|
||||
exit
|
||||
end
|
||||
|
||||
set --global __ETC_PROFILE_NIX_SOURCED 1
|
||||
|
||||
# Local helpers
|
||||
|
||||
function add_path --argument-names new_path
|
||||
if type -q fish_add_path
|
||||
# fish 3.2.0 or newer
|
||||
@@ -20,50 +10,50 @@ function add_path --argument-names new_path
|
||||
end
|
||||
end
|
||||
|
||||
# Main configuration
|
||||
if test -n "$HOME" && test -n "$USER"
|
||||
|
||||
# Set up the per-user profile.
|
||||
# Set up the per-user profile.
|
||||
|
||||
set --local NIX_LINK $HOME/.nix-profile
|
||||
set NIX_LINK $HOME/.nix-profile
|
||||
|
||||
# Set up environment.
|
||||
# This part should be kept in sync with nixpkgs:nixos/modules/programs/environment.nix
|
||||
set --export NIX_PROFILES "@localstatedir@/nix/profiles/default $HOME/.nix-profile"
|
||||
# Set up environment.
|
||||
# This part should be kept in sync with nixpkgs:nixos/modules/programs/environment.nix
|
||||
set --export NIX_PROFILES "@localstatedir@/nix/profiles/default $HOME/.nix-profile"
|
||||
|
||||
# Populate bash completions, .desktop files, etc
|
||||
if test -z "$XDG_DATA_DIRS"
|
||||
# According to XDG spec the default is /usr/local/share:/usr/share, don't set something that prevents that default
|
||||
set --export XDG_DATA_DIRS "/usr/local/share:/usr/share:$NIX_LINK/share:/nix/var/nix/profiles/default/share"
|
||||
else
|
||||
set --export XDG_DATA_DIRS "$XDG_DATA_DIRS:$NIX_LINK/share:/nix/var/nix/profiles/default/share"
|
||||
# Populate bash completions, .desktop files, etc
|
||||
if test -z "$XDG_DATA_DIRS"
|
||||
# According to XDG spec the default is /usr/local/share:/usr/share, don't set something that prevents that default
|
||||
set --export XDG_DATA_DIRS "/usr/local/share:/usr/share:$NIX_LINK/share:/nix/var/nix/profiles/default/share"
|
||||
else
|
||||
set --export XDG_DATA_DIRS "$XDG_DATA_DIRS:$NIX_LINK/share:/nix/var/nix/profiles/default/share"
|
||||
end
|
||||
|
||||
# Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work.
|
||||
if test -n "$NIX_SSL_CERT_FILE"
|
||||
: # Allow users to override the NIX_SSL_CERT_FILE
|
||||
else if test -e /etc/ssl/certs/ca-certificates.crt # NixOS, Ubuntu, Debian, Gentoo, Arch
|
||||
set --export NIX_SSL_CERT_FILE /etc/ssl/certs/ca-certificates.crt
|
||||
else if test -e /etc/ssl/ca-bundle.pem # openSUSE Tumbleweed
|
||||
set --export NIX_SSL_CERT_FILE /etc/ssl/ca-bundle.pem
|
||||
else if test -e /etc/ssl/certs/ca-bundle.crt # Old NixOS
|
||||
set --export NIX_SSL_CERT_FILE /etc/ssl/certs/ca-bundle.crt
|
||||
else if test -e /etc/pki/tls/certs/ca-bundle.crt # Fedora, CentOS
|
||||
set --export NIX_SSL_CERT_FILE /etc/pki/tls/certs/ca-bundle.crt
|
||||
else if test -e "$NIX_LINK/etc/ssl/certs/ca-bundle.crt" # fall back to cacert in Nix profile
|
||||
set --export NIX_SSL_CERT_FILE "$NIX_LINK/etc/ssl/certs/ca-bundle.crt"
|
||||
else if test -e "$NIX_LINK/etc/ca-bundle.crt" # old cacert in Nix profile
|
||||
set --export NIX_SSL_CERT_FILE "$NIX_LINK/etc/ca-bundle.crt"
|
||||
end
|
||||
|
||||
# Only use MANPATH if it is already set. In general `man` will just simply
|
||||
# pick up `.nix-profile/share/man` because is it close to `.nix-profile/bin`
|
||||
# which is in the $PATH. For more info, run `manpath -d`.
|
||||
if set --query MANPATH
|
||||
set --export --prepend --path MANPATH "$NIX_LINK/share/man"
|
||||
end
|
||||
|
||||
add_path "$NIX_LINK/bin"
|
||||
set --erase NIX_LINK
|
||||
end
|
||||
|
||||
# Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work.
|
||||
if test -n "$NIX_SSL_CERT_FILE"
|
||||
: # Allow users to override the NIX_SSL_CERT_FILE
|
||||
else if test -e /etc/ssl/certs/ca-certificates.crt # NixOS, Ubuntu, Debian, Gentoo, Arch
|
||||
set --export NIX_SSL_CERT_FILE /etc/ssl/certs/ca-certificates.crt
|
||||
else if test -e /etc/ssl/ca-bundle.pem # openSUSE Tumbleweed
|
||||
set --export NIX_SSL_CERT_FILE /etc/ssl/ca-bundle.pem
|
||||
else if test -e /etc/ssl/certs/ca-bundle.crt # Old NixOS
|
||||
set --export NIX_SSL_CERT_FILE /etc/ssl/certs/ca-bundle.crt
|
||||
else if test -e /etc/pki/tls/certs/ca-bundle.crt # Fedora, CentOS
|
||||
set --export NIX_SSL_CERT_FILE /etc/pki/tls/certs/ca-bundle.crt
|
||||
else if test -e "$NIX_LINK/etc/ssl/certs/ca-bundle.crt" # fall back to cacert in Nix profile
|
||||
set --export NIX_SSL_CERT_FILE "$NIX_LINK/etc/ssl/certs/ca-bundle.crt"
|
||||
else if test -e "$NIX_LINK/etc/ca-bundle.crt" # old cacert in Nix profile
|
||||
set --export NIX_SSL_CERT_FILE "$NIX_LINK/etc/ca-bundle.crt"
|
||||
end
|
||||
|
||||
# Only use MANPATH if it is already set. In general `man` will just simply
|
||||
# pick up `.nix-profile/share/man` because is it close to `.nix-profile/bin`
|
||||
# which is in the $PATH. For more info, run `manpath -d`.
|
||||
if set --query MANPATH
|
||||
set --export --prepend --path MANPATH "$NIX_LINK/share/man"
|
||||
end
|
||||
|
||||
add_path "$NIX_LINK/bin"
|
||||
|
||||
# Cleanup
|
||||
|
||||
functions -e add_path
|
||||
|
||||
@@ -51,7 +51,7 @@ static bool allSupportedLocally(Store & store, const std::set<std::string>& requ
|
||||
static int main_build_remote(int argc, char * * argv)
|
||||
{
|
||||
{
|
||||
logger = makeJSONLogger(getStandardError());
|
||||
logger = makeJSONLogger(*logger);
|
||||
|
||||
/* Ensure we don't get any SSH passphrase or host key popups. */
|
||||
unsetenv("DISPLAY");
|
||||
|
||||
@@ -347,7 +347,7 @@ struct MixEnvironment : virtual Args
|
||||
void setEnviron();
|
||||
};
|
||||
|
||||
void completeFlakeInputAttrPath(
|
||||
void completeFlakeInputPath(
|
||||
AddCompletions & completions,
|
||||
ref<EvalState> evalState,
|
||||
const std::vector<FlakeRef> & flakeRefs,
|
||||
|
||||
@@ -34,10 +34,8 @@ EvalSettings evalSettings {
|
||||
// FIXME `parseFlakeRef` should take a `std::string_view`.
|
||||
auto flakeRef = parseFlakeRef(fetchSettings, std::string { rest }, {}, true, false);
|
||||
debug("fetching flake search path element '%s''", rest);
|
||||
auto [accessor, lockedRef] = flakeRef.resolve(state.store).lazyFetch(state.store);
|
||||
auto storePath = nix::fetchToStore(*state.store, SourcePath(accessor), FetchMode::Copy, lockedRef.input.getName());
|
||||
state.allowPath(storePath);
|
||||
return state.storePath(storePath);
|
||||
auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first;
|
||||
return state.rootPath(state.store->toRealPath(storePath));
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -179,16 +177,14 @@ SourcePath lookupFileArg(EvalState & state, std::string_view s, const Path * bas
|
||||
state.fetchSettings,
|
||||
EvalSettings::resolvePseudoUrl(s));
|
||||
auto storePath = fetchToStore(*state.store, SourcePath(accessor), FetchMode::Copy);
|
||||
return state.storePath(storePath);
|
||||
return state.rootPath(CanonPath(state.store->toRealPath(storePath)));
|
||||
}
|
||||
|
||||
else if (hasPrefix(s, "flake:")) {
|
||||
experimentalFeatureSettings.require(Xp::Flakes);
|
||||
auto flakeRef = parseFlakeRef(fetchSettings, std::string(s.substr(6)), {}, true, false);
|
||||
auto [accessor, lockedRef] = flakeRef.resolve(state.store).lazyFetch(state.store);
|
||||
auto storePath = nix::fetchToStore(*state.store, SourcePath(accessor), FetchMode::Copy, lockedRef.input.getName());
|
||||
state.allowPath(storePath);
|
||||
return state.storePath(storePath);
|
||||
auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first;
|
||||
return state.rootPath(CanonPath(state.store->toRealPath(storePath)));
|
||||
}
|
||||
|
||||
else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') {
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace nix {
|
||||
|
||||
namespace fs { using namespace std::filesystem; }
|
||||
|
||||
void completeFlakeInputAttrPath(
|
||||
void completeFlakeInputPath(
|
||||
AddCompletions & completions,
|
||||
ref<EvalState> evalState,
|
||||
const std::vector<FlakeRef> & flakeRefs,
|
||||
@@ -117,10 +117,10 @@ MixFlakeOptions::MixFlakeOptions()
|
||||
.labels = {"input-path"},
|
||||
.handler = {[&](std::string s) {
|
||||
warn("'--update-input' is a deprecated alias for 'flake update' and will be removed in a future version.");
|
||||
lockFlags.inputUpdates.insert(flake::parseInputAttrPath(s));
|
||||
lockFlags.inputUpdates.insert(flake::parseInputPath(s));
|
||||
}},
|
||||
.completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) {
|
||||
completeFlakeInputAttrPath(completions, getEvalState(), getFlakeRefsForCompletion(), prefix);
|
||||
completeFlakeInputPath(completions, getEvalState(), getFlakeRefsForCompletion(), prefix);
|
||||
}}
|
||||
});
|
||||
|
||||
@@ -129,15 +129,15 @@ MixFlakeOptions::MixFlakeOptions()
|
||||
.description = "Override a specific flake input (e.g. `dwarffs/nixpkgs`). This implies `--no-write-lock-file`.",
|
||||
.category = category,
|
||||
.labels = {"input-path", "flake-url"},
|
||||
.handler = {[&](std::string inputAttrPath, std::string flakeRef) {
|
||||
.handler = {[&](std::string inputPath, std::string flakeRef) {
|
||||
lockFlags.writeLockFile = false;
|
||||
lockFlags.inputOverrides.insert_or_assign(
|
||||
flake::parseInputAttrPath(inputAttrPath),
|
||||
flake::parseInputPath(inputPath),
|
||||
parseFlakeRef(fetchSettings, flakeRef, absPath(getCommandBaseDir()), true));
|
||||
}},
|
||||
.completer = {[&](AddCompletions & completions, size_t n, std::string_view prefix) {
|
||||
if (n == 0) {
|
||||
completeFlakeInputAttrPath(completions, getEvalState(), getFlakeRefsForCompletion(), prefix);
|
||||
completeFlakeInputPath(completions, getEvalState(), getFlakeRefsForCompletion(), prefix);
|
||||
} else if (n == 1) {
|
||||
completeFlakeRef(completions, getEvalState()->store, prefix);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ Args::Flag hashAlgo(std::string && longName, HashAlgorithm * ha)
|
||||
{
|
||||
return Args::Flag {
|
||||
.longName = std::move(longName),
|
||||
.description = "Hash algorithm (`blake3`, `md5`, `sha1`, `sha256`, or `sha512`).",
|
||||
.description = "Hash algorithm (`md5`, `sha1`, `sha256`, or `sha512`).",
|
||||
.labels = {"hash-algo"},
|
||||
.handler = {[ha](std::string s) {
|
||||
*ha = parseHashAlgo(s);
|
||||
@@ -63,7 +63,7 @@ Args::Flag hashAlgoOpt(std::string && longName, std::optional<HashAlgorithm> * o
|
||||
{
|
||||
return Args::Flag {
|
||||
.longName = std::move(longName),
|
||||
.description = "Hash algorithm (`blake3`, `md5`, `sha1`, `sha256`, or `sha512`). Can be omitted for SRI hashes.",
|
||||
.description = "Hash algorithm (`md5`, `sha1`, `sha256`, or `sha512`). Can be omitted for SRI hashes.",
|
||||
.labels = {"hash-algo"},
|
||||
.handler = {[oha](std::string s) {
|
||||
*oha = std::optional<HashAlgorithm>{parseHashAlgo(s)};
|
||||
@@ -120,7 +120,7 @@ Args::Flag contentAddressMethod(ContentAddressMethod * method)
|
||||
|
||||
- [`text`](@docroot@/store/store-object/content-address.md#method-text):
|
||||
Like `flat`, but used for
|
||||
[derivations](@docroot@/glossary.md#gloss-store-derivation) serialized in store object and
|
||||
[derivations](@docroot@/glossary.md#store-derivation) serialized in store object and
|
||||
[`builtins.toFile`](@docroot@/language/builtins.html#builtins-toFile).
|
||||
For advanced use-cases only;
|
||||
for regular usage prefer `nar` and `flat`.
|
||||
|
||||
@@ -64,6 +64,14 @@ mkMesonLibrary (finalAttrs: {
|
||||
nlohmann_json
|
||||
];
|
||||
|
||||
preConfigure =
|
||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
||||
# Do the meson utils, without modification.
|
||||
''
|
||||
chmod u+w ./.version
|
||||
echo ${version} > ../../.version
|
||||
'';
|
||||
|
||||
mesonFlags = [
|
||||
(lib.mesonEnable "markdown" enableMarkdown)
|
||||
(lib.mesonOption "readline-flavor" readlineFlavor)
|
||||
|
||||
@@ -101,9 +101,6 @@ struct NixRepl
|
||||
Value & v,
|
||||
unsigned int maxDepth = std::numeric_limits<unsigned int>::max())
|
||||
{
|
||||
// Hide the progress bar during printing because it might interfere
|
||||
logger->pause();
|
||||
Finally resumeLoggerDefer([]() { logger->resume(); });
|
||||
::nix::printValue(*state, str, v, PrintOptions {
|
||||
.ansiColors = true,
|
||||
.force = true,
|
||||
|
||||
@@ -36,6 +36,14 @@ mkMesonLibrary (finalAttrs: {
|
||||
nix-expr
|
||||
];
|
||||
|
||||
preConfigure =
|
||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
||||
# Do the meson utils, without modification.
|
||||
''
|
||||
chmod u+w ./.version
|
||||
echo ${version} > ../../.version
|
||||
'';
|
||||
|
||||
mesonFlags = [
|
||||
];
|
||||
|
||||
|
||||
@@ -40,6 +40,14 @@ mkMesonLibrary (finalAttrs: {
|
||||
rapidcheck
|
||||
];
|
||||
|
||||
preConfigure =
|
||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
||||
# Do the meson utils, without modification.
|
||||
''
|
||||
chmod u+w ./.version
|
||||
echo ${version} > ../../.version
|
||||
'';
|
||||
|
||||
mesonFlags = [
|
||||
];
|
||||
|
||||
|
||||
@@ -1152,7 +1152,7 @@ namespace nix {
|
||||
|
||||
ASSERT_TRACE1("hashString \"foo\" \"content\"",
|
||||
UsageError,
|
||||
HintFmt("unknown hash algorithm '%s', expect 'blake3', 'md5', 'sha1', 'sha256', or 'sha512'", "foo"));
|
||||
HintFmt("unknown hash algorithm '%s', expect 'md5', 'sha1', 'sha256', or 'sha512'", "foo"));
|
||||
|
||||
ASSERT_TRACE2("hashString \"sha256\" {}",
|
||||
TypeError,
|
||||
|
||||
@@ -172,7 +172,7 @@ TEST_F(nix_api_expr_test, nix_expr_realise_context_bad_build)
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_expr_realise_context)
|
||||
{
|
||||
// TODO (ca-derivations): add a content-addressing derivation output, which produces a placeholder
|
||||
// TODO (ca-derivations): add a content-addressed derivation output, which produces a placeholder
|
||||
auto expr = R"(
|
||||
''
|
||||
a derivation output: ${
|
||||
|
||||
@@ -46,6 +46,14 @@ mkMesonExecutable (finalAttrs: {
|
||||
gtest
|
||||
];
|
||||
|
||||
preConfigure =
|
||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
||||
# Do the meson utils, without modification.
|
||||
''
|
||||
chmod u+w ./.version
|
||||
echo ${version} > ../../.version
|
||||
'';
|
||||
|
||||
mesonFlags = [
|
||||
];
|
||||
|
||||
|
||||
@@ -28,15 +28,20 @@ namespace nix {
|
||||
};
|
||||
|
||||
class CaptureLogging {
|
||||
std::unique_ptr<Logger> oldLogger;
|
||||
Logger * oldLogger;
|
||||
std::unique_ptr<CaptureLogger> tempLogger;
|
||||
public:
|
||||
CaptureLogging() {
|
||||
oldLogger = std::move(logger);
|
||||
logger = std::make_unique<CaptureLogger>();
|
||||
CaptureLogging() : tempLogger(std::make_unique<CaptureLogger>()) {
|
||||
oldLogger = logger;
|
||||
logger = tempLogger.get();
|
||||
}
|
||||
|
||||
~CaptureLogging() {
|
||||
logger = std::move(oldLogger);
|
||||
logger = oldLogger;
|
||||
}
|
||||
|
||||
std::string get() const {
|
||||
return tempLogger->get();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -108,7 +113,7 @@ namespace nix {
|
||||
CaptureLogging l;
|
||||
auto v = eval("builtins.trace \"test string 123\" 123");
|
||||
ASSERT_THAT(v, IsIntEq(123));
|
||||
auto text = (dynamic_cast<CaptureLogger *>(logger.get()))->get();
|
||||
auto text = l.get();
|
||||
ASSERT_NE(text.find("test string 123"), std::string::npos);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ let
|
||||
resolveInput =
|
||||
inputSpec: if builtins.isList inputSpec then getInputByPath lockFile.root inputSpec else inputSpec;
|
||||
|
||||
# Follow an input attrpath (e.g. ["dwarffs" "nixpkgs"]) from the
|
||||
# Follow an input path (e.g. ["dwarffs" "nixpkgs"]) from the
|
||||
# root node, returning the final node.
|
||||
getInputByPath =
|
||||
nodeName: path:
|
||||
|
||||
@@ -57,7 +57,7 @@ Strings EvalSettings::getDefaultNixPath()
|
||||
{
|
||||
Strings res;
|
||||
auto add = [&](const Path & p, const std::string & s = std::string()) {
|
||||
if (std::filesystem::exists(p)) {
|
||||
if (pathAccessible(p)) {
|
||||
if (s.empty()) {
|
||||
res.push_back(p);
|
||||
} else {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
///@file
|
||||
|
||||
#include "config.hh"
|
||||
#include "ref.hh"
|
||||
#include "source-path.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -246,42 +246,15 @@ EvalState::EvalState(
|
||||
, repair(NoRepair)
|
||||
, emptyBindings(0)
|
||||
, rootFS(
|
||||
({
|
||||
/* In pure eval mode, we provide a filesystem that only
|
||||
contains the Nix store.
|
||||
|
||||
If we have a chroot store and pure eval is not enabled,
|
||||
use a union accessor to make the chroot store available
|
||||
at its logical location while still having the
|
||||
underlying directory available. This is necessary for
|
||||
instance if we're evaluating a file from the physical
|
||||
/nix/store while using a chroot store. */
|
||||
auto accessor = getFSSourceAccessor();
|
||||
|
||||
auto realStoreDir = dirOf(store->toRealPath(StorePath::dummy));
|
||||
if (settings.pureEval || store->storeDir != realStoreDir) {
|
||||
auto storeFS = makeMountedSourceAccessor(
|
||||
{
|
||||
{CanonPath::root, makeEmptySourceAccessor()},
|
||||
{CanonPath(store->storeDir), makeFSSourceAccessor(realStoreDir)}
|
||||
});
|
||||
accessor = settings.pureEval
|
||||
? storeFS
|
||||
: makeUnionSourceAccessor({accessor, storeFS});
|
||||
}
|
||||
|
||||
/* Apply access control if needed. */
|
||||
if (settings.restrictEval || settings.pureEval)
|
||||
accessor = AllowListSourceAccessor::create(accessor, {},
|
||||
[&settings](const CanonPath & path) -> RestrictedPathError {
|
||||
auto modeInformation = settings.pureEval
|
||||
? "in pure evaluation mode (use '--impure' to override)"
|
||||
: "in restricted mode";
|
||||
throw RestrictedPathError("access to absolute path '%1%' is forbidden %2%", path, modeInformation);
|
||||
});
|
||||
|
||||
accessor;
|
||||
}))
|
||||
settings.restrictEval || settings.pureEval
|
||||
? ref<SourceAccessor>(AllowListSourceAccessor::create(getFSSourceAccessor(), {},
|
||||
[&settings](const CanonPath & path) -> RestrictedPathError {
|
||||
auto modeInformation = settings.pureEval
|
||||
? "in pure evaluation mode (use '--impure' to override)"
|
||||
: "in restricted mode";
|
||||
throw RestrictedPathError("access to absolute path '%1%' is forbidden %2%", path, modeInformation);
|
||||
}))
|
||||
: getFSSourceAccessor())
|
||||
, corepkgsFS(make_ref<MemorySourceAccessor>())
|
||||
, internalFS(make_ref<MemorySourceAccessor>())
|
||||
, derivationInternal{corepkgsFS->addFile(
|
||||
@@ -371,7 +344,7 @@ void EvalState::allowPath(const Path & path)
|
||||
void EvalState::allowPath(const StorePath & storePath)
|
||||
{
|
||||
if (auto rootFS2 = rootFS.dynamic_pointer_cast<AllowListSourceAccessor>())
|
||||
rootFS2->allowPrefix(CanonPath(store->printStorePath(storePath)));
|
||||
rootFS2->allowPrefix(CanonPath(store->toRealPath(storePath)));
|
||||
}
|
||||
|
||||
void EvalState::allowClosure(const StorePath & storePath)
|
||||
@@ -449,6 +422,16 @@ void EvalState::checkURI(const std::string & uri)
|
||||
}
|
||||
|
||||
|
||||
Path EvalState::toRealPath(const Path & path, const NixStringContext & context)
|
||||
{
|
||||
// FIXME: check whether 'path' is in 'context'.
|
||||
return
|
||||
!context.empty() && store->isInStore(path)
|
||||
? store->toRealPath(path)
|
||||
: path;
|
||||
}
|
||||
|
||||
|
||||
Value * EvalState::addConstant(const std::string & name, Value & v, Constant info)
|
||||
{
|
||||
Value * v2 = allocValue();
|
||||
@@ -2068,7 +2051,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
||||
else if (firstType == nPath) {
|
||||
if (!context.empty())
|
||||
state.error<EvalError>("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow();
|
||||
v.mkPath(state.rootPath(CanonPath(str())));
|
||||
v.mkPath(state.rootPath(CanonPath(canonPath(str()))));
|
||||
} else
|
||||
v.mkStringMove(c_str(), context);
|
||||
}
|
||||
@@ -2449,7 +2432,7 @@ SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext
|
||||
auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
|
||||
if (path == "" || path[0] != '/')
|
||||
error<EvalError>("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow();
|
||||
return rootPath(path);
|
||||
return rootPath(CanonPath(path));
|
||||
}
|
||||
|
||||
|
||||
@@ -3087,11 +3070,8 @@ std::optional<SourcePath> EvalState::resolveLookupPathPath(const LookupPath::Pat
|
||||
auto i = lookupPathResolved.find(value);
|
||||
if (i != lookupPathResolved.end()) return i->second;
|
||||
|
||||
auto finish = [&](std::optional<SourcePath> res) {
|
||||
if (res)
|
||||
debug("resolved search path element '%s' to '%s'", value, *res);
|
||||
else
|
||||
debug("failed to resolve search path element '%s'", value);
|
||||
auto finish = [&](SourcePath res) {
|
||||
debug("resolved search path element '%s' to '%s'", value, res);
|
||||
lookupPathResolved.emplace(value, res);
|
||||
return res;
|
||||
};
|
||||
@@ -3103,7 +3083,7 @@ std::optional<SourcePath> EvalState::resolveLookupPathPath(const LookupPath::Pat
|
||||
fetchSettings,
|
||||
EvalSettings::resolvePseudoUrl(value));
|
||||
auto storePath = fetchToStore(*store, SourcePath(accessor), FetchMode::Copy);
|
||||
return finish(this->storePath(storePath));
|
||||
return finish(rootPath(store->toRealPath(storePath)));
|
||||
} catch (Error & e) {
|
||||
logWarning({
|
||||
.msg = HintFmt("Nix search path entry '%1%' cannot be downloaded, ignoring", value)
|
||||
@@ -3143,7 +3123,8 @@ std::optional<SourcePath> EvalState::resolveLookupPathPath(const LookupPath::Pat
|
||||
}
|
||||
}
|
||||
|
||||
return finish(std::nullopt);
|
||||
debug("failed to resolve search path element '%s'", value);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -389,15 +389,6 @@ public:
|
||||
*/
|
||||
SourcePath rootPath(PathView path);
|
||||
|
||||
/**
|
||||
* Return a `SourcePath` that refers to `path` in the store.
|
||||
*
|
||||
* For now, this has to also be within the root filesystem for
|
||||
* backwards compat, but for Windows and maybe also pure eval, we'll
|
||||
* probably want to do something different.
|
||||
*/
|
||||
SourcePath storePath(const StorePath & path);
|
||||
|
||||
/**
|
||||
* Allow access to a path.
|
||||
*/
|
||||
@@ -421,6 +412,17 @@ public:
|
||||
|
||||
void checkURI(const std::string & uri);
|
||||
|
||||
/**
|
||||
* When using a diverted store and 'path' is in the Nix store, map
|
||||
* 'path' to the diverted location (e.g. /nix/store/foo is mapped
|
||||
* to /home/alice/my-nix/nix/store/foo). However, this is only
|
||||
* done if the context is not empty, since otherwise we're
|
||||
* probably trying to read from the actual /nix/store. This is
|
||||
* intended to distinguish between import-from-derivation and
|
||||
* sources stored in the actual /nix/store.
|
||||
*/
|
||||
Path toRealPath(const Path & path, const NixStringContext & context);
|
||||
|
||||
/**
|
||||
* Parse a Nix expression from the specified file.
|
||||
*/
|
||||
|
||||
@@ -24,7 +24,6 @@ deps_public_maybe_subproject = [
|
||||
dependency('nix-fetchers'),
|
||||
]
|
||||
subdir('nix-meson-build-support/subprojects')
|
||||
subdir('nix-meson-build-support/big-objs')
|
||||
|
||||
boost = dependency(
|
||||
'boost',
|
||||
|
||||
@@ -77,6 +77,14 @@ mkMesonLibrary (finalAttrs: {
|
||||
nlohmann_json
|
||||
] ++ lib.optional enableGC boehmgc;
|
||||
|
||||
preConfigure =
|
||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
||||
# Do the meson utils, without modification.
|
||||
''
|
||||
chmod u+w ./.version
|
||||
echo ${version} > ../../.version
|
||||
'';
|
||||
|
||||
mesonFlags = [
|
||||
(lib.mesonEnable "gc" enableGC)
|
||||
];
|
||||
|
||||
@@ -359,18 +359,11 @@ string_parts_interpolated
|
||||
|
||||
path_start
|
||||
: PATH {
|
||||
std::string_view literal({$1.p, $1.l});
|
||||
Path path(absPath(literal, state->basePath.path.abs()));
|
||||
Path path(absPath(std::string_view{$1.p, $1.l}, state->basePath.path.abs()));
|
||||
/* add back in the trailing '/' to the first segment */
|
||||
if (literal.size() > 1 && literal.back() == '/')
|
||||
path += '/';
|
||||
$$ =
|
||||
/* Absolute paths are always interpreted relative to the
|
||||
root filesystem accessor, rather than the accessor of the
|
||||
current Nix expression. */
|
||||
literal.front() == '/'
|
||||
? new ExprPath(state->rootFS, std::move(path))
|
||||
: new ExprPath(state->basePath.accessor, std::move(path));
|
||||
if ($1.p[$1.l-1] == '/' && $1.l > 1)
|
||||
path += "/";
|
||||
$$ = new ExprPath(ref<SourceAccessor>(state->rootFS), std::move(path));
|
||||
}
|
||||
| HPATH {
|
||||
if (state->settings.pureEval) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include "store-api.hh"
|
||||
#include "eval.hh"
|
||||
|
||||
namespace nix {
|
||||
@@ -13,9 +12,4 @@ SourcePath EvalState::rootPath(PathView path)
|
||||
return {rootFS, CanonPath(absPath(path))};
|
||||
}
|
||||
|
||||
SourcePath EvalState::storePath(const StorePath & path)
|
||||
{
|
||||
return {rootFS, CanonPath{store->printStorePath(path)}};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -145,7 +145,8 @@ static SourcePath realisePath(EvalState & state, const PosIdx pos, Value & v, st
|
||||
try {
|
||||
if (!context.empty() && path.accessor == state.rootFS) {
|
||||
auto rewrites = state.realiseContext(context);
|
||||
path = {path.accessor, CanonPath(rewriteStrings(path.path.abs(), rewrites))};
|
||||
auto realPath = state.toRealPath(rewriteStrings(path.path.abs(), rewrites), context);
|
||||
path = {path.accessor, CanonPath(realPath)};
|
||||
}
|
||||
return resolveSymlinks ? path.resolveSymlinks(*resolveSymlinks) : path;
|
||||
} catch (Error & e) {
|
||||
@@ -1594,13 +1595,9 @@ static RegisterPrimOp primop_placeholder({
|
||||
.name = "placeholder",
|
||||
.args = {"output"},
|
||||
.doc = R"(
|
||||
Return at
|
||||
[output placeholder string](@docroot@/store/drv.md#output-placeholder)
|
||||
for the specified *output* that will be substituted by the corresponding
|
||||
[output path](@docroot@/glossary.md#gloss-output-path)
|
||||
at build time.
|
||||
|
||||
Typical outputs would be `"out"`, `"bin"` or `"dev"`.
|
||||
Return a placeholder string for the specified *output* that will be
|
||||
substituted by the corresponding output path at build time. Typical
|
||||
outputs would be `"out"`, `"bin"` or `"dev"`.
|
||||
)",
|
||||
.fun = prim_placeholder,
|
||||
});
|
||||
@@ -2138,15 +2135,12 @@ static RegisterPrimOp primop_outputOf({
|
||||
.name = "__outputOf",
|
||||
.args = {"derivation-reference", "output-name"},
|
||||
.doc = R"(
|
||||
Return the output path of a derivation, literally or using an
|
||||
[input placeholder string](@docroot@/store/drv.md#input-placeholder)
|
||||
if needed.
|
||||
Return the output path of a derivation, literally or using a placeholder if needed.
|
||||
|
||||
If the derivation has a statically-known output path (i.e. the derivation output is input-addressed, or fixed content-addresed), the output path will just be returned.
|
||||
But if the derivation is content-addressed or if the derivation is itself not-statically produced (i.e. is the output of another derivation), an input placeholder will be returned instead.
|
||||
But if the derivation is content-addressed or if the derivation is itself not-statically produced (i.e. is the output of another derivation), a placeholder will be returned instead.
|
||||
|
||||
*`derivation reference`* must be a string that may contain a regular store path to a derivation, or may be an input placeholder reference.
|
||||
If the derivation is produced by a derivation, you must explicitly select `drv.outPath`.
|
||||
*`derivation reference`* must be a string that may contain a regular store path to a derivation, or may be a placeholder reference. If the derivation is produced by a derivation, you must explicitly select `drv.outPath`.
|
||||
This primop can be chained arbitrarily deeply.
|
||||
For instance,
|
||||
|
||||
@@ -2156,9 +2150,9 @@ static RegisterPrimOp primop_outputOf({
|
||||
"out"
|
||||
```
|
||||
|
||||
will return a input placeholder for the output of the output of `myDrv`.
|
||||
will return a placeholder for the output of the output of `myDrv`.
|
||||
|
||||
This primop corresponds to the `^` sigil for [deriving paths](@docroot@/glossary.md#gloss-deriving-paths), e.g. as part of installable syntax on the command line.
|
||||
This primop corresponds to the `^` sigil for derivable paths, e.g. as part of installable syntax on the command line.
|
||||
)",
|
||||
.fun = prim_outputOf,
|
||||
.experimentalFeature = Xp::DynamicDerivations,
|
||||
@@ -2478,11 +2472,21 @@ static void addPath(
|
||||
const NixStringContext & context)
|
||||
{
|
||||
try {
|
||||
StorePathSet refs;
|
||||
|
||||
if (path.accessor == state.rootFS && state.store->isInStore(path.path.abs())) {
|
||||
// FIXME: handle CA derivation outputs (where path needs to
|
||||
// be rewritten to the actual output).
|
||||
auto rewrites = state.realiseContext(context);
|
||||
path = {path.accessor, CanonPath(rewriteStrings(path.path.abs(), rewrites))};
|
||||
path = {state.rootFS, CanonPath(state.toRealPath(rewriteStrings(path.path.abs(), rewrites), context))};
|
||||
|
||||
try {
|
||||
auto [storePath, subPath] = state.store->toStorePath(path.path.abs());
|
||||
// FIXME: we should scanForReferences on the path before adding it
|
||||
refs = state.store->queryPathInfo(storePath)->references;
|
||||
path = {state.rootFS, CanonPath(state.store->toRealPath(storePath) + subPath)};
|
||||
} catch (Error &) { // FIXME: should be InvalidPathError
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<PathFilter> filter;
|
||||
|
||||
@@ -90,26 +90,24 @@ static void fetchTree(
|
||||
fetchers::Input input { state.fetchSettings };
|
||||
NixStringContext context;
|
||||
std::optional<std::string> type;
|
||||
auto fetcher = params.isFetchGit ? "fetchGit" : "fetchTree";
|
||||
if (params.isFetchGit) type = "git";
|
||||
|
||||
state.forceValue(*args[0], pos);
|
||||
|
||||
if (args[0]->type() == nAttrs) {
|
||||
state.forceAttrs(*args[0], pos, fmt("while evaluating the argument passed to '%s'", fetcher));
|
||||
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.fetchTree");
|
||||
|
||||
fetchers::Attrs attrs;
|
||||
|
||||
if (auto aType = args[0]->attrs()->get(state.sType)) {
|
||||
if (type)
|
||||
state.error<EvalError>(
|
||||
"unexpected argument 'type'"
|
||||
"unexpected attribute 'type'"
|
||||
).atPos(pos).debugThrow();
|
||||
type = state.forceStringNoCtx(*aType->value, aType->pos,
|
||||
fmt("while evaluating the `type` argument passed to '%s'", fetcher));
|
||||
type = state.forceStringNoCtx(*aType->value, aType->pos, "while evaluating the `type` attribute passed to builtins.fetchTree");
|
||||
} else if (!type)
|
||||
state.error<EvalError>(
|
||||
"argument 'type' is missing in call to '%s'", fetcher
|
||||
"attribute 'type' is missing in call to 'fetchTree'"
|
||||
).atPos(pos).debugThrow();
|
||||
|
||||
attrs.emplace("type", type.value());
|
||||
@@ -129,8 +127,9 @@ static void fetchTree(
|
||||
else if (attr.value->type() == nInt) {
|
||||
auto intValue = attr.value->integer().value;
|
||||
|
||||
if (intValue < 0)
|
||||
state.error<EvalError>("negative value given for '%s' argument '%s': %d", fetcher, state.symbols[attr.name], intValue).atPos(pos).debugThrow();
|
||||
if (intValue < 0) {
|
||||
state.error<EvalError>("negative value given for fetchTree attr %1%: %2%", state.symbols[attr.name], intValue).atPos(pos).debugThrow();
|
||||
}
|
||||
|
||||
attrs.emplace(state.symbols[attr.name], uint64_t(intValue));
|
||||
} else if (state.symbols[attr.name] == "publicKeys") {
|
||||
@@ -138,8 +137,8 @@ static void fetchTree(
|
||||
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, context).dump());
|
||||
}
|
||||
else
|
||||
state.error<TypeError>("argument '%s' to '%s' is %s while a string, Boolean or integer is expected",
|
||||
state.symbols[attr.name], fetcher, showType(*attr.value)).debugThrow();
|
||||
state.error<TypeError>("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
|
||||
state.symbols[attr.name], showType(*attr.value)).debugThrow();
|
||||
}
|
||||
|
||||
if (params.isFetchGit && !attrs.contains("exportIgnore") && (!attrs.contains("submodules") || !*fetchers::maybeGetBoolAttr(attrs, "submodules"))) {
|
||||
@@ -154,14 +153,14 @@ static void fetchTree(
|
||||
if (!params.allowNameArgument)
|
||||
if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
|
||||
state.error<EvalError>(
|
||||
"argument 'name' isn’t supported in call to '%s'", fetcher
|
||||
"attribute 'name' isn’t supported in call to 'fetchTree'"
|
||||
).atPos(pos).debugThrow();
|
||||
|
||||
input = fetchers::Input::fromAttrs(state.fetchSettings, std::move(attrs));
|
||||
} else {
|
||||
auto url = state.coerceToString(pos, *args[0], context,
|
||||
fmt("while evaluating the first argument passed to '%s'", fetcher),
|
||||
false, false).toOwned();
|
||||
"while evaluating the first argument passed to the fetcher",
|
||||
false, false).toOwned();
|
||||
|
||||
if (params.isFetchGit) {
|
||||
fetchers::Attrs attrs;
|
||||
@@ -174,7 +173,7 @@ static void fetchTree(
|
||||
} else {
|
||||
if (!experimentalFeatureSettings.isEnabled(Xp::Flakes))
|
||||
state.error<EvalError>(
|
||||
"passing a string argument to '%s' requires the 'flakes' experimental feature", fetcher
|
||||
"passing a string argument to 'fetchTree' requires the 'flakes' experimental feature"
|
||||
).atPos(pos).debugThrow();
|
||||
input = fetchers::Input::fromURL(state.fetchSettings, url);
|
||||
}
|
||||
@@ -183,16 +182,15 @@ static void fetchTree(
|
||||
if (!state.settings.pureEval && !input.isDirect() && experimentalFeatureSettings.isEnabled(Xp::Flakes))
|
||||
input = lookupInRegistries(state.store, input).first;
|
||||
|
||||
if (state.settings.pureEval && !input.isLocked()) {
|
||||
if (input.getNarHash())
|
||||
warn(
|
||||
"Input '%s' is unlocked (e.g. lacks a Git revision) but does have a NAR hash. "
|
||||
"This is deprecated since such inputs are verifiable but may not be reproducible.",
|
||||
input.to_string());
|
||||
else
|
||||
state.error<EvalError>(
|
||||
"in pure evaluation mode, '%s' will not fetch unlocked input '%s'",
|
||||
fetcher, input.to_string()).atPos(pos).debugThrow();
|
||||
if (state.settings.pureEval && !input.isConsideredLocked(state.fetchSettings)) {
|
||||
auto fetcher = "fetchTree";
|
||||
if (params.isFetchGit)
|
||||
fetcher = "fetchGit";
|
||||
|
||||
state.error<EvalError>(
|
||||
"in pure evaluation mode, '%s' will not fetch unlocked input '%s'",
|
||||
fetcher, input.to_string()
|
||||
).atPos(pos).debugThrow();
|
||||
}
|
||||
|
||||
state.checkURI(input.toURLString());
|
||||
@@ -367,12 +365,6 @@ static RegisterPrimOp primop_fetchTree({
|
||||
|
||||
Default: `false`
|
||||
|
||||
- `lfs` (Bool, optional)
|
||||
|
||||
Fetch any [Git LFS](https://git-lfs.com/) files.
|
||||
|
||||
Default: `false`
|
||||
|
||||
- `allRefs` (Bool, optional)
|
||||
|
||||
By default, this has no effect. This becomes relevant only once `shallow` cloning is disabled.
|
||||
@@ -697,13 +689,6 @@ static RegisterPrimOp primop_fetchGit({
|
||||
|
||||
Make a shallow clone when fetching the Git tree.
|
||||
When this is enabled, the options `ref` and `allRefs` have no effect anymore.
|
||||
|
||||
- `lfs` (default: `false`)
|
||||
|
||||
A boolean that when `true` specifies that [Git LFS] files should be fetched.
|
||||
|
||||
[Git LFS]: https://git-lfs.com/
|
||||
|
||||
- `allRefs`
|
||||
|
||||
Whether to fetch all references (eg. branches and tags) of the repository.
|
||||
|
||||
@@ -7,18 +7,13 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "fs-sink.hh"
|
||||
#include "serialise.hh"
|
||||
#include "git-lfs-fetch.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
namespace fs {
|
||||
using namespace std::filesystem;
|
||||
}
|
||||
|
||||
class GitUtilsTest : public ::testing::Test
|
||||
{
|
||||
// We use a single repository for all tests.
|
||||
fs::path tmpDir;
|
||||
Path tmpDir;
|
||||
std::unique_ptr<AutoDelete> delTmpDir;
|
||||
|
||||
public:
|
||||
@@ -46,11 +41,6 @@ public:
|
||||
{
|
||||
return GitRepo::openRepo(tmpDir, true, false);
|
||||
}
|
||||
|
||||
std::string getRepoName() const
|
||||
{
|
||||
return tmpDir.filename();
|
||||
}
|
||||
};
|
||||
|
||||
void writeString(CreateRegularFileSink & fileSink, std::string contents, bool executable)
|
||||
@@ -88,7 +78,7 @@ TEST_F(GitUtilsTest, sink_basic)
|
||||
// sink->createHardlink("foo-1.1/links/foo-2", CanonPath("foo-1.1/hello"));
|
||||
|
||||
auto result = repo->dereferenceSingletonDirectory(sink->flush());
|
||||
auto accessor = repo->getAccessor(result, false, getRepoName());
|
||||
auto accessor = repo->getAccessor(result, false);
|
||||
auto entries = accessor->readDirectory(CanonPath::root);
|
||||
ASSERT_EQ(entries.size(), 5);
|
||||
ASSERT_EQ(accessor->readFile(CanonPath("hello")), "hello world");
|
||||
|
||||
@@ -31,9 +31,6 @@ deps_private += rapidcheck
|
||||
gtest = dependency('gtest', main : true)
|
||||
deps_private += gtest
|
||||
|
||||
libgit2 = dependency('libgit2')
|
||||
deps_private += libgit2
|
||||
|
||||
add_project_arguments(
|
||||
# TODO(Qyriad): Yes this is how the autoconf+Make system did it.
|
||||
# It would be nice for our headers to be idempotent instead.
|
||||
@@ -47,7 +44,6 @@ subdir('nix-meson-build-support/common')
|
||||
|
||||
sources = files(
|
||||
'public-key.cc',
|
||||
'git-utils.cc'
|
||||
)
|
||||
|
||||
include_dirs = [include_directories('.')]
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
nix-fetchers,
|
||||
nix-store-test-support,
|
||||
|
||||
libgit2,
|
||||
rapidcheck,
|
||||
gtest,
|
||||
runCommand,
|
||||
@@ -43,9 +42,16 @@ mkMesonExecutable (finalAttrs: {
|
||||
nix-store-test-support
|
||||
rapidcheck
|
||||
gtest
|
||||
libgit2
|
||||
];
|
||||
|
||||
preConfigure =
|
||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
||||
# Do the meson utils, without modification.
|
||||
''
|
||||
chmod u+w ./.version
|
||||
echo ${version} > ../../.version
|
||||
'';
|
||||
|
||||
mesonFlags = [
|
||||
];
|
||||
|
||||
|
||||
@@ -155,6 +155,12 @@ bool Input::isLocked() const
|
||||
return scheme && scheme->isLocked(*this);
|
||||
}
|
||||
|
||||
bool Input::isConsideredLocked(
|
||||
const Settings & settings) const
|
||||
{
|
||||
return isLocked() || (settings.allowDirtyLocks && getNarHash());
|
||||
}
|
||||
|
||||
bool Input::isFinal() const
|
||||
{
|
||||
return maybeGetBoolAttr(attrs, "__final").value_or(false);
|
||||
@@ -186,7 +192,6 @@ bool Input::contains(const Input & other) const
|
||||
return false;
|
||||
}
|
||||
|
||||
// FIXME: remove
|
||||
std::pair<StorePath, Input> Input::fetchToStore(ref<Store> store) const
|
||||
{
|
||||
if (!scheme)
|
||||
@@ -201,6 +206,10 @@ std::pair<StorePath, Input> Input::fetchToStore(ref<Store> store) const
|
||||
auto narHash = store->queryPathInfo(storePath)->narHash;
|
||||
result.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
|
||||
|
||||
// FIXME: we would like to mark inputs as final in
|
||||
// getAccessorUnchecked(), but then we can't add
|
||||
// narHash. Or maybe narHash should be excluded from the
|
||||
// concept of "final" inputs?
|
||||
result.attrs.insert_or_assign("__final", Explicit<bool>(true));
|
||||
|
||||
assert(result.isFinal());
|
||||
@@ -281,8 +290,6 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessor(ref<Store> store) const
|
||||
try {
|
||||
auto [accessor, result] = getAccessorUnchecked(store);
|
||||
|
||||
result.attrs.insert_or_assign("__final", Explicit<bool>(true));
|
||||
|
||||
checkLocks(*this, result);
|
||||
|
||||
return {accessor, std::move(result)};
|
||||
|
||||
@@ -90,6 +90,15 @@ public:
|
||||
*/
|
||||
bool isLocked() const;
|
||||
|
||||
/**
|
||||
* Return whether the input is either locked, or, if
|
||||
* `allow-dirty-locks` is enabled, it has a NAR hash. In the
|
||||
* latter case, we can verify the input but we may not be able to
|
||||
* fetch it from anywhere.
|
||||
*/
|
||||
bool isConsideredLocked(
|
||||
const Settings & settings) const;
|
||||
|
||||
/**
|
||||
* Only for relative path flakes, i.e. 'path:./foo', returns the
|
||||
* relative path, i.e. './foo'.
|
||||
|
||||
@@ -1,279 +0,0 @@
|
||||
#include "git-lfs-fetch.hh"
|
||||
#include "git-utils.hh"
|
||||
#include "filetransfer.hh"
|
||||
#include "processes.hh"
|
||||
#include "url.hh"
|
||||
#include "users.hh"
|
||||
#include "hash.hh"
|
||||
|
||||
#include <git2/attr.h>
|
||||
#include <git2/config.h>
|
||||
#include <git2/errors.h>
|
||||
#include <git2/remote.h>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace nix::lfs {
|
||||
|
||||
// if authHeader is "", downloadToSink assumes no auth is expected
|
||||
static void downloadToSink(
|
||||
const std::string & url,
|
||||
const std::string & authHeader,
|
||||
// FIXME: passing a StringSink is superfluous, we may as well
|
||||
// return a string. Or use an abstract Sink for streaming.
|
||||
StringSink & sink,
|
||||
std::string sha256Expected,
|
||||
size_t sizeExpected)
|
||||
{
|
||||
FileTransferRequest request(url);
|
||||
Headers headers;
|
||||
if (!authHeader.empty())
|
||||
headers.push_back({"Authorization", authHeader});
|
||||
request.headers = headers;
|
||||
getFileTransfer()->download(std::move(request), sink);
|
||||
|
||||
auto sizeActual = sink.s.length();
|
||||
if (sizeExpected != sizeActual)
|
||||
throw Error("size mismatch while fetching %s: expected %d but got %d", url, sizeExpected, sizeActual);
|
||||
|
||||
auto sha256Actual = hashString(HashAlgorithm::SHA256, sink.s).to_string(HashFormat::Base16, false);
|
||||
if (sha256Actual != sha256Expected)
|
||||
throw Error(
|
||||
"hash mismatch while fetching %s: expected sha256:%s but got sha256:%s", url, sha256Expected, sha256Actual);
|
||||
}
|
||||
|
||||
static std::string getLfsApiToken(const ParsedURL & url)
|
||||
{
|
||||
auto [status, output] = runProgram(RunOptions{
|
||||
.program = "ssh",
|
||||
.args = {*url.authority, "git-lfs-authenticate", url.path, "download"},
|
||||
});
|
||||
|
||||
if (output.empty())
|
||||
throw Error(
|
||||
"git-lfs-authenticate: no output (cmd: ssh %s git-lfs-authenticate %s download)",
|
||||
url.authority.value_or(""),
|
||||
url.path);
|
||||
|
||||
auto queryResp = nlohmann::json::parse(output);
|
||||
if (!queryResp.contains("header"))
|
||||
throw Error("no header in git-lfs-authenticate response");
|
||||
if (!queryResp["header"].contains("Authorization"))
|
||||
throw Error("no Authorization in git-lfs-authenticate response");
|
||||
|
||||
return queryResp["header"]["Authorization"].get<std::string>();
|
||||
}
|
||||
|
||||
typedef std::unique_ptr<git_config, Deleter<git_config_free>> GitConfig;
|
||||
typedef std::unique_ptr<git_config_entry, Deleter<git_config_entry_free>> GitConfigEntry;
|
||||
|
||||
static std::string getLfsEndpointUrl(git_repository * repo)
|
||||
{
|
||||
GitConfig config;
|
||||
if (git_repository_config(Setter(config), repo)) {
|
||||
GitConfigEntry entry;
|
||||
if (!git_config_get_entry(Setter(entry), config.get(), "lfs.url")) {
|
||||
auto value = std::string(entry->value);
|
||||
if (!value.empty()) {
|
||||
debug("Found explicit lfs.url value: %s", value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
git_remote * remote = nullptr;
|
||||
if (git_remote_lookup(&remote, repo, "origin"))
|
||||
return "";
|
||||
|
||||
const char * url_c_str = git_remote_url(remote);
|
||||
if (!url_c_str)
|
||||
return "";
|
||||
|
||||
return std::string(url_c_str);
|
||||
}
|
||||
|
||||
static std::optional<Pointer> parseLfsPointer(std::string_view content, std::string_view filename)
|
||||
{
|
||||
// https://github.com/git-lfs/git-lfs/blob/2ef4108/docs/spec.md
|
||||
//
|
||||
// example git-lfs pointer file:
|
||||
// version https://git-lfs.github.com/spec/v1
|
||||
// oid sha256:f5e02aa71e67f41d79023a128ca35bad86cf7b6656967bfe0884b3a3c4325eaf
|
||||
// size 10000000
|
||||
// (ending \n)
|
||||
|
||||
if (!content.starts_with("version ")) {
|
||||
// Invalid pointer file
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!content.starts_with("version https://git-lfs.github.com/spec/v1")) {
|
||||
// In case there's new spec versions in the future, but for now only v1 exists
|
||||
debug("Invalid version found on potential lfs pointer file, skipping");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string oid;
|
||||
std::string size;
|
||||
|
||||
for (auto & line : tokenizeString<Strings>(content, "\n")) {
|
||||
if (line.starts_with("version ")) {
|
||||
continue;
|
||||
}
|
||||
if (line.starts_with("oid sha256:")) {
|
||||
oid = line.substr(11); // skip "oid sha256:"
|
||||
continue;
|
||||
}
|
||||
if (line.starts_with("size ")) {
|
||||
size = line.substr(5); // skip "size "
|
||||
continue;
|
||||
}
|
||||
|
||||
debug("Custom extension '%s' found, ignoring", line);
|
||||
}
|
||||
|
||||
if (oid.length() != 64 || !std::all_of(oid.begin(), oid.end(), ::isxdigit)) {
|
||||
debug("Invalid sha256 %s, skipping", oid);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (size.length() == 0 || !std::all_of(size.begin(), size.end(), ::isdigit)) {
|
||||
debug("Invalid size %s, skipping", size);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::make_optional(Pointer{oid, std::stoul(size)});
|
||||
}
|
||||
|
||||
Fetch::Fetch(git_repository * repo, git_oid rev)
|
||||
{
|
||||
this->repo = repo;
|
||||
this->rev = rev;
|
||||
|
||||
const auto remoteUrl = lfs::getLfsEndpointUrl(repo);
|
||||
|
||||
this->url = nix::parseURL(nix::fixGitURL(remoteUrl)).canonicalise();
|
||||
}
|
||||
|
||||
bool Fetch::shouldFetch(const CanonPath & path) const
|
||||
{
|
||||
const char * attr = nullptr;
|
||||
git_attr_options opts = GIT_ATTR_OPTIONS_INIT;
|
||||
opts.attr_commit_id = this->rev;
|
||||
opts.flags = GIT_ATTR_CHECK_INCLUDE_COMMIT | GIT_ATTR_CHECK_NO_SYSTEM;
|
||||
if (git_attr_get_ext(&attr, (git_repository *) (this->repo), &opts, path.rel_c_str(), "filter"))
|
||||
throw Error("cannot get git-lfs attribute: %s", git_error_last()->message);
|
||||
debug("Git filter for '%s' is '%s'", path, attr ? attr : "null");
|
||||
return attr != nullptr && !std::string(attr).compare("lfs");
|
||||
}
|
||||
|
||||
static nlohmann::json pointerToPayload(const std::vector<Pointer> & items)
|
||||
{
|
||||
nlohmann::json jArray = nlohmann::json::array();
|
||||
for (const auto & pointer : items)
|
||||
jArray.push_back({{"oid", pointer.oid}, {"size", pointer.size}});
|
||||
return jArray;
|
||||
}
|
||||
|
||||
std::vector<nlohmann::json> Fetch::fetchUrls(const std::vector<Pointer> & pointers) const
|
||||
{
|
||||
ParsedURL httpUrl(url);
|
||||
httpUrl.scheme = url.scheme == "ssh" ? "https" : url.scheme;
|
||||
FileTransferRequest request(httpUrl.to_string() + "/info/lfs/objects/batch");
|
||||
request.post = true;
|
||||
Headers headers;
|
||||
if (this->url.scheme == "ssh")
|
||||
headers.push_back({"Authorization", lfs::getLfsApiToken(this->url)});
|
||||
headers.push_back({"Content-Type", "application/vnd.git-lfs+json"});
|
||||
headers.push_back({"Accept", "application/vnd.git-lfs+json"});
|
||||
request.headers = headers;
|
||||
nlohmann::json oidList = pointerToPayload(pointers);
|
||||
nlohmann::json data = {{"operation", "download"}};
|
||||
data["objects"] = oidList;
|
||||
request.data = data.dump();
|
||||
|
||||
FileTransferResult result = getFileTransfer()->upload(request);
|
||||
auto responseString = result.data;
|
||||
|
||||
std::vector<nlohmann::json> objects;
|
||||
// example resp here:
|
||||
// {"objects":[{"oid":"f5e02aa71e67f41d79023a128ca35bad86cf7b6656967bfe0884b3a3c4325eaf","size":10000000,"actions":{"download":{"href":"https://gitlab.com/b-camacho/test-lfs.git/gitlab-lfs/objects/f5e02aa71e67f41d79023a128ca35bad86cf7b6656967bfe0884b3a3c4325eaf","header":{"Authorization":"Basic
|
||||
// Yi1jYW1hY2hvOmV5SjBlWEFpT2lKS1YxUWlMQ0poYkdjaU9pSklVekkxTmlKOS5leUprWVhSaElqcDdJbUZqZEc5eUlqb2lZaTFqWVcxaFkyaHZJbjBzSW1wMGFTSTZJbUptTURZNFpXVTFMVEprWmpVdE5HWm1ZUzFpWWpRMExUSXpNVEV3WVRReU1qWmtaaUlzSW1saGRDSTZNVGN4TkRZeE16ZzBOU3dpYm1KbUlqb3hOekUwTmpFek9EUXdMQ0psZUhBaU9qRTNNVFEyTWpFd05EVjkuZk9yMDNkYjBWSTFXQzFZaTBKRmJUNnJTTHJPZlBwVW9lYllkT0NQZlJ4QQ=="}}},"authenticated":true}]}
|
||||
|
||||
try {
|
||||
auto resp = nlohmann::json::parse(responseString);
|
||||
if (resp.contains("objects"))
|
||||
objects.insert(objects.end(), resp["objects"].begin(), resp["objects"].end());
|
||||
else
|
||||
throw Error("response does not contain 'objects'");
|
||||
|
||||
return objects;
|
||||
} catch (const nlohmann::json::parse_error & e) {
|
||||
printMsg(lvlTalkative, "Full response: '%1%'", responseString);
|
||||
throw Error("response did not parse as json: %s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void Fetch::fetch(
|
||||
const std::string & content,
|
||||
const CanonPath & pointerFilePath,
|
||||
StringSink & sink,
|
||||
std::function<void(uint64_t)> sizeCallback) const
|
||||
{
|
||||
debug("trying to fetch '%s' using git-lfs", pointerFilePath);
|
||||
|
||||
if (content.length() >= 1024) {
|
||||
warn("encountered file '%s' that should have been a git-lfs pointer, but is too large", pointerFilePath);
|
||||
sizeCallback(content.length());
|
||||
sink(content);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto pointer = parseLfsPointer(content, pointerFilePath.rel());
|
||||
if (pointer == std::nullopt) {
|
||||
warn("encountered file '%s' that should have been a git-lfs pointer, but is invalid", pointerFilePath);
|
||||
sizeCallback(content.length());
|
||||
sink(content);
|
||||
return;
|
||||
}
|
||||
|
||||
Path cacheDir = getCacheDir() + "/git-lfs";
|
||||
std::string key = hashString(HashAlgorithm::SHA256, pointerFilePath.rel()).to_string(HashFormat::Base16, false)
|
||||
+ "/" + pointer->oid;
|
||||
Path cachePath = cacheDir + "/" + key;
|
||||
if (pathExists(cachePath)) {
|
||||
debug("using cache entry %s -> %s", key, cachePath);
|
||||
sink(readFile(cachePath));
|
||||
return;
|
||||
}
|
||||
debug("did not find cache entry for %s", key);
|
||||
|
||||
std::vector<Pointer> pointers;
|
||||
pointers.push_back(pointer.value());
|
||||
const auto objUrls = fetchUrls(pointers);
|
||||
|
||||
const auto obj = objUrls[0];
|
||||
try {
|
||||
std::string sha256 = obj.at("oid"); // oid is also the sha256
|
||||
std::string ourl = obj.at("actions").at("download").at("href");
|
||||
std::string authHeader = "";
|
||||
if (obj.at("actions").at("download").contains("header")
|
||||
&& obj.at("actions").at("download").at("header").contains("Authorization")) {
|
||||
authHeader = obj["actions"]["download"]["header"]["Authorization"];
|
||||
}
|
||||
const uint64_t size = obj.at("size");
|
||||
sizeCallback(size);
|
||||
downloadToSink(ourl, authHeader, sink, sha256, size);
|
||||
|
||||
debug("creating cache entry %s -> %s", key, cachePath);
|
||||
if (!pathExists(dirOf(cachePath)))
|
||||
createDirs(dirOf(cachePath));
|
||||
writeFile(cachePath, sink.s);
|
||||
|
||||
debug("%s fetched with git-lfs", pointerFilePath);
|
||||
} catch (const nlohmann::json::out_of_range & e) {
|
||||
throw Error("bad json from /info/lfs/objects/batch: %s %s", obj, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace nix::lfs
|
||||
@@ -1,43 +0,0 @@
|
||||
#include "canon-path.hh"
|
||||
#include "serialise.hh"
|
||||
#include "url.hh"
|
||||
|
||||
#include <git2/repository.h>
|
||||
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
|
||||
namespace nix::lfs {
|
||||
|
||||
/**
|
||||
* git-lfs pointer
|
||||
* @see https://github.com/git-lfs/git-lfs/blob/2ef4108/docs/spec.md
|
||||
*/
|
||||
struct Pointer
|
||||
{
|
||||
std::string oid; // git-lfs managed object id. you give this to the lfs server
|
||||
// for downloads
|
||||
size_t size; // in bytes
|
||||
};
|
||||
|
||||
struct Fetch
|
||||
{
|
||||
// Reference to the repository
|
||||
const git_repository * repo;
|
||||
|
||||
// Git commit being fetched
|
||||
git_oid rev;
|
||||
|
||||
// derived from git remote url
|
||||
nix::ParsedURL url;
|
||||
|
||||
Fetch(git_repository * repo, git_oid rev);
|
||||
bool shouldFetch(const CanonPath & path) const;
|
||||
void fetch(
|
||||
const std::string & content,
|
||||
const CanonPath & pointerFilePath,
|
||||
StringSink & sink,
|
||||
std::function<void(uint64_t)> sizeCallback) const;
|
||||
std::vector<nlohmann::json> fetchUrls(const std::vector<Pointer> & pointers) const;
|
||||
};
|
||||
|
||||
} // namespace nix::lfs
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "git-utils.hh"
|
||||
#include "git-lfs-fetch.hh"
|
||||
#include "cache.hh"
|
||||
#include "finally.hh"
|
||||
#include "processes.hh"
|
||||
@@ -61,6 +60,14 @@ namespace nix {
|
||||
|
||||
struct GitSourceAccessor;
|
||||
|
||||
// Some wrapper types that ensure that the git_*_free functions get called.
|
||||
template<auto del>
|
||||
struct Deleter
|
||||
{
|
||||
template <typename T>
|
||||
void operator()(T * p) const { del(p); };
|
||||
};
|
||||
|
||||
typedef std::unique_ptr<git_repository, Deleter<git_repository_free>> Repository;
|
||||
typedef std::unique_ptr<git_tree_entry, Deleter<git_tree_entry_free>> TreeEntry;
|
||||
typedef std::unique_ptr<git_tree, Deleter<git_tree_free>> Tree;
|
||||
@@ -78,6 +85,20 @@ typedef std::unique_ptr<git_odb, Deleter<git_odb_free>> ObjectDb;
|
||||
typedef std::unique_ptr<git_packbuilder, Deleter<git_packbuilder_free>> PackBuilder;
|
||||
typedef std::unique_ptr<git_indexer, Deleter<git_indexer_free>> Indexer;
|
||||
|
||||
// A helper to ensure that we don't leak objects returned by libgit2.
|
||||
template<typename T>
|
||||
struct Setter
|
||||
{
|
||||
T & t;
|
||||
typename T::pointer p = nullptr;
|
||||
|
||||
Setter(T & t) : t(t) { }
|
||||
|
||||
~Setter() { if (p) t = T(p); }
|
||||
|
||||
operator typename T::pointer * () { return &p; }
|
||||
};
|
||||
|
||||
Hash toHash(const git_oid & oid)
|
||||
{
|
||||
#ifdef GIT_EXPERIMENTAL_SHA256
|
||||
@@ -485,15 +506,12 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
||||
/**
|
||||
* A 'GitSourceAccessor' with no regard for export-ignore or any other transformations.
|
||||
*/
|
||||
ref<GitSourceAccessor> getRawAccessor(
|
||||
const Hash & rev,
|
||||
bool smudgeLfs = false);
|
||||
ref<GitSourceAccessor> getRawAccessor(const Hash & rev);
|
||||
|
||||
ref<SourceAccessor> getAccessor(
|
||||
const Hash & rev,
|
||||
bool exportIgnore,
|
||||
std::string displayPrefix,
|
||||
bool smudgeLfs = false) override;
|
||||
std::string displayPrefix) override;
|
||||
|
||||
ref<SourceAccessor> getAccessor(const WorkdirInfo & wd, bool exportIgnore, MakeNotAllowedError e) override;
|
||||
|
||||
@@ -652,40 +670,24 @@ ref<GitRepo> GitRepo::openRepo(const std::filesystem::path & path, bool create,
|
||||
/**
|
||||
* Raw git tree input accessor.
|
||||
*/
|
||||
|
||||
struct GitSourceAccessor : SourceAccessor
|
||||
{
|
||||
ref<GitRepoImpl> repo;
|
||||
Object root;
|
||||
std::optional<lfs::Fetch> lfsFetch = std::nullopt;
|
||||
|
||||
GitSourceAccessor(ref<GitRepoImpl> repo_, const Hash & rev, bool smudgeLfs)
|
||||
GitSourceAccessor(ref<GitRepoImpl> repo_, const Hash & rev)
|
||||
: repo(repo_)
|
||||
, root(peelToTreeOrBlob(lookupObject(*repo, hashToOID(rev)).get()))
|
||||
{
|
||||
if (smudgeLfs)
|
||||
lfsFetch = std::make_optional(lfs::Fetch(*repo, hashToOID(rev)));
|
||||
}
|
||||
|
||||
std::string readBlob(const CanonPath & path, bool symlink)
|
||||
{
|
||||
const auto blob = getBlob(path, symlink);
|
||||
auto blob = getBlob(path, symlink);
|
||||
|
||||
if (lfsFetch) {
|
||||
if (lfsFetch->shouldFetch(path)) {
|
||||
StringSink s;
|
||||
try {
|
||||
auto contents = std::string((const char *) git_blob_rawcontent(blob.get()), git_blob_rawsize(blob.get()));
|
||||
lfsFetch->fetch(contents, path, s, [&s](uint64_t size){ s.s.reserve(size); });
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "while smudging git-lfs file '%s'", path);
|
||||
throw;
|
||||
}
|
||||
return s.s;
|
||||
}
|
||||
}
|
||||
auto data = std::string_view((const char *) git_blob_rawcontent(blob.get()), git_blob_rawsize(blob.get()));
|
||||
|
||||
return std::string((const char *) git_blob_rawcontent(blob.get()), git_blob_rawsize(blob.get()));
|
||||
return std::string(data);
|
||||
}
|
||||
|
||||
std::string readFile(const CanonPath & path) override
|
||||
@@ -1189,22 +1191,19 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
|
||||
}
|
||||
};
|
||||
|
||||
ref<GitSourceAccessor> GitRepoImpl::getRawAccessor(
|
||||
const Hash & rev,
|
||||
bool smudgeLfs)
|
||||
ref<GitSourceAccessor> GitRepoImpl::getRawAccessor(const Hash & rev)
|
||||
{
|
||||
auto self = ref<GitRepoImpl>(shared_from_this());
|
||||
return make_ref<GitSourceAccessor>(self, rev, smudgeLfs);
|
||||
return make_ref<GitSourceAccessor>(self, rev);
|
||||
}
|
||||
|
||||
ref<SourceAccessor> GitRepoImpl::getAccessor(
|
||||
const Hash & rev,
|
||||
bool exportIgnore,
|
||||
std::string displayPrefix,
|
||||
bool smudgeLfs)
|
||||
std::string displayPrefix)
|
||||
{
|
||||
auto self = ref<GitRepoImpl>(shared_from_this());
|
||||
ref<GitSourceAccessor> rawGitAccessor = getRawAccessor(rev, smudgeLfs);
|
||||
ref<GitSourceAccessor> rawGitAccessor = getRawAccessor(rev);
|
||||
rawGitAccessor->setPathDisplay(std::move(displayPrefix));
|
||||
if (exportIgnore)
|
||||
return make_ref<GitExportIgnoreSourceAccessor>(self, rawGitAccessor, rev);
|
||||
|
||||
@@ -89,8 +89,7 @@ struct GitRepo
|
||||
virtual ref<SourceAccessor> getAccessor(
|
||||
const Hash & rev,
|
||||
bool exportIgnore,
|
||||
std::string displayPrefix,
|
||||
bool smudgeLfs = false) = 0;
|
||||
std::string displayPrefix) = 0;
|
||||
|
||||
virtual ref<SourceAccessor> getAccessor(const WorkdirInfo & wd, bool exportIgnore, MakeNotAllowedError makeNotAllowedError) = 0;
|
||||
|
||||
@@ -127,26 +126,4 @@ struct GitRepo
|
||||
|
||||
ref<GitRepo> getTarballCache();
|
||||
|
||||
// A helper to ensure that the `git_*_free` functions get called.
|
||||
template<auto del>
|
||||
struct Deleter
|
||||
{
|
||||
template <typename T>
|
||||
void operator()(T * p) const { del(p); };
|
||||
};
|
||||
|
||||
// A helper to ensure that we don't leak objects returned by libgit2.
|
||||
template<typename T>
|
||||
struct Setter
|
||||
{
|
||||
T & t;
|
||||
typename T::pointer p = nullptr;
|
||||
|
||||
Setter(T & t) : t(t) { }
|
||||
|
||||
~Setter() { if (p) t = T(p); }
|
||||
|
||||
operator typename T::pointer * () { return &p; }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "pathlocks.hh"
|
||||
#include "processes.hh"
|
||||
#include "git.hh"
|
||||
#include "mounted-source-accessor.hh"
|
||||
#include "git-utils.hh"
|
||||
#include "logging.hh"
|
||||
#include "finally.hh"
|
||||
@@ -184,7 +185,7 @@ struct GitInputScheme : InputScheme
|
||||
for (auto & [name, value] : url.query) {
|
||||
if (name == "rev" || name == "ref" || name == "keytype" || name == "publicKey" || name == "publicKeys")
|
||||
attrs.emplace(name, value);
|
||||
else if (name == "shallow" || name == "submodules" || name == "lfs" || name == "exportIgnore" || name == "allRefs" || name == "verifyCommit")
|
||||
else if (name == "shallow" || name == "submodules" || name == "exportIgnore" || name == "allRefs" || name == "verifyCommit")
|
||||
attrs.emplace(name, Explicit<bool> { value == "1" });
|
||||
else
|
||||
url2.query.emplace(name, value);
|
||||
@@ -209,7 +210,6 @@ struct GitInputScheme : InputScheme
|
||||
"rev",
|
||||
"shallow",
|
||||
"submodules",
|
||||
"lfs",
|
||||
"exportIgnore",
|
||||
"lastModified",
|
||||
"revCount",
|
||||
@@ -262,8 +262,6 @@ struct GitInputScheme : InputScheme
|
||||
if (auto ref = input.getRef()) url.query.insert_or_assign("ref", *ref);
|
||||
if (getShallowAttr(input))
|
||||
url.query.insert_or_assign("shallow", "1");
|
||||
if (getLfsAttr(input))
|
||||
url.query.insert_or_assign("lfs", "1");
|
||||
if (getSubmodulesAttr(input))
|
||||
url.query.insert_or_assign("submodules", "1");
|
||||
if (maybeGetBoolAttr(input.attrs, "exportIgnore").value_or(false))
|
||||
@@ -413,11 +411,6 @@ struct GitInputScheme : InputScheme
|
||||
return maybeGetBoolAttr(input.attrs, "submodules").value_or(false);
|
||||
}
|
||||
|
||||
bool getLfsAttr(const Input & input) const
|
||||
{
|
||||
return maybeGetBoolAttr(input.attrs, "lfs").value_or(false);
|
||||
}
|
||||
|
||||
bool getExportIgnoreAttr(const Input & input) const
|
||||
{
|
||||
return maybeGetBoolAttr(input.attrs, "exportIgnore").value_or(false);
|
||||
@@ -685,8 +678,7 @@ struct GitInputScheme : InputScheme
|
||||
verifyCommit(input, repo);
|
||||
|
||||
bool exportIgnore = getExportIgnoreAttr(input);
|
||||
bool smudgeLfs = getLfsAttr(input);
|
||||
auto accessor = repo->getAccessor(rev, exportIgnore, "«" + input.to_string() + "»", smudgeLfs);
|
||||
auto accessor = repo->getAccessor(rev, exportIgnore, "«" + input.to_string() + "»");
|
||||
|
||||
/* If the repo has submodules, fetch them and return a mounted
|
||||
input accessor consisting of the accessor for the top-level
|
||||
@@ -706,7 +698,6 @@ struct GitInputScheme : InputScheme
|
||||
attrs.insert_or_assign("rev", submoduleRev.gitRev());
|
||||
attrs.insert_or_assign("exportIgnore", Explicit<bool>{ exportIgnore });
|
||||
attrs.insert_or_assign("submodules", Explicit<bool>{ true });
|
||||
attrs.insert_or_assign("lfs", Explicit<bool>{ smudgeLfs });
|
||||
attrs.insert_or_assign("allRefs", Explicit<bool>{ true });
|
||||
auto submoduleInput = fetchers::Input::fromAttrs(*input.settings, std::move(attrs));
|
||||
auto [submoduleAccessor, submoduleInput2] =
|
||||
@@ -847,7 +838,7 @@ struct GitInputScheme : InputScheme
|
||||
{
|
||||
auto makeFingerprint = [&](const Hash & rev)
|
||||
{
|
||||
return rev.gitRev() + (getSubmodulesAttr(input) ? ";s" : "") + (getExportIgnoreAttr(input) ? ";e" : "") + (getLfsAttr(input) ? ";l" : "");
|
||||
return rev.gitRev() + (getSubmodulesAttr(input) ? ";s" : "") + (getExportIgnoreAttr(input) ? ";e" : "");
|
||||
};
|
||||
|
||||
if (auto rev = input.getRev())
|
||||
|
||||
@@ -14,7 +14,7 @@ cxx = meson.get_compiler('cpp')
|
||||
|
||||
subdir('nix-meson-build-support/deps-lists')
|
||||
|
||||
configuration_data()
|
||||
configdata = configuration_data()
|
||||
|
||||
deps_private_maybe_subproject = [
|
||||
]
|
||||
@@ -48,12 +48,12 @@ sources = files(
|
||||
'fetch-to-store.cc',
|
||||
'fetchers.cc',
|
||||
'filtering-source-accessor.cc',
|
||||
'git-lfs-fetch.cc',
|
||||
'git-utils.cc',
|
||||
'git.cc',
|
||||
'github.cc',
|
||||
'indirect.cc',
|
||||
'mercurial.cc',
|
||||
'mounted-source-accessor.cc',
|
||||
'path.cc',
|
||||
'registry.cc',
|
||||
'store-path-accessor.cc',
|
||||
@@ -69,8 +69,8 @@ headers = files(
|
||||
'fetch-to-store.hh',
|
||||
'fetchers.hh',
|
||||
'filtering-source-accessor.hh',
|
||||
'git-lfs-fetch.hh',
|
||||
'git-utils.hh',
|
||||
'mounted-source-accessor.hh',
|
||||
'registry.hh',
|
||||
'store-path-accessor.hh',
|
||||
'tarball.hh',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "source-accessor.hh"
|
||||
#include "mounted-source-accessor.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -23,6 +23,12 @@ struct MountedSourceAccessor : SourceAccessor
|
||||
return accessor->readFile(subpath);
|
||||
}
|
||||
|
||||
bool pathExists(const CanonPath & path) override
|
||||
{
|
||||
auto [accessor, subpath] = resolve(path);
|
||||
return accessor->pathExists(subpath);
|
||||
}
|
||||
|
||||
std::optional<Stat> maybeLstat(const CanonPath & path) override
|
||||
{
|
||||
auto [accessor, subpath] = resolve(path);
|
||||
@@ -63,12 +69,6 @@ struct MountedSourceAccessor : SourceAccessor
|
||||
path.pop();
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::filesystem::path> getPhysicalPath(const CanonPath & path) override
|
||||
{
|
||||
auto [accessor, subpath] = resolve(path);
|
||||
return accessor->getPhysicalPath(subpath);
|
||||
}
|
||||
};
|
||||
|
||||
ref<SourceAccessor> makeMountedSourceAccessor(std::map<CanonPath, ref<SourceAccessor>> mounts)
|
||||
9
src/libfetchers/mounted-source-accessor.hh
Normal file
9
src/libfetchers/mounted-source-accessor.hh
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "source-accessor.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
ref<SourceAccessor> makeMountedSourceAccessor(std::map<CanonPath, ref<SourceAccessor>> mounts);
|
||||
|
||||
}
|
||||
@@ -41,6 +41,14 @@ mkMesonLibrary (finalAttrs: {
|
||||
nlohmann_json
|
||||
];
|
||||
|
||||
preConfigure =
|
||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
||||
# Do the meson utils, without modification.
|
||||
''
|
||||
chmod u+w ./.version
|
||||
echo ${version} > ../../.version
|
||||
'';
|
||||
|
||||
meta = {
|
||||
platforms = lib.platforms.unix ++ lib.platforms.windows;
|
||||
};
|
||||
|
||||
@@ -38,6 +38,14 @@ mkMesonLibrary (finalAttrs: {
|
||||
nix-flake
|
||||
];
|
||||
|
||||
preConfigure =
|
||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
||||
# Do the meson utils, without modification.
|
||||
''
|
||||
chmod u+w ./.version
|
||||
echo ${version} > ../../.version
|
||||
'';
|
||||
|
||||
mesonFlags = [
|
||||
];
|
||||
|
||||
|
||||
@@ -46,6 +46,14 @@ mkMesonExecutable (finalAttrs: {
|
||||
gtest
|
||||
];
|
||||
|
||||
preConfigure =
|
||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
||||
# Do the meson utils, without modification.
|
||||
''
|
||||
chmod u+w ./.version
|
||||
echo ${version} > ../../.version
|
||||
'';
|
||||
|
||||
mesonFlags = [
|
||||
];
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "flake/settings.hh"
|
||||
#include "value-to-json.hh"
|
||||
#include "local-fs-store.hh"
|
||||
#include "fetch-to-store.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
@@ -25,7 +24,7 @@ namespace flake {
|
||||
struct FetchedFlake
|
||||
{
|
||||
FlakeRef lockedRef;
|
||||
ref<SourceAccessor> accessor;
|
||||
StorePath storePath;
|
||||
};
|
||||
|
||||
typedef std::map<FlakeRef, FetchedFlake> FlakeCache;
|
||||
@@ -41,7 +40,7 @@ static std::optional<FetchedFlake> lookupInFlakeCache(
|
||||
return i->second;
|
||||
}
|
||||
|
||||
static std::tuple<ref<SourceAccessor>, FlakeRef, FlakeRef> fetchOrSubstituteTree(
|
||||
static std::tuple<StorePath, FlakeRef, FlakeRef> fetchOrSubstituteTree(
|
||||
EvalState & state,
|
||||
const FlakeRef & originalRef,
|
||||
bool useRegistries,
|
||||
@@ -52,8 +51,8 @@ static std::tuple<ref<SourceAccessor>, FlakeRef, FlakeRef> fetchOrSubstituteTree
|
||||
|
||||
if (!fetched) {
|
||||
if (originalRef.input.isDirect()) {
|
||||
auto [accessor, lockedRef] = originalRef.lazyFetch(state.store);
|
||||
fetched.emplace(FetchedFlake{.lockedRef = lockedRef, .accessor = accessor});
|
||||
auto [storePath, lockedRef] = originalRef.fetchTree(state.store);
|
||||
fetched.emplace(FetchedFlake{.lockedRef = lockedRef, .storePath = storePath});
|
||||
} else {
|
||||
if (useRegistries) {
|
||||
resolvedRef = originalRef.resolve(
|
||||
@@ -65,8 +64,8 @@ static std::tuple<ref<SourceAccessor>, FlakeRef, FlakeRef> fetchOrSubstituteTree
|
||||
});
|
||||
fetched = lookupInFlakeCache(flakeCache, originalRef);
|
||||
if (!fetched) {
|
||||
auto [accessor, lockedRef] = resolvedRef.lazyFetch(state.store);
|
||||
fetched.emplace(FetchedFlake{.lockedRef = lockedRef, .accessor = accessor});
|
||||
auto [storePath, lockedRef] = resolvedRef.fetchTree(state.store);
|
||||
fetched.emplace(FetchedFlake{.lockedRef = lockedRef, .storePath = storePath});
|
||||
}
|
||||
flakeCache.insert_or_assign(resolvedRef, *fetched);
|
||||
}
|
||||
@@ -77,27 +76,14 @@ static std::tuple<ref<SourceAccessor>, FlakeRef, FlakeRef> fetchOrSubstituteTree
|
||||
flakeCache.insert_or_assign(originalRef, *fetched);
|
||||
}
|
||||
|
||||
debug("got tree '%s' from '%s'", fetched->accessor, fetched->lockedRef);
|
||||
debug("got tree '%s' from '%s'",
|
||||
state.store->printStorePath(fetched->storePath), fetched->lockedRef);
|
||||
|
||||
return {fetched->accessor, resolvedRef, fetched->lockedRef};
|
||||
}
|
||||
state.allowPath(fetched->storePath);
|
||||
|
||||
static StorePath copyInputToStore(
|
||||
EvalState & state,
|
||||
fetchers::Input & input,
|
||||
const fetchers::Input & originalInput,
|
||||
ref<SourceAccessor> accessor)
|
||||
{
|
||||
auto storePath = fetchToStore(*state.store, accessor, FetchMode::Copy, input.getName());
|
||||
assert(!originalRef.input.getNarHash() || fetched->storePath == originalRef.input.computeStorePath(*state.store));
|
||||
|
||||
state.allowPath(storePath);
|
||||
|
||||
auto narHash = state.store->queryPathInfo(storePath)->narHash;
|
||||
input.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
|
||||
|
||||
assert(!originalInput.getNarHash() || storePath == originalInput.computeStorePath(*state.store));
|
||||
|
||||
return storePath;
|
||||
return {fetched->storePath, resolvedRef, fetched->lockedRef};
|
||||
}
|
||||
|
||||
static void forceTrivialValue(EvalState & state, Value & value, const PosIdx pos)
|
||||
@@ -115,54 +101,19 @@ static void expectType(EvalState & state, ValueType type,
|
||||
showType(type), showType(value.type()), state.positions[pos]);
|
||||
}
|
||||
|
||||
static std::pair<std::map<FlakeId, FlakeInput>, fetchers::Attrs> parseFlakeInputs(
|
||||
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
|
||||
EvalState & state,
|
||||
Value * value,
|
||||
const PosIdx pos,
|
||||
const InputAttrPath & lockRootAttrPath,
|
||||
const SourcePath & flakeDir,
|
||||
bool allowSelf);
|
||||
|
||||
static void parseFlakeInputAttr(
|
||||
EvalState & state,
|
||||
const Attr & attr,
|
||||
fetchers::Attrs & attrs)
|
||||
{
|
||||
// Allow selecting a subset of enum values
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wswitch-enum"
|
||||
switch (attr.value->type()) {
|
||||
case nString:
|
||||
attrs.emplace(state.symbols[attr.name], attr.value->c_str());
|
||||
break;
|
||||
case nBool:
|
||||
attrs.emplace(state.symbols[attr.name], Explicit<bool> { attr.value->boolean() });
|
||||
break;
|
||||
case nInt: {
|
||||
auto intValue = attr.value->integer().value;
|
||||
if (intValue < 0)
|
||||
state.error<EvalError>("negative value given for flake input attribute %1%: %2%", state.symbols[attr.name], intValue).debugThrow();
|
||||
attrs.emplace(state.symbols[attr.name], uint64_t(intValue));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (attr.name == state.symbols.create("publicKeys")) {
|
||||
experimentalFeatureSettings.require(Xp::VerifiedFetches);
|
||||
NixStringContext emptyContext = {};
|
||||
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, attr.pos, emptyContext).dump());
|
||||
} else
|
||||
state.error<TypeError>("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
|
||||
state.symbols[attr.name], showType(*attr.value)).debugThrow();
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
const InputPath & lockRootPath,
|
||||
const SourcePath & flakeDir);
|
||||
|
||||
static FlakeInput parseFlakeInput(
|
||||
EvalState & state,
|
||||
std::string_view inputName,
|
||||
Value * value,
|
||||
const PosIdx pos,
|
||||
const InputAttrPath & lockRootAttrPath,
|
||||
const InputPath & lockRootPath,
|
||||
const SourcePath & flakeDir)
|
||||
{
|
||||
expectType(state, nAttrs, *value, pos);
|
||||
@@ -186,7 +137,7 @@ static FlakeInput parseFlakeInput(
|
||||
else if (attr.value->type() == nPath) {
|
||||
auto path = attr.value->path();
|
||||
if (path.accessor != flakeDir.accessor)
|
||||
throw Error("input attribute path '%s' at %s must be in the same source tree as %s",
|
||||
throw Error("input path '%s' at %s must be in the same source tree as %s",
|
||||
path, state.positions[attr.pos], flakeDir);
|
||||
url = "path:" + flakeDir.path.makeRelative(path.path);
|
||||
}
|
||||
@@ -198,14 +149,44 @@ static FlakeInput parseFlakeInput(
|
||||
expectType(state, nBool, *attr.value, attr.pos);
|
||||
input.isFlake = attr.value->boolean();
|
||||
} else if (attr.name == sInputs) {
|
||||
input.overrides = parseFlakeInputs(state, attr.value, attr.pos, lockRootAttrPath, flakeDir, false).first;
|
||||
input.overrides = parseFlakeInputs(state, attr.value, attr.pos, lockRootPath, flakeDir);
|
||||
} else if (attr.name == sFollows) {
|
||||
expectType(state, nString, *attr.value, attr.pos);
|
||||
auto follows(parseInputAttrPath(attr.value->c_str()));
|
||||
follows.insert(follows.begin(), lockRootAttrPath.begin(), lockRootAttrPath.end());
|
||||
auto follows(parseInputPath(attr.value->c_str()));
|
||||
follows.insert(follows.begin(), lockRootPath.begin(), lockRootPath.end());
|
||||
input.follows = follows;
|
||||
} else
|
||||
parseFlakeInputAttr(state, attr, attrs);
|
||||
} else {
|
||||
// Allow selecting a subset of enum values
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wswitch-enum"
|
||||
switch (attr.value->type()) {
|
||||
case nString:
|
||||
attrs.emplace(state.symbols[attr.name], attr.value->c_str());
|
||||
break;
|
||||
case nBool:
|
||||
attrs.emplace(state.symbols[attr.name], Explicit<bool> { attr.value->boolean() });
|
||||
break;
|
||||
case nInt: {
|
||||
auto intValue = attr.value->integer().value;
|
||||
|
||||
if (intValue < 0) {
|
||||
state.error<EvalError>("negative value given for flake input attribute %1%: %2%", state.symbols[attr.name], intValue).debugThrow();
|
||||
}
|
||||
|
||||
attrs.emplace(state.symbols[attr.name], uint64_t(intValue));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (attr.name == state.symbols.create("publicKeys")) {
|
||||
experimentalFeatureSettings.require(Xp::VerifiedFetches);
|
||||
NixStringContext emptyContext = {};
|
||||
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, emptyContext).dump());
|
||||
} else
|
||||
state.error<TypeError>("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
|
||||
state.symbols[attr.name], showType(*attr.value)).debugThrow();
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
} catch (Error & e) {
|
||||
e.addTrace(
|
||||
state.positions[attr.pos],
|
||||
@@ -235,39 +216,28 @@ static FlakeInput parseFlakeInput(
|
||||
return input;
|
||||
}
|
||||
|
||||
static std::pair<std::map<FlakeId, FlakeInput>, fetchers::Attrs> parseFlakeInputs(
|
||||
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
|
||||
EvalState & state,
|
||||
Value * value,
|
||||
const PosIdx pos,
|
||||
const InputAttrPath & lockRootAttrPath,
|
||||
const SourcePath & flakeDir,
|
||||
bool allowSelf)
|
||||
const InputPath & lockRootPath,
|
||||
const SourcePath & flakeDir)
|
||||
{
|
||||
std::map<FlakeId, FlakeInput> inputs;
|
||||
fetchers::Attrs selfAttrs;
|
||||
|
||||
expectType(state, nAttrs, *value, pos);
|
||||
|
||||
for (auto & inputAttr : *value->attrs()) {
|
||||
auto inputName = state.symbols[inputAttr.name];
|
||||
if (inputName == "self") {
|
||||
if (!allowSelf)
|
||||
throw Error("'self' input attribute not allowed at %s", state.positions[inputAttr.pos]);
|
||||
expectType(state, nAttrs, *inputAttr.value, inputAttr.pos);
|
||||
for (auto & attr : *inputAttr.value->attrs())
|
||||
parseFlakeInputAttr(state, attr, selfAttrs);
|
||||
} else {
|
||||
inputs.emplace(inputName,
|
||||
parseFlakeInput(state,
|
||||
inputName,
|
||||
inputAttr.value,
|
||||
inputAttr.pos,
|
||||
lockRootAttrPath,
|
||||
flakeDir));
|
||||
}
|
||||
inputs.emplace(state.symbols[inputAttr.name],
|
||||
parseFlakeInput(state,
|
||||
state.symbols[inputAttr.name],
|
||||
inputAttr.value,
|
||||
inputAttr.pos,
|
||||
lockRootPath,
|
||||
flakeDir));
|
||||
}
|
||||
|
||||
return {inputs, selfAttrs};
|
||||
return inputs;
|
||||
}
|
||||
|
||||
static Flake readFlake(
|
||||
@@ -276,7 +246,7 @@ static Flake readFlake(
|
||||
const FlakeRef & resolvedRef,
|
||||
const FlakeRef & lockedRef,
|
||||
const SourcePath & rootDir,
|
||||
const InputAttrPath & lockRootAttrPath)
|
||||
const InputPath & lockRootPath)
|
||||
{
|
||||
auto flakeDir = rootDir / CanonPath(resolvedRef.subdir);
|
||||
auto flakePath = flakeDir / "flake.nix";
|
||||
@@ -299,11 +269,8 @@ static Flake readFlake(
|
||||
|
||||
auto sInputs = state.symbols.create("inputs");
|
||||
|
||||
if (auto inputs = vInfo.attrs()->get(sInputs)) {
|
||||
auto [flakeInputs, selfAttrs] = parseFlakeInputs(state, inputs->value, inputs->pos, lockRootAttrPath, flakeDir, true);
|
||||
flake.inputs = std::move(flakeInputs);
|
||||
flake.selfAttrs = std::move(selfAttrs);
|
||||
}
|
||||
if (auto inputs = vInfo.attrs()->get(sInputs))
|
||||
flake.inputs = parseFlakeInputs(state, inputs->value, inputs->pos, lockRootPath, flakeDir);
|
||||
|
||||
auto sOutputs = state.symbols.create("outputs");
|
||||
|
||||
@@ -334,10 +301,10 @@ static Flake readFlake(
|
||||
state.symbols[setting.name],
|
||||
std::string(state.forceStringNoCtx(*setting.value, setting.pos, "")));
|
||||
else if (setting.value->type() == nPath) {
|
||||
auto storePath = fetchToStore(*state.store, setting.value->path(), FetchMode::Copy);
|
||||
NixStringContext emptyContext = {};
|
||||
flake.config.settings.emplace(
|
||||
state.symbols[setting.name],
|
||||
state.store->printStorePath(storePath));
|
||||
state.coerceToString(setting.pos, *setting.value, emptyContext, "", false, true, true).toOwned());
|
||||
}
|
||||
else if (setting.value->type() == nInt)
|
||||
flake.config.settings.emplace(
|
||||
@@ -375,55 +342,17 @@ static Flake readFlake(
|
||||
return flake;
|
||||
}
|
||||
|
||||
static FlakeRef applySelfAttrs(
|
||||
const FlakeRef & ref,
|
||||
const Flake & flake)
|
||||
{
|
||||
auto newRef(ref);
|
||||
|
||||
std::set<std::string> allowedAttrs{"submodules", "lfs"};
|
||||
|
||||
for (auto & attr : flake.selfAttrs) {
|
||||
if (!allowedAttrs.contains(attr.first))
|
||||
throw Error("flake 'self' attribute '%s' is not supported", attr.first);
|
||||
newRef.input.attrs.insert_or_assign(attr.first, attr.second);
|
||||
}
|
||||
|
||||
return newRef;
|
||||
}
|
||||
|
||||
static Flake getFlake(
|
||||
EvalState & state,
|
||||
const FlakeRef & originalRef,
|
||||
bool useRegistries,
|
||||
FlakeCache & flakeCache,
|
||||
const InputAttrPath & lockRootAttrPath)
|
||||
const InputPath & lockRootPath)
|
||||
{
|
||||
// Fetch a lazy tree first.
|
||||
auto [accessor, resolvedRef, lockedRef] = fetchOrSubstituteTree(
|
||||
auto [storePath, resolvedRef, lockedRef] = fetchOrSubstituteTree(
|
||||
state, originalRef, useRegistries, flakeCache);
|
||||
|
||||
// Parse/eval flake.nix to get at the input.self attributes.
|
||||
auto flake = readFlake(state, originalRef, resolvedRef, lockedRef, {accessor}, lockRootAttrPath);
|
||||
|
||||
// Re-fetch the tree if necessary.
|
||||
auto newLockedRef = applySelfAttrs(lockedRef, flake);
|
||||
|
||||
if (lockedRef != newLockedRef) {
|
||||
debug("refetching input '%s' due to self attribute", newLockedRef);
|
||||
// FIXME: need to remove attrs that are invalidated by the changed input attrs, such as 'narHash'.
|
||||
newLockedRef.input.attrs.erase("narHash");
|
||||
auto [accessor2, resolvedRef2, lockedRef2] = fetchOrSubstituteTree(
|
||||
state, newLockedRef, false, flakeCache);
|
||||
accessor = accessor2;
|
||||
lockedRef = lockedRef2;
|
||||
}
|
||||
|
||||
// Copy the tree to the store.
|
||||
auto storePath = copyInputToStore(state, lockedRef.input, originalRef.input, accessor);
|
||||
|
||||
// Re-parse flake.nix from the store.
|
||||
return readFlake(state, originalRef, resolvedRef, lockedRef, state.storePath(storePath), lockRootAttrPath);
|
||||
return readFlake(state, originalRef, resolvedRef, lockedRef, state.rootPath(state.store->toRealPath(storePath)), lockRootPath);
|
||||
}
|
||||
|
||||
Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool useRegistries)
|
||||
@@ -478,12 +407,12 @@ LockedFlake lockFlake(
|
||||
{
|
||||
FlakeInput input;
|
||||
SourcePath sourcePath;
|
||||
std::optional<InputAttrPath> parentInputAttrPath; // FIXME: rename to inputAttrPathPrefix?
|
||||
std::optional<InputPath> parentInputPath; // FIXME: rename to inputPathPrefix?
|
||||
};
|
||||
|
||||
std::map<InputAttrPath, OverrideTarget> overrides;
|
||||
std::set<InputAttrPath> explicitCliOverrides;
|
||||
std::set<InputAttrPath> overridesUsed, updatesUsed;
|
||||
std::map<InputPath, OverrideTarget> overrides;
|
||||
std::set<InputPath> explicitCliOverrides;
|
||||
std::set<InputPath> overridesUsed, updatesUsed;
|
||||
std::map<ref<Node>, SourcePath> nodePaths;
|
||||
|
||||
for (auto & i : lockFlags.inputOverrides) {
|
||||
@@ -507,9 +436,9 @@ LockedFlake lockFlake(
|
||||
std::function<void(
|
||||
const FlakeInputs & flakeInputs,
|
||||
ref<Node> node,
|
||||
const InputAttrPath & inputAttrPathPrefix,
|
||||
const InputPath & inputPathPrefix,
|
||||
std::shared_ptr<const Node> oldNode,
|
||||
const InputAttrPath & followsPrefix,
|
||||
const InputPath & followsPrefix,
|
||||
const SourcePath & sourcePath,
|
||||
bool trustLock)>
|
||||
computeLocks;
|
||||
@@ -521,7 +450,7 @@ LockedFlake lockFlake(
|
||||
/* The node whose locks are to be updated.*/
|
||||
ref<Node> node,
|
||||
/* The path to this node in the lock file graph. */
|
||||
const InputAttrPath & inputAttrPathPrefix,
|
||||
const InputPath & inputPathPrefix,
|
||||
/* The old node, if any, from which locks can be
|
||||
copied. */
|
||||
std::shared_ptr<const Node> oldNode,
|
||||
@@ -529,59 +458,59 @@ LockedFlake lockFlake(
|
||||
interpreted. When a node is initially locked, it's
|
||||
relative to the node's flake; when it's already locked,
|
||||
it's relative to the root of the lock file. */
|
||||
const InputAttrPath & followsPrefix,
|
||||
const InputPath & followsPrefix,
|
||||
/* The source path of this node's flake. */
|
||||
const SourcePath & sourcePath,
|
||||
bool trustLock)
|
||||
{
|
||||
debug("computing lock file node '%s'", printInputAttrPath(inputAttrPathPrefix));
|
||||
debug("computing lock file node '%s'", printInputPath(inputPathPrefix));
|
||||
|
||||
/* Get the overrides (i.e. attributes of the form
|
||||
'inputs.nixops.inputs.nixpkgs.url = ...'). */
|
||||
for (auto & [id, input] : flakeInputs) {
|
||||
for (auto & [idOverride, inputOverride] : input.overrides) {
|
||||
auto inputAttrPath(inputAttrPathPrefix);
|
||||
inputAttrPath.push_back(id);
|
||||
inputAttrPath.push_back(idOverride);
|
||||
overrides.emplace(inputAttrPath,
|
||||
auto inputPath(inputPathPrefix);
|
||||
inputPath.push_back(id);
|
||||
inputPath.push_back(idOverride);
|
||||
overrides.emplace(inputPath,
|
||||
OverrideTarget {
|
||||
.input = inputOverride,
|
||||
.sourcePath = sourcePath,
|
||||
.parentInputAttrPath = inputAttrPathPrefix
|
||||
.parentInputPath = inputPathPrefix
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* Check whether this input has overrides for a
|
||||
non-existent input. */
|
||||
for (auto [inputAttrPath, inputOverride] : overrides) {
|
||||
auto inputAttrPath2(inputAttrPath);
|
||||
auto follow = inputAttrPath2.back();
|
||||
inputAttrPath2.pop_back();
|
||||
if (inputAttrPath2 == inputAttrPathPrefix && !flakeInputs.count(follow))
|
||||
for (auto [inputPath, inputOverride] : overrides) {
|
||||
auto inputPath2(inputPath);
|
||||
auto follow = inputPath2.back();
|
||||
inputPath2.pop_back();
|
||||
if (inputPath2 == inputPathPrefix && !flakeInputs.count(follow))
|
||||
warn(
|
||||
"input '%s' has an override for a non-existent input '%s'",
|
||||
printInputAttrPath(inputAttrPathPrefix), follow);
|
||||
printInputPath(inputPathPrefix), follow);
|
||||
}
|
||||
|
||||
/* Go over the flake inputs, resolve/fetch them if
|
||||
necessary (i.e. if they're new or the flakeref changed
|
||||
from what's in the lock file). */
|
||||
for (auto & [id, input2] : flakeInputs) {
|
||||
auto inputAttrPath(inputAttrPathPrefix);
|
||||
inputAttrPath.push_back(id);
|
||||
auto inputAttrPathS = printInputAttrPath(inputAttrPath);
|
||||
debug("computing input '%s'", inputAttrPathS);
|
||||
auto inputPath(inputPathPrefix);
|
||||
inputPath.push_back(id);
|
||||
auto inputPathS = printInputPath(inputPath);
|
||||
debug("computing input '%s'", inputPathS);
|
||||
|
||||
try {
|
||||
|
||||
/* Do we have an override for this input from one of the
|
||||
ancestors? */
|
||||
auto i = overrides.find(inputAttrPath);
|
||||
auto i = overrides.find(inputPath);
|
||||
bool hasOverride = i != overrides.end();
|
||||
bool hasCliOverride = explicitCliOverrides.contains(inputAttrPath);
|
||||
bool hasCliOverride = explicitCliOverrides.contains(inputPath);
|
||||
if (hasOverride)
|
||||
overridesUsed.insert(inputAttrPath);
|
||||
overridesUsed.insert(inputPath);
|
||||
auto input = hasOverride ? i->second.input : input2;
|
||||
|
||||
/* Resolve relative 'path:' inputs relative to
|
||||
@@ -596,11 +525,11 @@ LockedFlake lockFlake(
|
||||
/* Resolve 'follows' later (since it may refer to an input
|
||||
path we haven't processed yet. */
|
||||
if (input.follows) {
|
||||
InputAttrPath target;
|
||||
InputPath target;
|
||||
|
||||
target.insert(target.end(), input.follows->begin(), input.follows->end());
|
||||
|
||||
debug("input '%s' follows '%s'", inputAttrPathS, printInputAttrPath(target));
|
||||
debug("input '%s' follows '%s'", inputPathS, printInputPath(target));
|
||||
node->inputs.insert_or_assign(id, target);
|
||||
continue;
|
||||
}
|
||||
@@ -609,7 +538,7 @@ LockedFlake lockFlake(
|
||||
|
||||
auto overridenParentPath =
|
||||
input.ref->input.isRelative()
|
||||
? std::optional<InputAttrPath>(hasOverride ? i->second.parentInputAttrPath : inputAttrPathPrefix)
|
||||
? std::optional<InputPath>(hasOverride ? i->second.parentInputPath : inputPathPrefix)
|
||||
: std::nullopt;
|
||||
|
||||
auto resolveRelativePath = [&]() -> std::optional<SourcePath>
|
||||
@@ -628,9 +557,9 @@ LockedFlake lockFlake(
|
||||
auto getInputFlake = [&](const FlakeRef & ref)
|
||||
{
|
||||
if (auto resolvedPath = resolveRelativePath()) {
|
||||
return readFlake(state, ref, ref, ref, *resolvedPath, inputAttrPath);
|
||||
return readFlake(state, ref, ref, ref, *resolvedPath, inputPath);
|
||||
} else {
|
||||
return getFlake(state, ref, useRegistries, flakeCache, inputAttrPath);
|
||||
return getFlake(state, ref, useRegistries, flakeCache, inputPath);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -638,19 +567,19 @@ LockedFlake lockFlake(
|
||||
And the input is not in updateInputs? */
|
||||
std::shared_ptr<LockedNode> oldLock;
|
||||
|
||||
updatesUsed.insert(inputAttrPath);
|
||||
updatesUsed.insert(inputPath);
|
||||
|
||||
if (oldNode && !lockFlags.inputUpdates.count(inputAttrPath))
|
||||
if (oldNode && !lockFlags.inputUpdates.count(inputPath))
|
||||
if (auto oldLock2 = get(oldNode->inputs, id))
|
||||
if (auto oldLock3 = std::get_if<0>(&*oldLock2))
|
||||
oldLock = *oldLock3;
|
||||
|
||||
if (oldLock
|
||||
&& oldLock->originalRef == *input.ref
|
||||
&& oldLock->parentInputAttrPath == overridenParentPath
|
||||
&& oldLock->parentPath == overridenParentPath
|
||||
&& !hasCliOverride)
|
||||
{
|
||||
debug("keeping existing input '%s'", inputAttrPathS);
|
||||
debug("keeping existing input '%s'", inputPathS);
|
||||
|
||||
/* Copy the input from the old lock since its flakeref
|
||||
didn't change and there is no override from a
|
||||
@@ -659,18 +588,18 @@ LockedFlake lockFlake(
|
||||
oldLock->lockedRef,
|
||||
oldLock->originalRef,
|
||||
oldLock->isFlake,
|
||||
oldLock->parentInputAttrPath);
|
||||
oldLock->parentPath);
|
||||
|
||||
node->inputs.insert_or_assign(id, childNode);
|
||||
|
||||
/* If we have this input in updateInputs, then we
|
||||
must fetch the flake to update it. */
|
||||
auto lb = lockFlags.inputUpdates.lower_bound(inputAttrPath);
|
||||
auto lb = lockFlags.inputUpdates.lower_bound(inputPath);
|
||||
|
||||
auto mustRefetch =
|
||||
lb != lockFlags.inputUpdates.end()
|
||||
&& lb->size() > inputAttrPath.size()
|
||||
&& std::equal(inputAttrPath.begin(), inputAttrPath.end(), lb->begin());
|
||||
&& lb->size() > inputPath.size()
|
||||
&& std::equal(inputPath.begin(), inputPath.end(), lb->begin());
|
||||
|
||||
FlakeInputs fakeInputs;
|
||||
|
||||
@@ -689,7 +618,7 @@ LockedFlake lockFlake(
|
||||
if (!trustLock) {
|
||||
// It is possible that the flake has changed,
|
||||
// so we must confirm all the follows that are in the lock file are also in the flake.
|
||||
auto overridePath(inputAttrPath);
|
||||
auto overridePath(inputPath);
|
||||
overridePath.push_back(i.first);
|
||||
auto o = overrides.find(overridePath);
|
||||
// If the override disappeared, we have to refetch the flake,
|
||||
@@ -713,21 +642,21 @@ LockedFlake lockFlake(
|
||||
if (mustRefetch) {
|
||||
auto inputFlake = getInputFlake(oldLock->lockedRef);
|
||||
nodePaths.emplace(childNode, inputFlake.path.parent());
|
||||
computeLocks(inputFlake.inputs, childNode, inputAttrPath, oldLock, followsPrefix,
|
||||
computeLocks(inputFlake.inputs, childNode, inputPath, oldLock, followsPrefix,
|
||||
inputFlake.path, false);
|
||||
} else {
|
||||
computeLocks(fakeInputs, childNode, inputAttrPath, oldLock, followsPrefix, sourcePath, true);
|
||||
computeLocks(fakeInputs, childNode, inputPath, oldLock, followsPrefix, sourcePath, true);
|
||||
}
|
||||
|
||||
} else {
|
||||
/* We need to create a new lock file entry. So fetch
|
||||
this input. */
|
||||
debug("creating new input '%s'", inputAttrPathS);
|
||||
debug("creating new input '%s'", inputPathS);
|
||||
|
||||
if (!lockFlags.allowUnlocked
|
||||
&& !input.ref->input.isLocked()
|
||||
&& !input.ref->input.isRelative())
|
||||
throw Error("cannot update unlocked flake input '%s' in pure mode", inputAttrPathS);
|
||||
throw Error("cannot update unlocked flake input '%s' in pure mode", inputPathS);
|
||||
|
||||
/* Note: in case of an --override-input, we use
|
||||
the *original* ref (input2.ref) for the
|
||||
@@ -736,7 +665,7 @@ LockedFlake lockFlake(
|
||||
nuked the next time we update the lock
|
||||
file. That is, overrides are sticky unless you
|
||||
use --no-write-lock-file. */
|
||||
auto ref = (input2.ref && explicitCliOverrides.contains(inputAttrPath)) ? *input2.ref : *input.ref;
|
||||
auto ref = (input2.ref && explicitCliOverrides.contains(inputPath)) ? *input2.ref : *input.ref;
|
||||
|
||||
if (input.isFlake) {
|
||||
auto inputFlake = getInputFlake(*input.ref);
|
||||
@@ -762,11 +691,11 @@ LockedFlake lockFlake(
|
||||
own lock file. */
|
||||
nodePaths.emplace(childNode, inputFlake.path.parent());
|
||||
computeLocks(
|
||||
inputFlake.inputs, childNode, inputAttrPath,
|
||||
inputFlake.inputs, childNode, inputPath,
|
||||
oldLock
|
||||
? std::dynamic_pointer_cast<const Node>(oldLock)
|
||||
: readLockFile(state.fetchSettings, inputFlake.lockFilePath()).root.get_ptr(),
|
||||
oldLock ? followsPrefix : inputAttrPath,
|
||||
oldLock ? followsPrefix : inputPath,
|
||||
inputFlake.path,
|
||||
false);
|
||||
}
|
||||
@@ -778,13 +707,9 @@ LockedFlake lockFlake(
|
||||
if (auto resolvedPath = resolveRelativePath()) {
|
||||
return {*resolvedPath, *input.ref};
|
||||
} else {
|
||||
auto [accessor, resolvedRef, lockedRef] = fetchOrSubstituteTree(
|
||||
auto [storePath, resolvedRef, lockedRef] = fetchOrSubstituteTree(
|
||||
state, *input.ref, useRegistries, flakeCache);
|
||||
|
||||
// FIXME: allow input to be lazy.
|
||||
auto storePath = copyInputToStore(state, lockedRef.input, input.ref->input, accessor);
|
||||
|
||||
return {state.storePath(storePath), lockedRef};
|
||||
return {state.rootPath(state.store->toRealPath(storePath)), lockedRef};
|
||||
}
|
||||
}();
|
||||
|
||||
@@ -797,7 +722,7 @@ LockedFlake lockFlake(
|
||||
}
|
||||
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "while updating the flake input '%s'", inputAttrPathS);
|
||||
e.addTrace({}, "while updating the flake input '%s'", inputPathS);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -817,11 +742,11 @@ LockedFlake lockFlake(
|
||||
for (auto & i : lockFlags.inputOverrides)
|
||||
if (!overridesUsed.count(i.first))
|
||||
warn("the flag '--override-input %s %s' does not match any input",
|
||||
printInputAttrPath(i.first), i.second);
|
||||
printInputPath(i.first), i.second);
|
||||
|
||||
for (auto & i : lockFlags.inputUpdates)
|
||||
if (!updatesUsed.count(i))
|
||||
warn("'%s' does not match any input of this flake", printInputAttrPath(i));
|
||||
warn("'%s' does not match any input of this flake", printInputPath(i));
|
||||
|
||||
/* Check 'follows' inputs. */
|
||||
newLockFile.check();
|
||||
@@ -921,6 +846,21 @@ LockedFlake lockFlake(
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<StorePath, Path> sourcePathToStorePath(
|
||||
ref<Store> store,
|
||||
const SourcePath & _path)
|
||||
{
|
||||
auto path = _path.path.abs();
|
||||
|
||||
if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>()) {
|
||||
auto realStoreDir = store2->getRealStoreDir();
|
||||
if (isInDir(path, realStoreDir))
|
||||
path = store2->storeDir + path.substr(realStoreDir.size());
|
||||
}
|
||||
|
||||
return store->toStorePath(path);
|
||||
}
|
||||
|
||||
void callFlake(EvalState & state,
|
||||
const LockedFlake & lockedFlake,
|
||||
Value & vRes)
|
||||
@@ -938,7 +878,7 @@ void callFlake(EvalState & state,
|
||||
|
||||
auto lockedNode = node.dynamic_pointer_cast<const LockedNode>();
|
||||
|
||||
auto [storePath, subdir] = state.store->toStorePath(sourcePath.path.abs());
|
||||
auto [storePath, subdir] = sourcePathToStorePath(state.store, sourcePath);
|
||||
|
||||
emitTreeAttrs(
|
||||
state,
|
||||
|
||||
@@ -57,7 +57,7 @@ struct FlakeInput
|
||||
* false = (fetched) static source path
|
||||
*/
|
||||
bool isFlake = true;
|
||||
std::optional<InputAttrPath> follows;
|
||||
std::optional<InputPath> follows;
|
||||
FlakeInputs overrides;
|
||||
};
|
||||
|
||||
@@ -79,37 +79,24 @@ struct Flake
|
||||
* The original flake specification (by the user)
|
||||
*/
|
||||
FlakeRef originalRef;
|
||||
|
||||
/**
|
||||
* registry references and caching resolved to the specific underlying flake
|
||||
*/
|
||||
FlakeRef resolvedRef;
|
||||
|
||||
/**
|
||||
* the specific local store result of invoking the fetcher
|
||||
*/
|
||||
FlakeRef lockedRef;
|
||||
|
||||
/**
|
||||
* The path of `flake.nix`.
|
||||
*/
|
||||
SourcePath path;
|
||||
|
||||
/**
|
||||
* Pretend that `lockedRef` is dirty.
|
||||
* pretend that 'lockedRef' is dirty
|
||||
*/
|
||||
bool forceDirty = false;
|
||||
|
||||
std::optional<std::string> description;
|
||||
|
||||
FlakeInputs inputs;
|
||||
|
||||
/**
|
||||
* Attributes to be retroactively applied to the `self` input
|
||||
* (such as `submodules = true`).
|
||||
*/
|
||||
fetchers::Attrs selfAttrs;
|
||||
|
||||
/**
|
||||
* 'nixConfig' attribute
|
||||
*/
|
||||
@@ -214,13 +201,13 @@ struct LockFlags
|
||||
/**
|
||||
* Flake inputs to be overridden.
|
||||
*/
|
||||
std::map<InputAttrPath, FlakeRef> inputOverrides;
|
||||
std::map<InputPath, FlakeRef> inputOverrides;
|
||||
|
||||
/**
|
||||
* Flake inputs to be updated. This means that any existing lock
|
||||
* for those inputs will be ignored.
|
||||
*/
|
||||
std::set<InputAttrPath> inputUpdates;
|
||||
std::set<InputPath> inputUpdates;
|
||||
};
|
||||
|
||||
LockedFlake lockFlake(
|
||||
@@ -234,6 +221,16 @@ void callFlake(
|
||||
const LockedFlake & lockedFlake,
|
||||
Value & v);
|
||||
|
||||
/**
|
||||
* Map a `SourcePath` to the corresponding store path. This is a
|
||||
* temporary hack to support chroot stores while we don't have full
|
||||
* lazy trees. FIXME: Remove this once we can pass a sourcePath rather
|
||||
* than a storePath to call-flake.nix.
|
||||
*/
|
||||
std::pair<StorePath, Path> sourcePathToStorePath(
|
||||
ref<Store> store,
|
||||
const SourcePath & path);
|
||||
|
||||
}
|
||||
|
||||
void emitTreeAttrs(
|
||||
|
||||
@@ -107,7 +107,7 @@ std::pair<FlakeRef, std::string> parsePathFlakeRefWithFragment(
|
||||
to 'baseDir'). If so, search upward to the root of the
|
||||
repo (i.e. the directory containing .git). */
|
||||
|
||||
path = absPath(path, baseDir, true);
|
||||
path = absPath(path, baseDir);
|
||||
|
||||
if (isFlake) {
|
||||
|
||||
@@ -283,10 +283,10 @@ FlakeRef FlakeRef::fromAttrs(
|
||||
fetchers::maybeGetStrAttr(attrs, "dir").value_or(""));
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, FlakeRef> FlakeRef::lazyFetch(ref<Store> store) const
|
||||
std::pair<StorePath, FlakeRef> FlakeRef::fetchTree(ref<Store> store) const
|
||||
{
|
||||
auto [accessor, lockedInput] = input.getAccessor(store);
|
||||
return {accessor, FlakeRef(std::move(lockedInput), subdir)};
|
||||
auto [storePath, lockedInput] = input.fetchToStore(store);
|
||||
return {std::move(storePath), FlakeRef(std::move(lockedInput), subdir)};
|
||||
}
|
||||
|
||||
std::tuple<FlakeRef, std::string, ExtendedOutputsSpec> parseFlakeRefWithFragmentAndExtendedOutputsSpec(
|
||||
|
||||
@@ -71,7 +71,7 @@ struct FlakeRef
|
||||
const fetchers::Settings & fetchSettings,
|
||||
const fetchers::Attrs & attrs);
|
||||
|
||||
std::pair<ref<SourceAccessor>, FlakeRef> lazyFetch(ref<Store> store) const;
|
||||
std::pair<StorePath, FlakeRef> fetchTree(ref<Store> store) const;
|
||||
};
|
||||
|
||||
std::ostream & operator << (std::ostream & str, const FlakeRef & flakeRef);
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
#include <unordered_set>
|
||||
|
||||
#include "fetch-settings.hh"
|
||||
#include "flake/settings.hh"
|
||||
#include "lockfile.hh"
|
||||
#include "store-api.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
@@ -12,6 +9,8 @@
|
||||
#include <iterator>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "strings.hh"
|
||||
#include "flake/settings.hh"
|
||||
|
||||
namespace nix::flake {
|
||||
|
||||
@@ -44,18 +43,11 @@ LockedNode::LockedNode(
|
||||
: lockedRef(getFlakeRef(fetchSettings, json, "locked", "info")) // FIXME: remove "info"
|
||||
, originalRef(getFlakeRef(fetchSettings, json, "original", nullptr))
|
||||
, isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true)
|
||||
, parentInputAttrPath(json.find("parent") != json.end() ? (std::optional<InputAttrPath>) json["parent"] : std::nullopt)
|
||||
, parentPath(json.find("parent") != json.end() ? (std::optional<InputPath>) json["parent"] : std::nullopt)
|
||||
{
|
||||
if (!lockedRef.input.isLocked() && !lockedRef.input.isRelative()) {
|
||||
if (lockedRef.input.getNarHash())
|
||||
warn(
|
||||
"Lock file entry '%s' is unlocked (e.g. lacks a Git revision) but does have a NAR hash. "
|
||||
"This is deprecated since such inputs are verifiable but may not be reproducible.",
|
||||
lockedRef.to_string());
|
||||
else
|
||||
throw Error("Lock file contains unlocked input '%s'. Use '--allow-dirty-locks' to accept this lock file.",
|
||||
fetchers::attrsToJSON(lockedRef.input.toAttrs()));
|
||||
}
|
||||
if (!lockedRef.input.isConsideredLocked(fetchSettings) && !lockedRef.input.isRelative())
|
||||
throw Error("Lock file contains unlocked input '%s'. Use '--allow-dirty-locks' to accept this lock file.",
|
||||
fetchers::attrsToJSON(lockedRef.input.toAttrs()));
|
||||
|
||||
// For backward compatibility, lock file entries are implicitly final.
|
||||
assert(!lockedRef.input.attrs.contains("__final"));
|
||||
@@ -67,7 +59,7 @@ StorePath LockedNode::computeStorePath(Store & store) const
|
||||
return lockedRef.input.computeStorePath(store);
|
||||
}
|
||||
|
||||
static std::shared_ptr<Node> doFind(const ref<Node> & root, const InputAttrPath & path, std::vector<InputAttrPath> & visited)
|
||||
static std::shared_ptr<Node> doFind(const ref<Node> & root, const InputPath & path, std::vector<InputPath> & visited)
|
||||
{
|
||||
auto pos = root;
|
||||
|
||||
@@ -75,8 +67,8 @@ static std::shared_ptr<Node> doFind(const ref<Node> & root, const InputAttrPath
|
||||
|
||||
if (found != visited.end()) {
|
||||
std::vector<std::string> cycle;
|
||||
std::transform(found, visited.cend(), std::back_inserter(cycle), printInputAttrPath);
|
||||
cycle.push_back(printInputAttrPath(path));
|
||||
std::transform(found, visited.cend(), std::back_inserter(cycle), printInputPath);
|
||||
cycle.push_back(printInputPath(path));
|
||||
throw Error("follow cycle detected: [%s]", concatStringsSep(" -> ", cycle));
|
||||
}
|
||||
visited.push_back(path);
|
||||
@@ -98,9 +90,9 @@ static std::shared_ptr<Node> doFind(const ref<Node> & root, const InputAttrPath
|
||||
return pos;
|
||||
}
|
||||
|
||||
std::shared_ptr<Node> LockFile::findInput(const InputAttrPath & path)
|
||||
std::shared_ptr<Node> LockFile::findInput(const InputPath & path)
|
||||
{
|
||||
std::vector<InputAttrPath> visited;
|
||||
std::vector<InputPath> visited;
|
||||
return doFind(root, path, visited);
|
||||
}
|
||||
|
||||
@@ -123,7 +115,7 @@ LockFile::LockFile(
|
||||
if (jsonNode.find("inputs") == jsonNode.end()) return;
|
||||
for (auto & i : jsonNode["inputs"].items()) {
|
||||
if (i.value().is_array()) { // FIXME: remove, obsolete
|
||||
InputAttrPath path;
|
||||
InputPath path;
|
||||
for (auto & j : i.value())
|
||||
path.push_back(j);
|
||||
node.inputs.insert_or_assign(i.key(), path);
|
||||
@@ -211,8 +203,8 @@ std::pair<nlohmann::json, LockFile::KeyMap> LockFile::toJSON() const
|
||||
n["locked"].erase("__final");
|
||||
if (!lockedNode->isFlake)
|
||||
n["flake"] = false;
|
||||
if (lockedNode->parentInputAttrPath)
|
||||
n["parent"] = *lockedNode->parentInputAttrPath;
|
||||
if (lockedNode->parentPath)
|
||||
n["parent"] = *lockedNode->parentPath;
|
||||
}
|
||||
|
||||
nodes[key] = std::move(n);
|
||||
@@ -256,20 +248,11 @@ std::optional<FlakeRef> LockFile::isUnlocked(const fetchers::Settings & fetchSet
|
||||
|
||||
visit(root);
|
||||
|
||||
/* Return whether the input is either locked, or, if
|
||||
`allow-dirty-locks` is enabled, it has a NAR hash. In the
|
||||
latter case, we can verify the input but we may not be able to
|
||||
fetch it from anywhere. */
|
||||
auto isConsideredLocked = [&](const fetchers::Input & input)
|
||||
{
|
||||
return input.isLocked() || (fetchSettings.allowDirtyLocks && input.getNarHash());
|
||||
};
|
||||
|
||||
for (auto & i : nodes) {
|
||||
if (i == ref<const Node>(root)) continue;
|
||||
auto node = i.dynamic_pointer_cast<const LockedNode>();
|
||||
if (node
|
||||
&& (!isConsideredLocked(node->lockedRef.input)
|
||||
&& (!node->lockedRef.input.isConsideredLocked(fetchSettings)
|
||||
|| !node->lockedRef.input.isFinal())
|
||||
&& !node->lockedRef.input.isRelative())
|
||||
return node->lockedRef;
|
||||
@@ -284,36 +267,36 @@ bool LockFile::operator ==(const LockFile & other) const
|
||||
return toJSON().first == other.toJSON().first;
|
||||
}
|
||||
|
||||
InputAttrPath parseInputAttrPath(std::string_view s)
|
||||
InputPath parseInputPath(std::string_view s)
|
||||
{
|
||||
InputAttrPath path;
|
||||
InputPath path;
|
||||
|
||||
for (auto & elem : tokenizeString<std::vector<std::string>>(s, "/")) {
|
||||
if (!std::regex_match(elem, flakeIdRegex))
|
||||
throw UsageError("invalid flake input attribute path element '%s'", elem);
|
||||
throw UsageError("invalid flake input path element '%s'", elem);
|
||||
path.push_back(elem);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
std::map<InputAttrPath, Node::Edge> LockFile::getAllInputs() const
|
||||
std::map<InputPath, Node::Edge> LockFile::getAllInputs() const
|
||||
{
|
||||
std::set<ref<Node>> done;
|
||||
std::map<InputAttrPath, Node::Edge> res;
|
||||
std::map<InputPath, Node::Edge> res;
|
||||
|
||||
std::function<void(const InputAttrPath & prefix, ref<Node> node)> recurse;
|
||||
std::function<void(const InputPath & prefix, ref<Node> node)> recurse;
|
||||
|
||||
recurse = [&](const InputAttrPath & prefix, ref<Node> node)
|
||||
recurse = [&](const InputPath & prefix, ref<Node> node)
|
||||
{
|
||||
if (!done.insert(node).second) return;
|
||||
|
||||
for (auto &[id, input] : node->inputs) {
|
||||
auto inputAttrPath(prefix);
|
||||
inputAttrPath.push_back(id);
|
||||
res.emplace(inputAttrPath, input);
|
||||
auto inputPath(prefix);
|
||||
inputPath.push_back(id);
|
||||
res.emplace(inputPath, input);
|
||||
if (auto child = std::get_if<0>(&input))
|
||||
recurse(inputAttrPath, *child);
|
||||
recurse(inputPath, *child);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -337,7 +320,7 @@ std::ostream & operator <<(std::ostream & stream, const Node::Edge & edge)
|
||||
if (auto node = std::get_if<0>(&edge))
|
||||
stream << describe((*node)->lockedRef);
|
||||
else if (auto follows = std::get_if<1>(&edge))
|
||||
stream << fmt("follows '%s'", printInputAttrPath(*follows));
|
||||
stream << fmt("follows '%s'", printInputPath(*follows));
|
||||
return stream;
|
||||
}
|
||||
|
||||
@@ -364,15 +347,15 @@ std::string LockFile::diff(const LockFile & oldLocks, const LockFile & newLocks)
|
||||
while (i != oldFlat.end() || j != newFlat.end()) {
|
||||
if (j != newFlat.end() && (i == oldFlat.end() || i->first > j->first)) {
|
||||
res += fmt("• " ANSI_GREEN "Added input '%s':" ANSI_NORMAL "\n %s\n",
|
||||
printInputAttrPath(j->first), j->second);
|
||||
printInputPath(j->first), j->second);
|
||||
++j;
|
||||
} else if (i != oldFlat.end() && (j == newFlat.end() || i->first < j->first)) {
|
||||
res += fmt("• " ANSI_RED "Removed input '%s'" ANSI_NORMAL "\n", printInputAttrPath(i->first));
|
||||
res += fmt("• " ANSI_RED "Removed input '%s'" ANSI_NORMAL "\n", printInputPath(i->first));
|
||||
++i;
|
||||
} else {
|
||||
if (!equals(i->second, j->second)) {
|
||||
res += fmt("• " ANSI_BOLD "Updated input '%s':" ANSI_NORMAL "\n %s\n → %s\n",
|
||||
printInputAttrPath(i->first),
|
||||
printInputPath(i->first),
|
||||
i->second,
|
||||
j->second);
|
||||
}
|
||||
@@ -388,19 +371,19 @@ void LockFile::check()
|
||||
{
|
||||
auto inputs = getAllInputs();
|
||||
|
||||
for (auto & [inputAttrPath, input] : inputs) {
|
||||
for (auto & [inputPath, input] : inputs) {
|
||||
if (auto follows = std::get_if<1>(&input)) {
|
||||
if (!follows->empty() && !findInput(*follows))
|
||||
throw Error("input '%s' follows a non-existent input '%s'",
|
||||
printInputAttrPath(inputAttrPath),
|
||||
printInputAttrPath(*follows));
|
||||
printInputPath(inputPath),
|
||||
printInputPath(*follows));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void check();
|
||||
|
||||
std::string printInputAttrPath(const InputAttrPath & path)
|
||||
std::string printInputPath(const InputPath & path)
|
||||
{
|
||||
return concatStringsSep("/", path);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class StorePath;
|
||||
|
||||
namespace nix::flake {
|
||||
|
||||
typedef std::vector<FlakeId> InputAttrPath;
|
||||
typedef std::vector<FlakeId> InputPath;
|
||||
|
||||
struct LockedNode;
|
||||
|
||||
@@ -23,7 +23,7 @@ struct LockedNode;
|
||||
*/
|
||||
struct Node : std::enable_shared_from_this<Node>
|
||||
{
|
||||
typedef std::variant<ref<LockedNode>, InputAttrPath> Edge;
|
||||
typedef std::variant<ref<LockedNode>, InputPath> Edge;
|
||||
|
||||
std::map<FlakeId, Edge> inputs;
|
||||
|
||||
@@ -40,17 +40,17 @@ struct LockedNode : Node
|
||||
|
||||
/* The node relative to which relative source paths
|
||||
(e.g. 'path:../foo') are interpreted. */
|
||||
std::optional<InputAttrPath> parentInputAttrPath;
|
||||
std::optional<InputPath> parentPath;
|
||||
|
||||
LockedNode(
|
||||
const FlakeRef & lockedRef,
|
||||
const FlakeRef & originalRef,
|
||||
bool isFlake = true,
|
||||
std::optional<InputAttrPath> parentInputAttrPath = {})
|
||||
: lockedRef(std::move(lockedRef))
|
||||
, originalRef(std::move(originalRef))
|
||||
std::optional<InputPath> parentPath = {})
|
||||
: lockedRef(lockedRef)
|
||||
, originalRef(originalRef)
|
||||
, isFlake(isFlake)
|
||||
, parentInputAttrPath(std::move(parentInputAttrPath))
|
||||
, parentPath(parentPath)
|
||||
{ }
|
||||
|
||||
LockedNode(
|
||||
@@ -83,9 +83,9 @@ struct LockFile
|
||||
|
||||
bool operator ==(const LockFile & other) const;
|
||||
|
||||
std::shared_ptr<Node> findInput(const InputAttrPath & path);
|
||||
std::shared_ptr<Node> findInput(const InputPath & path);
|
||||
|
||||
std::map<InputAttrPath, Node::Edge> getAllInputs() const;
|
||||
std::map<InputPath, Node::Edge> getAllInputs() const;
|
||||
|
||||
static std::string diff(const LockFile & oldLocks, const LockFile & newLocks);
|
||||
|
||||
@@ -97,8 +97,8 @@ struct LockFile
|
||||
|
||||
std::ostream & operator <<(std::ostream & stream, const LockFile & lockFile);
|
||||
|
||||
InputAttrPath parseInputAttrPath(std::string_view s);
|
||||
InputPath parseInputPath(std::string_view s);
|
||||
|
||||
std::string printInputAttrPath(const InputAttrPath & path);
|
||||
std::string printInputPath(const InputPath & path);
|
||||
|
||||
}
|
||||
|
||||
@@ -40,6 +40,14 @@ mkMesonLibrary (finalAttrs: {
|
||||
nlohmann_json
|
||||
];
|
||||
|
||||
preConfigure =
|
||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
||||
# Do the meson utils, without modification.
|
||||
''
|
||||
chmod u+w ./.version
|
||||
echo ${version} > ../../.version
|
||||
'';
|
||||
|
||||
meta = {
|
||||
platforms = lib.platforms.unix ++ lib.platforms.windows;
|
||||
};
|
||||
|
||||
@@ -40,6 +40,14 @@ mkMesonLibrary (finalAttrs: {
|
||||
nix-main
|
||||
];
|
||||
|
||||
preConfigure =
|
||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
||||
# Do the meson utils, without modification.
|
||||
''
|
||||
chmod u+w ./.version
|
||||
echo ${version} > ../../.version
|
||||
'';
|
||||
|
||||
mesonFlags = [
|
||||
];
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@ namespace nix {
|
||||
|
||||
LogFormat defaultLogFormat = LogFormat::raw;
|
||||
|
||||
LogFormat parseLogFormat(const std::string & logFormatStr)
|
||||
{
|
||||
LogFormat parseLogFormat(const std::string & logFormatStr) {
|
||||
if (logFormatStr == "raw" || getEnv("NIX_GET_COMPLETIONS"))
|
||||
return LogFormat::raw;
|
||||
else if (logFormatStr == "raw-with-logs")
|
||||
@@ -21,15 +20,14 @@ LogFormat parseLogFormat(const std::string & logFormatStr)
|
||||
throw Error("option 'log-format' has an invalid value '%s'", logFormatStr);
|
||||
}
|
||||
|
||||
std::unique_ptr<Logger> makeDefaultLogger()
|
||||
{
|
||||
Logger * makeDefaultLogger() {
|
||||
switch (defaultLogFormat) {
|
||||
case LogFormat::raw:
|
||||
return makeSimpleLogger(false);
|
||||
case LogFormat::rawWithLogs:
|
||||
return makeSimpleLogger(true);
|
||||
case LogFormat::internalJSON:
|
||||
return makeJSONLogger(getStandardError());
|
||||
return makeJSONLogger(*makeSimpleLogger(true));
|
||||
case LogFormat::bar:
|
||||
return makeProgressBar();
|
||||
case LogFormat::barWithLogs: {
|
||||
@@ -42,19 +40,16 @@ std::unique_ptr<Logger> makeDefaultLogger()
|
||||
}
|
||||
}
|
||||
|
||||
void setLogFormat(const std::string & logFormatStr)
|
||||
{
|
||||
void setLogFormat(const std::string & logFormatStr) {
|
||||
setLogFormat(parseLogFormat(logFormatStr));
|
||||
}
|
||||
|
||||
void setLogFormat(const LogFormat & logFormat)
|
||||
{
|
||||
void setLogFormat(const LogFormat & logFormat) {
|
||||
defaultLogFormat = logFormat;
|
||||
createDefaultLogger();
|
||||
}
|
||||
|
||||
void createDefaultLogger()
|
||||
{
|
||||
void createDefaultLogger() {
|
||||
logger = makeDefaultLogger();
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,14 @@ mkMesonLibrary (finalAttrs: {
|
||||
openssl
|
||||
];
|
||||
|
||||
preConfigure =
|
||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
||||
# Do the meson utils, without modification.
|
||||
''
|
||||
chmod u+w ./.version
|
||||
echo ${version} > ../../.version
|
||||
'';
|
||||
|
||||
meta = {
|
||||
platforms = lib.platforms.unix ++ lib.platforms.windows;
|
||||
};
|
||||
|
||||
@@ -117,15 +117,13 @@ public:
|
||||
{
|
||||
{
|
||||
auto state(state_.lock());
|
||||
if (state->active) {
|
||||
state->active = false;
|
||||
writeToStderr("\r\e[K");
|
||||
updateCV.notify_one();
|
||||
quitCV.notify_one();
|
||||
}
|
||||
if (!state->active) return;
|
||||
state->active = false;
|
||||
writeToStderr("\r\e[K");
|
||||
updateCV.notify_one();
|
||||
quitCV.notify_one();
|
||||
}
|
||||
if (updateThread.joinable())
|
||||
updateThread.join();
|
||||
updateThread.join();
|
||||
}
|
||||
|
||||
void pause() override {
|
||||
@@ -555,9 +553,9 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<Logger> makeProgressBar()
|
||||
Logger * makeProgressBar()
|
||||
{
|
||||
return std::make_unique<ProgressBar>(isTTY());
|
||||
return new ProgressBar(isTTY());
|
||||
}
|
||||
|
||||
void startProgressBar()
|
||||
@@ -567,8 +565,9 @@ void startProgressBar()
|
||||
|
||||
void stopProgressBar()
|
||||
{
|
||||
if (auto progressBar = dynamic_cast<ProgressBar *>(logger.get()))
|
||||
progressBar->stop();
|
||||
auto progressBar = dynamic_cast<ProgressBar *>(logger);
|
||||
if (progressBar) progressBar->stop();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
std::unique_ptr<Logger> makeProgressBar();
|
||||
Logger * makeProgressBar();
|
||||
|
||||
void startProgressBar();
|
||||
|
||||
|
||||
@@ -315,6 +315,20 @@ void printVersion(const std::string & programName)
|
||||
throw Exit();
|
||||
}
|
||||
|
||||
|
||||
void showManPage(const std::string & name)
|
||||
{
|
||||
restoreProcessContext();
|
||||
setEnv("MANPATH", settings.nixManDir.c_str());
|
||||
execlp("man", "man", name.c_str(), nullptr);
|
||||
if (errno == ENOENT) {
|
||||
// Not SysError because we don't want to suffix the errno, aka No such file or directory.
|
||||
throw Error("The '%1%' command was not found, but it is needed for '%2%' and some other '%3%' commands' help text. Perhaps you could install the '%1%' command?", "man", name.c_str(), "nix-*");
|
||||
}
|
||||
throw SysError("command 'man %1%' failed", name.c_str());
|
||||
}
|
||||
|
||||
|
||||
int handleExceptions(const std::string & programName, std::function<void()> fun)
|
||||
{
|
||||
ReceiveInterrupts receiveInterrupts; // FIXME: need better place for this
|
||||
|
||||
@@ -70,6 +70,11 @@ struct LegacyArgs : public MixCommonArgs, public RootArgs
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Show the manual page for the specified program.
|
||||
*/
|
||||
void showManPage(const std::string & name);
|
||||
|
||||
/**
|
||||
* The constructor of this class starts a pager if standard output is a
|
||||
* terminal and $PAGER is set. Standard output is redirected to the
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user