Compare commits

..

55 Commits

Author SHA1 Message Date
Sergei Zimmerman
b1c0f416cb libutil: Make computeClosure async
This makes computeFSClosure much faster in practice, because the graph is now traversed in an async BFS manner, while
previously it serialised on each single path info query. For something like

rm ~/.cache/nix/binary-cache-v7.sqlite && nix path-info --store https://cache.nixos.org --recursive /nix/store/7zz3zmv2a0ssmgqlfhy4rsb6ii6z475a-stdenv-linux.drv

The difference is huge:

(Before)

Command being timed: "nix path-info --store https://cache.nixos.org --recursive /nix/store/7zz3zmv2a0ssmgqlfhy4rsb6ii6z475a-stdenv-linux.drv"
        User time (seconds): 0.07
        System time (seconds): 0.06
        Percent of CPU this job got: 1%
        Elapsed (wall clock) time (h:mm:ss or m:ss): 0:08.88

(After)

Command being timed: "build/src/nix/nix path-info --store https://cache.nixos.org --recursive /nix/store/7zz3zmv2a0ssmgqlfhy4rsb6ii6z475a-stdenv-linux.drv"
        User time (seconds): 0.07
        System time (seconds): 0.04
        Percent of CPU this job got: 22%
        Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.49

Basically 20x speedup with just processing stuff in an async manner.

Note that for now we have rather ad-hoc event loop just for
computeClosure. This seems perfectly fine for now, but in the future
we could extended it and possibly have a global even loop with multiple
threads handling it.

For now we have basic rate limiting <= 64 coroutines in flight which is
smaller than CURLMOPT_MAX_CONCURRENT_STREAMS (100 by default).
2025-12-19 07:28:44 +03:00
Sergei Zimmerman
8104858643 libstore/filetransfer: Fix double callback on enqueueFileTransfer that is shutting down 2025-12-19 07:25:18 +03:00
Sergei Zimmerman
a6c1d5637a libstore/filetransfer: Remove unused using namespace 2025-12-19 05:20:45 +03:00
Sergei Zimmerman
27006cc8a9 Merge pull request #14832 from NixOS/o-tmpfile-fallback
libutil: Gracefully fall back from unsupported O_TMPFILE
2025-12-18 20:39:56 +00:00
Sergei Zimmerman
06f21596a0 libutil: Gracefully fall back from unsupported O_TMPFILE
Some filesystems, notably most FUSE-based ones and some top-level overlaysfs
ones do not support this and we need a graceful fallback.
2025-12-18 22:12:14 +03:00
Jörg Thalheim
9254fab407 Merge pull request #14828 from Zaczero/zaczero/libstore-registerValidPaths
libstore: reuse parsed derivations in registerValidPaths
2025-12-18 12:59:43 +00:00
Jörg Thalheim
994324feda libstore-tests: reduce registerValidPaths benchmark to single test case
Testing with 10 derivations is sufficient to verify performance
characteristics. The larger test cases (50, 200) don't provide
additional insight and slow down the benchmark unnecessarily.
2025-12-18 09:46:03 +01:00
Jörg Thalheim
1f739961e5 libstore: simplify registerValidPaths by removing redundant checkInvariants loop
The separate checkInvariants loop after addValidPath was added in 2014
(d210cdc43) to work around an assertion failure:

  nix-store: derivations.cc:242: Assertion 'store.isValidPath(i->first)' failed.

At that time, hashDerivationModulo() contained assert(store.isValidPath(...))
which required input derivations to be registered as valid in the database
before computing their hash. The workaround was to:
1. Call addValidPath with checkOutputs=false
2. Add all references to the database
3. Run checkInvariants in a separate loop after paths were valid

In 2020 (bccff827d), the isValidPath assertion was removed to fix a
deadlock in IFD through the daemon (issue #4235). The fix changed
hashDerivationModulo to use readInvalidDerivation, which reads directly
from the filesystem without requiring database validity.

This made the separate checkInvariants loop unnecessary, but nobody
noticed the code could be simplified. The comment "We can't do this in
addValidPath() above, because the references might not be valid yet"
became stale.

Now we simply call addValidPath() with the default checkOutputs=true,
which runs checkInvariants internally using the already-parsed
derivation. This commit eliminates the separate loop over derivations.
2025-12-18 09:34:17 +01:00
Kamil Monicz
cccfa385e6 libstore: reuse parsed derivations in registerValidPaths
- LocalStore::registerValidPaths() parsed derivations twice: once in addValidPath() and again when calling checkInvariants(), despite already having loaded the derivation.
- Plumb the parsed Derivation out of addValidPath() and reuse it for the invariant check pass, falling back to re-parsing only when a derivation wasn’t newly registered in this call.
- BM_RegisterValidPathsDerivations/200_mean runs 32% faster
2025-12-18 06:00:38 +01:00
Kamil Monicz
9d2100a165 libstore-tests: benchmark registerValidPaths on derivations
- Add a focused nix-store-benchmarks benchmark that registers many derivation paths into a temporary local store root
2025-12-18 06:00:31 +01:00
John Ericson
4769f3c0b2 Merge pull request #14824 from roberth/issue-14776
doc: drop rsync dependency from manual build
2025-12-18 03:18:45 +00:00
Robert Hensing
ab354dc8f6 doc: drop rsync dependency from manual build
rsync was only used to copy source files while following symlinks.
Replace with tar --dereference, which serves the same purpose.
Tried plain cp but couldn't get it to work reliably. tar is already
a test dependency.

Add tests/functional/derivation to fileset to include the symlink
targets.

Fixes #14776
2025-12-18 03:41:45 +01:00
John Ericson
188cb798ad Merge pull request #14817 from NixOS/fix-socket-mingw
Windows fixes
2025-12-18 00:30:19 +00:00
John Ericson
1aa7ab0dcf Merge pull request #14819 from NixOS/mingw-fixes-more
Assorted windows fixes for libutil, HANDLEs and path handling
2025-12-17 22:28:27 +00:00
John Ericson
208ed3c538 Fix select / fdset usage on Windows
These functions use `SOCKET` not `int`, despite them being unix
functions.
2025-12-17 16:55:04 -05:00
John Ericson
b6add8dcc6 Merge pull request #14818 from NixOS/fix-windows-dev-shell
Fix up dev shell in a few ways
2025-12-17 21:52:56 +00:00
John Ericson
79750a3ccc Split out socket.hh from unix-domain-socket.hh
There are other types of sockets.
2025-12-17 16:51:01 -05:00
John Ericson
30cd9e43e1 Fix windows build of new source accessor test
We don't have the dirFd on window at this time.
2025-12-17 16:51:01 -05:00
Sergei Zimmerman
0695630eb5 libutil: Fix FdSource::read on Windows
We need to signal the EOF condition, otherwise the read never terminates.
2025-12-18 00:30:07 +03:00
Sergei Zimmerman
89dc57f6aa libutil: Implement HANDLE-based lseek for Windows
For windows we should live fully in the HANDLE land instead
of converting back-n-forth (which sometimes is destructive).
Using native API is much better for this.
2025-12-18 00:30:06 +03:00
Sergei Zimmerman
f274a7273a libutil: Implement deletePath on windows via std::filesystem::remove_all
It doesn't track the number of bytes deleted, but since this code is
security critical also we can split unix and windows implementations.
If the need arises we can implement a smarter recursive deletion function
ourselves in the future.

Review with --color-moved.
2025-12-18 00:30:05 +03:00
Sergei Zimmerman
675656ffba libutil: Fix canonPath, makeTempPath and createTempDir on windows
This at least makes canonPath not consider the drive letter as a path
component. There still some issues with it on windows, but at least
this gets us through some of the libutil-tests.

Also since we don't want to change which env variables nix considers
we don't use std::filesystem::temp_directory_path and implement the
windows version directly.
2025-12-18 00:30:04 +03:00
John Ericson
a5edc2d921 Fix up dev shell in a few ways
- Skip packages that don't build for Windows when building for windows
- Automatically disable kaitai / json schema, fixing todo
- Skip native build of Nix for manual
2025-12-17 15:41:47 -05:00
John Ericson
2f092870e4 Merge pull request #14648 from obsidiansystems/goal-division-of-labor
`DrvOutputSubstitutionGoal`: Don't actually fetch any store objects
2025-12-17 03:10:18 +00:00
John Ericson
b39da9c0c2 Merge pull request #14815 from NixOS/source-accessor-tests
libutil-tests: Add tests for makeFSSourceAccessor
2025-12-17 02:27:45 +00:00
John Ericson
f536b25367 Merge pull request #14247 from obsidiansystems/no-dependent-realisations
Remove dependent realisations
2025-12-17 02:14:22 +00:00
Sergei Zimmerman
017fae3f14 libutil-tests: Add tests for makeFSSourceAccessor
Should be pretty self-explanatory. We didn't really have unit tests
for the filesystem source accessor. Now we do and this will be immensely
useful for implementing a unix-only smarter accessor that doesn't suffer
from TOCTOU on symlinks.
2025-12-17 04:42:31 +03:00
John Ericson
018d6462de DrvOutputSubstitutionGoal: Don't actually fetch any store objects
We now have a nice separation of concerns: `DrvOutputSubstitutionGoal`
is *just* for getting realisations, and `PathSubstitutionGoal` is just
for fetching store objects.

The fetching of store objects that this used to do is now moved to the
caller.
2025-12-16 20:18:53 -05:00
John Ericson
4a5d960952 Remove dependent realisations
This progress on #11896. It introduces some issues temporarily which
will be fixed when #11928 is fixed.

The SQL tables are left in place because there is no point inducing a
migration now, when we will be immediately landing more changes after
this that also require schema changes. They will simply be ignored by in
this commit, and so all data will be preserved.
2025-12-16 19:56:19 -05:00
John Ericson
8cf8a9151a Merge pull request #14814 from NixOS/suggestions-compression-algo-enum
libutil: Add CompressionAlgo enum, add Suggestions to UnknownCompress…
2025-12-17 00:27:36 +00:00
Sergei Zimmerman
4060ec3a8c libutil: Add CompressionAlgo enum, add Suggestions to UnknownCompressionMethod exception
Error messages now include suggestions like:

error: unknown compression method 'bzip'
       Did you mean one of bzip2, gzip, lzip, grzip or lrzip?

Also a bit of progress on making the compression code use less stringly
typed compression type, which is good because it's easy to confuse
which strings are accepted where (e.g. Content-Encoding should be able
to accept x-gzip, but it shouldn't be exposed in NAR decompression and
so on). An enum cleanly separates the concerns of parsing strings / handling
libarchive write/read filters.
2025-12-17 02:39:44 +03:00
Sergei Zimmerman
e0830681e2 Merge pull request #14552 from hsjobeki/docs-sort
docs: add explanation to sort primop
2025-12-16 20:31:12 +00:00
Jörg Thalheim
9f2795e588 Merge pull request #14805 from NixOS/dependabot/github_actions/cachix/install-nix-action-31.9.0
build(deps): bump cachix/install-nix-action from 31.8.4 to 31.9.0
2025-12-16 19:58:01 +00:00
Jörg Thalheim
12cee327a0 Merge pull request #14806 from NixOS/dependabot/github_actions/korthout/backport-action-4.0.1
build(deps): bump korthout/backport-action from 3.4.1 to 4.0.1
2025-12-16 19:56:42 +00:00
Jörg Thalheim
3b73dcba39 Merge pull request #14807 from NixOS/dependabot/github_actions/actions/upload-artifact-6
build(deps): bump actions/upload-artifact from 5 to 6
2025-12-16 19:56:23 +00:00
Jörg Thalheim
dfad4b1403 Merge pull request #14808 from NixOS/dependabot/github_actions/actions/download-artifact-7
build(deps): bump actions/download-artifact from 6 to 7
2025-12-16 19:56:06 +00:00
John Ericson
5f69fd3e8d Merge pull request #14804 from Eveeifyeve/windows-symlink-issue-fix
manual: Add note on windows to use a git setting to avoid symlink issues in building
2025-12-16 04:21:52 +00:00
John Ericson
47416968d2 Merge pull request #14793 from obsidiansystems/test-11928
Create substitution unit tests
2025-12-16 03:30:40 +00:00
John Ericson
ce38abb697 Merge pull request #14755 from obsidiansystems/warn-non-object-exportReferencesGraph
Add warning for non-JSON-object `exportReferencesGraph`
2025-12-16 03:30:25 +00:00
Sergei Zimmerman
a38fc659cc Merge pull request #14791 from NixOS/fix-special-member-functions-a-lot
treewide: Follow rule of five
2025-12-16 00:09:06 +00:00
eveeifyeve
d5d7594029 manual: Add note on windows to use a git setting to avoid symlink issues in building
Ref #14787

This really doesn't really fixes the problem of the symlink, but it
solves the progress of getting windows working.

TODO: find out if it's a bug from meason & make a feature request to
avoid symlinks or generate symlinks upon build and git ignore, but still
goes back to the issue of is this a bug or do we need to make a feature
requests.

Co-authored-by: John Ericson <git@JohnEricson.me>
2025-12-16 09:09:33 +11:00
dependabot[bot]
1fc5648204 build(deps): bump actions/download-artifact from 6 to 7
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 22:01:01 +00:00
dependabot[bot]
d7e0bcaa51 build(deps): bump actions/upload-artifact from 5 to 6
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 22:00:57 +00:00
dependabot[bot]
4227d24bc3 build(deps): bump korthout/backport-action from 3.4.1 to 4.0.1
Bumps [korthout/backport-action](https://github.com/korthout/backport-action) from 3.4.1 to 4.0.1.
- [Release notes](https://github.com/korthout/backport-action/releases)
- [Commits](d07416681c...c656f5d585)

---
updated-dependencies:
- dependency-name: korthout/backport-action
  dependency-version: 4.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 22:00:53 +00:00
dependabot[bot]
7720dad11f build(deps): bump cachix/install-nix-action from 31.8.4 to 31.9.0
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 31.8.4 to 31.9.0.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Changelog](https://github.com/cachix/install-nix-action/blob/master/RELEASE.md)
- [Commits](0b0e072294...4e002c8ec8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 22:00:48 +00:00
John Ericson
1c63cf4001 Add warning for non-JSON-object exportReferencesGraph
This will help users debug their mistakes.
2025-12-15 15:53:19 -05:00
John Ericson
e145632aef Add unit test for double floating drv substitution
This test will be updated to track progress on #11928 --- it shows the
issue currently.
2025-12-15 01:49:58 -05:00
John Ericson
5cdf2a19bd Add basic floating CA drv output subst unit test 2025-12-15 01:37:05 -05:00
John Ericson
bb74677b08 Create basic substitution unit tests
- substitute single store object

- substitute single store object with single dep
2025-12-15 01:18:34 -05:00
John Ericson
3cfac9b079 Allow Worker instances to be locally configured with substituters
This will be useful for unit tests.
2025-12-15 00:53:45 -05:00
Sergei Zimmerman
198628790b libutil: Also fix AutoUnmount special member functions 2025-12-15 01:35:21 +03:00
Sergei Zimmerman
54d2268d84 treewide: Follow rule of five
Good to explicitly declare things to not accidentally do twice the work by
preventing that kind of misuse.
This is essentially just cppcoreguidelines-special-member-functions lint
in clang-tidy.
2025-12-15 01:35:20 +03:00
Sergei Zimmerman
8c74aadbf7 libutil: Fix AutoRemoveJail special member functions
These can't be copied and moving requires special logic too.
2025-12-15 01:07:22 +03:00
John Ericson
3a62be7227 Fix path locks move/assignment
No copying allowed

Co-authored-by: Sergei Zimmerman <sergei@zimmerman.foo>
2025-12-15 00:36:54 +03:00
Johannes Kirschbauer
af6326dfa4 docs: add explanation to sort primop 2025-12-05 10:03:43 +01:00
90 changed files with 2195 additions and 914 deletions

View File

@@ -26,7 +26,7 @@ jobs:
# required to find all branches
fetch-depth: 0
- name: Create backport PRs
uses: korthout/backport-action@d07416681cab29bf2661702f925f020aaa962997 # v3.4.1
uses: korthout/backport-action@c656f5d5851037b2b38fb5db2691a03fa229e3b2 # v4.0.1
id: backport
with:
# Config README: https://github.com/korthout/backport-action#backport-action

View File

@@ -125,13 +125,13 @@ jobs:
cat coverage-reports/index.txt >> $GITHUB_STEP_SUMMARY
if: ${{ matrix.instrumented }}
- name: Upload coverage reports
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: coverage-reports
path: coverage-reports/
if: ${{ matrix.instrumented }}
- name: Upload installer tarball
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: installer-${{matrix.os}}
path: out/*
@@ -164,7 +164,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Download installer tarball
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
with:
name: installer-${{matrix.os}}
path: out
@@ -174,7 +174,7 @@ jobs:
echo "installer-url=file://$GITHUB_WORKSPACE/out" >> "$GITHUB_OUTPUT"
TARBALL_PATH="$(find "$GITHUB_WORKSPACE/out" -name 'nix*.tar.xz' -print | head -n 1)"
echo "tarball-path=file://$TARBALL_PATH" >> "$GITHUB_OUTPUT"
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
- uses: cachix/install-nix-action@4e002c8ec80594ecd40e759629461e26c8abed15 # v31.9.0
if: ${{ !matrix.experimental-installer }}
with:
install_url: ${{ format('{0}/install', steps.installer-tarball-url.outputs.installer-url) }}

View File

@@ -26,7 +26,6 @@ bash = find_program('bash', native : true)
# HTML manual dependencies (conditional)
if get_option('html-manual')
mdbook = find_program('mdbook', native : true)
rsync = find_program('rsync', required : true, native : true)
endif
pymod = import('python')
@@ -126,7 +125,12 @@ if get_option('html-manual')
@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
@4@ -r -L --exclude='*.drv' --include='*.md' @CURRENT_SOURCE_DIR@/ @2@/
# Copy source to build directory, excluding the build directory itself
# (which is present when built as an individual component).
# Use tar with --dereference to copy symlink targets (e.g., JSON examples from tests).
(cd @CURRENT_SOURCE_DIR@ && find . -mindepth 1 -maxdepth 1 ! -name build | tar -c --dereference -T - -f -) | (cd @2@ && tar -xf -)
chmod -R u+w @2@
find @2@ -name '*.drv' -delete
(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
mv @2@/html @2@/manual
@@ -138,7 +142,6 @@ if get_option('html-manual')
mdbook.full_path(),
meson.current_build_dir(),
meson.project_version(),
rsync.full_path(),
),
],
input : [

View File

@@ -10,7 +10,6 @@
mdbook,
jq,
python3,
rsync,
nix-cli,
changelog-d,
json-schema-for-humans,
@@ -54,6 +53,8 @@ mkMesonDerivation (finalAttrs: {
../../src/libstore-tests/data/nar-info
../../src/libstore-tests/data/build-result
../../src/libstore-tests/data/dummy-store
# For derivation examples referenced by symlinks in doc/manual/source/protocols/json/schema/
../../tests/functional/derivation
# Too many different types of files to filter for now
../../doc/manual
./.
@@ -90,7 +91,6 @@ mkMesonDerivation (finalAttrs: {
]
++ lib.optionals buildHtmlManual [
mdbook
rsync
json-schema-for-humans
]
++ lib.optionals (!officialRelease && buildHtmlManual) [

View File

@@ -3,6 +3,10 @@
This section provides some notes on how to start hacking on Nix.
To get the latest version of Nix from GitHub:
> **Note**
>
> When checking out the repo on Windows, make sure you have the git setting `core.symlinks` enabled, before cloning, as there are symlinks in the repo.
```console
$ git clone https://github.com/NixOS/nix.git
$ cd nix

View File

@@ -1,27 +1,21 @@
{{#include build-trace-entry-v1-fixed.md}}
{{#include build-trace-entry-v2-fixed.md}}
## Examples
### Simple build trace entry
```json
{{#include schema/build-trace-entry-v1/simple.json}}
```
### Build trace entry with dependencies
```json
{{#include schema/build-trace-entry-v1/with-dependent-realisations.json}}
{{#include schema/build-trace-entry-v2/simple.json}}
```
### Build trace entry with signature
```json
{{#include schema/build-trace-entry-v1/with-signature.json}}
{{#include schema/build-trace-entry-v2/with-signature.json}}
```
<!--
## Raw Schema
[JSON Schema for Build Trace Entry v1](schema/build-trace-entry-v1.json)
-->
[JSON Schema for Build Trace Entry v1](schema/build-trace-entry-v2.json)
-->

View File

@@ -17,7 +17,7 @@ schemas = [
'derivation-v4',
'derivation-options-v1',
'deriving-path-v1',
'build-trace-entry-v1',
'build-trace-entry-v2',
'build-result-v1',
'store-v1',
]

View File

@@ -83,7 +83,7 @@ properties:
description: |
A mapping from output names to their build trace entries.
additionalProperties:
"$ref": "build-trace-entry-v1.yaml"
"$ref": "build-trace-entry-v2.yaml"
failure:
type: object

View File

@@ -1,5 +1,5 @@
"$schema": "http://json-schema.org/draft-04/schema"
"$id": "https://nix.dev/manual/nix/latest/protocols/json/schema/build-trace-entry-v1.json"
"$id": "https://nix.dev/manual/nix/latest/protocols/json/schema/build-trace-entry-v2.json"
title: Build Trace Entry
description: |
A record of a successful build outcome for a specific derivation output.
@@ -11,10 +11,17 @@ description: |
> This JSON format is currently
> [**experimental**](@docroot@/development/experimental-features.md#xp-feature-ca-derivations)
> and subject to change.
Verision history:
- Version 1: Original format
- Version 2: Remove `dependentRealisations`
type: object
required:
- id
- outPath
- dependentRealisations
- signatures
allOf:
- "$ref": "#/$defs/key"
@@ -22,9 +29,11 @@ allOf:
properties:
id: {}
outPath: {}
dependentRealisations: {}
signatures: {}
additionalProperties: false
additionalProperties:
dependentRealisations:
description: deprecated field
type: object
"$defs":
key:
@@ -60,7 +69,6 @@ additionalProperties: false
type: object
required:
- outPath
- dependentRealisations
- signatures
properties:
outPath:
@@ -69,19 +77,6 @@ additionalProperties: false
description: |
The path to the store object that resulted from building this derivation for the given output name.
dependentRealisations:
type: object
title: Underlying Base Build Trace
description: |
This is for [*derived*](@docroot@/store/build-trace.md#derived) build trace entries to ensure coherence.
Keys are derivation output IDs (same format as the main `id` field).
Values are the store paths that those dependencies resolved to.
As described in the linked section on derived build trace traces, derived build trace entries must be kept in addition and not instead of the underlying base build entries.
This is the set of base build trace entries that this derived build trace is derived from.
(The set is also a map since this miniature base build trace must be coherent, mapping each key to a single value.)
patternProperties:
"^sha256:[0-9a-f]{64}![a-zA-Z_][a-zA-Z0-9_-]*$":
"$ref": "store-path-v1.yaml"

View File

@@ -70,7 +70,7 @@ properties:
"^[A-Za-z0-9+/]{43}=$":
type: object
additionalProperties:
"$ref": "./build-trace-entry-v1.yaml#/$defs/value"
"$ref": "./build-trace-entry-v2.yaml#/$defs/value"
additionalProperties: false
"$defs":

View File

@@ -148,6 +148,15 @@ pkgs.nixComponents2.nix-util.overrideAttrs (
isInternal =
dep: internalDrvs ? ${builtins.unsafeDiscardStringContext dep.drvPath or "_non-existent_"};
activeComponentNames = lib.listToAttrs (
map (c: {
name = c.pname or c.name;
value = null;
}) activeComponents
);
isActiveComponent = name: activeComponentNames ? ${name};
in
{
pname = "shell-for-nix";
@@ -190,27 +199,19 @@ pkgs.nixComponents2.nix-util.overrideAttrs (
}
);
small =
(finalAttrs.finalPackage.withActiveComponents (
c:
lib.intersectAttrs (lib.genAttrs [
"nix-cli"
"nix-util-tests"
"nix-store-tests"
"nix-expr-tests"
"nix-fetchers-tests"
"nix-flake-tests"
"nix-functional-tests"
"nix-perl-bindings"
] (_: null)) c
)).overrideAttrs
(o: {
mesonFlags = o.mesonFlags ++ [
# TODO: infer from activeComponents or vice versa
"-Dkaitai-struct-checks=false"
"-Djson-schema-checks=false"
];
});
small = finalAttrs.finalPackage.withActiveComponents (
c:
lib.intersectAttrs (lib.genAttrs [
"nix-cli"
"nix-util-tests"
"nix-store-tests"
"nix-expr-tests"
"nix-fetchers-tests"
"nix-flake-tests"
"nix-functional-tests"
"nix-perl-bindings"
] (_: null)) c
);
};
# Remove the version suffix to avoid unnecessary attempts to substitute in nix develop
@@ -278,21 +279,33 @@ pkgs.nixComponents2.nix-util.overrideAttrs (
dontUseCmakeConfigure = true;
mesonFlags =
map (transformFlag "libutil") (ignoreCrossFile pkgs.nixComponents2.nix-util.mesonFlags)
++ map (transformFlag "libstore") (ignoreCrossFile pkgs.nixComponents2.nix-store.mesonFlags)
++ map (transformFlag "libfetchers") (ignoreCrossFile pkgs.nixComponents2.nix-fetchers.mesonFlags)
++ lib.optionals havePerl (
map (transformFlag "perl") (ignoreCrossFile pkgs.nixComponents2.nix-perl-bindings.mesonFlags)
)
++ map (transformFlag "libexpr") (ignoreCrossFile pkgs.nixComponents2.nix-expr.mesonFlags)
++ map (transformFlag "libcmd") (ignoreCrossFile pkgs.nixComponents2.nix-cmd.mesonFlags);
mesonFlags = [
(lib.mesonBool "kaitai-struct-checks" (isActiveComponent "nix-kaitai-struct-checks"))
(lib.mesonBool "json-schema-checks" (isActiveComponent "nix-json-schema-checks"))
]
++ map (transformFlag "libutil") (ignoreCrossFile pkgs.nixComponents2.nix-util.mesonFlags)
++ map (transformFlag "libstore") (ignoreCrossFile pkgs.nixComponents2.nix-store.mesonFlags)
++ map (transformFlag "libfetchers") (ignoreCrossFile pkgs.nixComponents2.nix-fetchers.mesonFlags)
++ lib.optionals havePerl (
map (transformFlag "perl") (ignoreCrossFile pkgs.nixComponents2.nix-perl-bindings.mesonFlags)
)
++ map (transformFlag "libexpr") (ignoreCrossFile pkgs.nixComponents2.nix-expr.mesonFlags)
++ map (transformFlag "libcmd") (ignoreCrossFile pkgs.nixComponents2.nix-cmd.mesonFlags);
nativeBuildInputs =
let
inputs =
dedupByString (v: "${v}") (
lib.filter (x: !isInternal x) (lib.lists.concatMap (c: c.nativeBuildInputs) activeComponents)
lib.filter (x: !isInternal x) (
lib.lists.concatMap (
# Nix manual has a build-time dependency on nix, but we
# don't want to do a native build just to enter the ross
# dev shell.
#
# TODO: think of a more principled fix for this.
c: lib.filter (f: f.pname or null != "nix") c.nativeBuildInputs
) activeComponents
)
)
++ lib.optional (
!buildCanExecuteHost
@@ -308,8 +321,8 @@ pkgs.nixComponents2.nix-util.overrideAttrs (
pkgs.buildPackages.nixfmt-rfc-style
pkgs.buildPackages.shellcheck
pkgs.buildPackages.include-what-you-use
pkgs.buildPackages.gdb
]
++ lib.optional pkgs.hostPlatform.isUnix pkgs.buildPackages.gdb
++ lib.optional (stdenv.cc.isClang && stdenv.hostPlatform == stdenv.buildPlatform) (
lib.hiPrio pkgs.buildPackages.clang-tools
)
@@ -325,13 +338,13 @@ pkgs.nixComponents2.nix-util.overrideAttrs (
)
);
buildInputs = [
pkgs.gbenchmark
]
++ dedupByString (v: "${v}") (
lib.filter (x: !isInternal x) (lib.lists.concatMap (c: c.buildInputs) activeComponents)
)
++ lib.optional havePerl pkgs.perl;
buildInputs =
# TODO change Nixpkgs to mark gbenchmark as building on Windows
lib.optional pkgs.hostPlatform.isUnix pkgs.gbenchmark
++ dedupByString (v: "${v}") (
lib.filter (x: !isInternal x) (lib.lists.concatMap (c: c.buildInputs) activeComponents)
)
++ lib.optional havePerl pkgs.perl;
propagatedBuildInputs = dedupByString (v: "${v}") (
lib.filter (x: !isInternal x) (lib.lists.concatMap (c: c.propagatedBuildInputs) activeComponents)

View File

@@ -62,9 +62,11 @@ schemas = [
},
{
'stem' : 'build-trace-entry',
'schema' : schema_dir / 'build-trace-entry-v1.yaml',
'schema' : schema_dir / 'build-trace-entry-v2.yaml',
'files' : [
'simple.json',
# The field is no longer supported, but we want to show that we
# ignore it during parsing.
'with-dependent-realisations.json',
'with-signature.json',
],

View File

@@ -741,6 +741,11 @@ public:
inDebugger = true;
}
DebuggerGuard(DebuggerGuard &&) = delete;
DebuggerGuard(const DebuggerGuard &) = delete;
DebuggerGuard & operator=(DebuggerGuard &&) = delete;
DebuggerGuard & operator=(const DebuggerGuard &) = delete;
~DebuggerGuard()
{
inDebugger = false;

View File

@@ -107,6 +107,8 @@ private:
Bindings & operator=(const Bindings &) = delete;
Bindings & operator=(Bindings &&) = delete;
~Bindings() = default;
friend class BindingsBuilder;
/**

View File

@@ -164,8 +164,6 @@ public:
Value ** elems;
ListBuilder(EvalMemory & mem, size_t size);
// NOTE: Can be noexcept because we are just copying integral values and
// raw pointers.
ListBuilder(ListBuilder && x) noexcept
: size(x.size)
, inlineElems{x.inlineElems[0], x.inlineElems[1]}
@@ -173,6 +171,11 @@ public:
{
}
ListBuilder(const ListBuilder &) = delete;
ListBuilder & operator=(ListBuilder &&) = delete;
ListBuilder & operator=(const ListBuilder &) = delete;
~ListBuilder() = default;
Value *& operator[](size_t n)
{
return elems[n];

View File

@@ -4061,6 +4061,8 @@ static RegisterPrimOp primop_sort({
1. Transitivity
If a is less than b and b is less than c, then it follows that a is less than c.
```nix
comparator a b && comparator b c -> comparator a c
```
@@ -4073,9 +4075,23 @@ static RegisterPrimOp primop_sort({
1. Transitivity of equivalence
First, two values a and b are considered equivalent with respect to the comparator if:
```
!comparator a b && !comparator b a
```
In other words, neither is considered "less than" the other.
Transitivity of equivalence means:
If a is equivalent to b, and b is equivalent to c, then a must also be equivalent to c.
```nix
let equiv = a: b: (!comparator a b && !comparator b a); in
equiv a b && equiv b c -> equiv a c
let
equiv = x: y: (!comparator x y && !comparator y x);
in
equiv a b && equiv b c -> equiv a c
```
If the *comparator* violates any of these properties, then `builtins.sort`

View File

@@ -88,25 +88,38 @@ public:
}
};
#define VERSIONED_CHARACTERIZATION_TEST_NO_JSON(FIXTURE, NAME, STEM, VERSION, VALUE) \
TEST_F(FIXTURE, NAME##_read) \
{ \
readProtoTest(STEM, VERSION, VALUE); \
} \
TEST_F(FIXTURE, NAME##_write) \
{ \
writeProtoTest(STEM, VERSION, VALUE); \
#define VERSIONED_READ_CHARACTERIZATION_TEST_NO_JSON(FIXTURE, NAME, STEM, VERSION, VALUE) \
TEST_F(FIXTURE, NAME##_read) \
{ \
readProtoTest(STEM, VERSION, VALUE); \
}
#define VERSIONED_CHARACTERIZATION_TEST(FIXTURE, NAME, STEM, VERSION, VALUE) \
VERSIONED_CHARACTERIZATION_TEST_NO_JSON(FIXTURE, NAME, STEM, VERSION, VALUE) \
TEST_F(FIXTURE, NAME##_json_read) \
{ \
readJsonTest(STEM, VALUE); \
} \
TEST_F(FIXTURE, NAME##_json_write) \
{ \
writeJsonTest(STEM, VALUE); \
#define VERSIONED_WRITE_CHARACTERIZATION_TEST_NO_JSON(FIXTURE, NAME, STEM, VERSION, VALUE) \
TEST_F(FIXTURE, NAME##_write) \
{ \
writeProtoTest(STEM, VERSION, VALUE); \
}
#define VERSIONED_CHARACTERIZATION_TEST_NO_JSON(FIXTURE, NAME, STEM, VERSION, VALUE) \
VERSIONED_READ_CHARACTERIZATION_TEST_NO_JSON(FIXTURE, NAME, STEM, VERSION, VALUE) \
VERSIONED_WRITE_CHARACTERIZATION_TEST_NO_JSON(FIXTURE, NAME, STEM, VERSION, VALUE)
#define VERSIONED_READ_CHARACTERIZATION_TEST(FIXTURE, NAME, STEM, VERSION, VALUE) \
VERSIONED_READ_CHARACTERIZATION_TEST_NO_JSON(FIXTURE, NAME, STEM, VERSION, VALUE) \
TEST_F(FIXTURE, NAME##_json_read) \
{ \
readJsonTest(STEM, VALUE); \
}
#define VERSIONED_WRITE_CHARACTERIZATION_TEST(FIXTURE, NAME, STEM, VERSION, VALUE) \
VERSIONED_WRITE_CHARACTERIZATION_TEST_NO_JSON(FIXTURE, NAME, STEM, VERSION, VALUE) \
TEST_F(FIXTURE, NAME##_json_write) \
{ \
writeJsonTest(STEM, VALUE); \
}
#define VERSIONED_CHARACTERIZATION_TEST(FIXTURE, NAME, STEM, VERSION, VALUE) \
VERSIONED_READ_CHARACTERIZATION_TEST(FIXTURE, NAME, STEM, VERSION, VALUE) \
VERSIONED_WRITE_CHARACTERIZATION_TEST(FIXTURE, NAME, STEM, VERSION, VALUE)
} // namespace nix

View File

@@ -47,24 +47,30 @@ public:
}
};
#define CHARACTERIZATION_TEST(NAME, STEM, VALUE) \
TEST_F(CommonProtoTest, NAME##_read) \
{ \
readProtoTest(STEM, VALUE); \
} \
TEST_F(CommonProtoTest, NAME##_write) \
{ \
writeProtoTest(STEM, VALUE); \
} \
TEST_F(CommonProtoTest, NAME##_json_read) \
{ \
readJsonTest(STEM, VALUE); \
} \
TEST_F(CommonProtoTest, NAME##_json_write) \
{ \
writeJsonTest(STEM, VALUE); \
#define READ_CHARACTERIZATION_TEST(NAME, STEM, VALUE) \
TEST_F(CommonProtoTest, NAME##_read) \
{ \
readProtoTest(STEM, VALUE); \
} \
TEST_F(CommonProtoTest, NAME##_json_read) \
{ \
readJsonTest(STEM, VALUE); \
}
#define WRITE_CHARACTERIZATION_TEST(NAME, STEM, VALUE) \
TEST_F(CommonProtoTest, NAME##_write) \
{ \
writeProtoTest(STEM, VALUE); \
} \
TEST_F(CommonProtoTest, NAME##_json_write) \
{ \
writeJsonTest(STEM, VALUE); \
}
#define CHARACTERIZATION_TEST(NAME, STEM, VALUE) \
READ_CHARACTERIZATION_TEST(NAME, STEM, VALUE) \
WRITE_CHARACTERIZATION_TEST(NAME, STEM, VALUE)
CHARACTERIZATION_TEST(
string,
"string",
@@ -141,7 +147,7 @@ CHARACTERIZATION_TEST(
},
}))
CHARACTERIZATION_TEST(
READ_CHARACTERIZATION_TEST(
realisation_with_deps,
"realisation-with-deps",
(std::tuple<Realisation>{
@@ -149,16 +155,6 @@ CHARACTERIZATION_TEST(
{
.outPath = StorePath{"g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo"},
.signatures = {"asdf", "qwer"},
.dependentRealisations =
{
{
DrvOutput{
.drvHash = Hash::parseSRI("sha256-b4afnqKCO9oWXgYHb9DeQ2berSwOjS27rSd9TxXDc/U="),
.outputName = "quux",
},
StorePath{"g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo"},
},
},
},
{
.drvHash = Hash::parseSRI("sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="),

View File

@@ -0,0 +1,58 @@
{
"buildTrace": {
"gnRuK+wfbXqRPzgO5MyiBebXrV10Kzv+tkZCEuPm7pY=": {
"out": {
"dependentRealisations": {},
"outPath": "hrva7l0gsk67wffmks761mv4ks4vzsx7-test-ca-drv-out",
"signatures": []
}
}
},
"config": {
"store": "/nix/store"
},
"contents": {
"hrva7l0gsk67wffmks761mv4ks4vzsx7-test-ca-drv-out": {
"contents": {
"contents": "I am the output of a CA derivation",
"executable": false,
"type": "regular"
},
"info": {
"ca": {
"hash": "sha256-l0+gmYB0AK65UWuoSh7AbVRI4rAc5/VGqzBGTHgMsiU=",
"method": "nar"
},
"deriver": null,
"narHash": "sha256-l0+gmYB0AK65UWuoSh7AbVRI4rAc5/VGqzBGTHgMsiU=",
"narSize": 152,
"references": [],
"registrationTime": null,
"signatures": [],
"storeDir": "/nix/store",
"ultimate": false,
"version": 2
}
}
},
"derivations": {
"vvyyj6h5ilinsv4q48q5y5vn7s3hxmhl-test-ca-drv.drv": {
"args": [],
"builder": "",
"env": {},
"inputs": {
"drvs": {},
"srcs": []
},
"name": "test-ca-drv",
"outputs": {
"out": {
"hashAlgo": "sha256",
"method": "nar"
}
},
"system": "",
"version": 4
}
}
}

View File

@@ -0,0 +1,27 @@
{
"buildTrace": {},
"config": {
"store": "/nix/store"
},
"contents": {},
"derivations": {
"vvyyj6h5ilinsv4q48q5y5vn7s3hxmhl-test-ca-drv.drv": {
"args": [],
"builder": "",
"env": {},
"inputs": {
"drvs": {},
"srcs": []
},
"name": "test-ca-drv",
"outputs": {
"out": {
"hashAlgo": "sha256",
"method": "nar"
}
},
"system": "",
"version": 4
}
}
}

View File

@@ -0,0 +1,39 @@
{
"buildTrace": {
"gnRuK+wfbXqRPzgO5MyiBebXrV10Kzv+tkZCEuPm7pY=": {
"out": {
"dependentRealisations": {},
"outPath": "hrva7l0gsk67wffmks761mv4ks4vzsx7-test-ca-drv-out",
"signatures": []
}
}
},
"config": {
"store": "/nix/store"
},
"contents": {
"hrva7l0gsk67wffmks761mv4ks4vzsx7-test-ca-drv-out": {
"contents": {
"contents": "I am the output of a CA derivation",
"executable": false,
"type": "regular"
},
"info": {
"ca": {
"hash": "sha256-l0+gmYB0AK65UWuoSh7AbVRI4rAc5/VGqzBGTHgMsiU=",
"method": "nar"
},
"deriver": null,
"narHash": "sha256-l0+gmYB0AK65UWuoSh7AbVRI4rAc5/VGqzBGTHgMsiU=",
"narSize": 152,
"references": [],
"registrationTime": null,
"signatures": [],
"storeDir": "/nix/store",
"ultimate": false,
"version": 2
}
}
},
"derivations": {}
}

View File

@@ -0,0 +1,83 @@
{
"buildTrace": {
"8vEkprm3vQ3BE6JLB8XKfU+AdAwEFOMI/skzyj3pr5I=": {
"out": {
"dependentRealisations": {},
"outPath": "px7apdw6ydm9ynjy5g0bpdcylw3xz2kj-root-drv-out",
"signatures": []
}
}
},
"config": {
"store": "/nix/store"
},
"contents": {
"px7apdw6ydm9ynjy5g0bpdcylw3xz2kj-root-drv-out": {
"contents": {
"contents": "I am the root output. I don't reference anything because the other derivation's output is just needed at build time.",
"executable": false,
"type": "regular"
},
"info": {
"ca": {
"hash": "sha256-0mlhg9y1FGb7YsHAsNOmtuW44b8TfoPaNPK6SjVYe5s=",
"method": "nar"
},
"deriver": null,
"narHash": "sha256-0mlhg9y1FGb7YsHAsNOmtuW44b8TfoPaNPK6SjVYe5s=",
"narSize": 232,
"references": [],
"registrationTime": null,
"signatures": [],
"storeDir": "/nix/store",
"ultimate": false,
"version": 2
}
}
},
"derivations": {
"11yvkl84ashq63ilwc2mi4va41z2disw-root-drv.drv": {
"args": [],
"builder": "",
"env": {},
"inputs": {
"drvs": {
"vy7j6m6p5y0327fhk3zxn12hbpzkh6lp-dep-drv.drv": {
"dynamicOutputs": {},
"outputs": [
"out"
]
}
},
"srcs": []
},
"name": "root-drv",
"outputs": {
"out": {
"hashAlgo": "sha256",
"method": "nar"
}
},
"system": "",
"version": 4
},
"vy7j6m6p5y0327fhk3zxn12hbpzkh6lp-dep-drv.drv": {
"args": [],
"builder": "",
"env": {},
"inputs": {
"drvs": {},
"srcs": []
},
"name": "dep-drv",
"outputs": {
"out": {
"hashAlgo": "sha256",
"method": "nar"
}
},
"system": "",
"version": 4
}
}
}

View File

@@ -0,0 +1,52 @@
{
"buildTrace": {},
"config": {
"store": "/nix/store"
},
"contents": {},
"derivations": {
"11yvkl84ashq63ilwc2mi4va41z2disw-root-drv.drv": {
"args": [],
"builder": "",
"env": {},
"inputs": {
"drvs": {
"vy7j6m6p5y0327fhk3zxn12hbpzkh6lp-dep-drv.drv": {
"dynamicOutputs": {},
"outputs": [
"out"
]
}
},
"srcs": []
},
"name": "root-drv",
"outputs": {
"out": {
"hashAlgo": "sha256",
"method": "nar"
}
},
"system": "",
"version": 4
},
"vy7j6m6p5y0327fhk3zxn12hbpzkh6lp-dep-drv.drv": {
"args": [],
"builder": "",
"env": {},
"inputs": {
"drvs": {},
"srcs": []
},
"name": "dep-drv",
"outputs": {
"out": {
"hashAlgo": "sha256",
"method": "nar"
}
},
"system": "",
"version": 4
}
}
}

View File

@@ -0,0 +1,68 @@
{
"buildTrace": {
"8vEkprm3vQ3BE6JLB8XKfU+AdAwEFOMI/skzyj3pr5I=": {
"out": {
"dependentRealisations": {},
"outPath": "px7apdw6ydm9ynjy5g0bpdcylw3xz2kj-root-drv-out",
"signatures": []
}
},
"gnRuK+wfbXqRPzgO5MyiBebXrV10Kzv+tkZCEuPm7pY=": {
"out": {
"dependentRealisations": {},
"outPath": "w0yjpwh59kpbyc7hz9jgmi44r9br908i-dep-drv-out",
"signatures": []
}
}
},
"config": {
"store": "/nix/store"
},
"contents": {
"px7apdw6ydm9ynjy5g0bpdcylw3xz2kj-root-drv-out": {
"contents": {
"contents": "I am the root output. I don't reference anything because the other derivation's output is just needed at build time.",
"executable": false,
"type": "regular"
},
"info": {
"ca": {
"hash": "sha256-0mlhg9y1FGb7YsHAsNOmtuW44b8TfoPaNPK6SjVYe5s=",
"method": "nar"
},
"deriver": null,
"narHash": "sha256-0mlhg9y1FGb7YsHAsNOmtuW44b8TfoPaNPK6SjVYe5s=",
"narSize": 232,
"references": [],
"registrationTime": null,
"signatures": [],
"storeDir": "/nix/store",
"ultimate": false,
"version": 2
}
},
"w0yjpwh59kpbyc7hz9jgmi44r9br908i-dep-drv-out": {
"contents": {
"contents": "I am the dependency output",
"executable": false,
"type": "regular"
},
"info": {
"ca": {
"hash": "sha256-HK2LBzSTtwuRjc44PH3Ac1JHHPKmfnAgNxz6I5mVgL8=",
"method": "nar"
},
"deriver": null,
"narHash": "sha256-HK2LBzSTtwuRjc44PH3Ac1JHHPKmfnAgNxz6I5mVgL8=",
"narSize": 144,
"references": [],
"registrationTime": null,
"signatures": [],
"storeDir": "/nix/store",
"ultimate": false,
"version": 2
}
}
},
"derivations": {}
}

View File

@@ -0,0 +1,31 @@
{
"buildTrace": {},
"config": {
"store": "/nix/store"
},
"contents": {
"axqic2q30v0sqvcpiqxs139q8w6zd4n8-hello": {
"contents": {
"contents": "Hello, world!",
"executable": false,
"type": "regular"
},
"info": {
"ca": {
"hash": "sha256-KShIJtIwWG1gMSpvPMt5drppc1h5WMwHWzVpNJiVqGI=",
"method": "nar"
},
"deriver": null,
"narHash": "sha256-KShIJtIwWG1gMSpvPMt5drppc1h5WMwHWzVpNJiVqGI=",
"narSize": 128,
"references": [],
"registrationTime": null,
"signatures": [],
"storeDir": "/nix/store",
"ultimate": false,
"version": 2
}
}
},
"derivations": {}
}

View File

@@ -0,0 +1,55 @@
{
"buildTrace": {},
"config": {
"store": "/nix/store"
},
"contents": {
"4k79i02avcckr96r97lqnswn75fi1gv7-dependency": {
"contents": {
"contents": "I am a dependency",
"executable": false,
"type": "regular"
},
"info": {
"ca": {
"hash": "sha256-miJnClL0Ai/HAmX1G/pz7P2TIaeFjP5D/VN1rhYf354=",
"method": "nar"
},
"deriver": null,
"narHash": "sha256-miJnClL0Ai/HAmX1G/pz7P2TIaeFjP5D/VN1rhYf354=",
"narSize": 136,
"references": [],
"registrationTime": null,
"signatures": [],
"storeDir": "/nix/store",
"ultimate": false,
"version": 2
}
},
"k09ldq9fvxb6vfwq0cmv6j1jgqx08y1n-main": {
"contents": {
"contents": "I depend on /nix/store/4k79i02avcckr96r97lqnswn75fi1gv7-dependency",
"executable": false,
"type": "regular"
},
"info": {
"ca": {
"hash": "sha256-CBfMK3HkqiXjpI8HNL1spWD/US4RnQHwI67Ojl50XoQ=",
"method": "nar"
},
"deriver": null,
"narHash": "sha256-CBfMK3HkqiXjpI8HNL1spWD/US4RnQHwI67Ojl50XoQ=",
"narSize": 184,
"references": [
"4k79i02avcckr96r97lqnswn75fi1gv7-dependency"
],
"registrationTime": null,
"signatures": [],
"storeDir": "/nix/store",
"ultimate": false,
"version": 2
}
}
},
"derivations": {}
}

View File

@@ -0,0 +1,55 @@
{
"buildTrace": {},
"config": {
"store": "/nix/store"
},
"contents": {
"4k79i02avcckr96r97lqnswn75fi1gv7-dependency": {
"contents": {
"contents": "I am a dependency",
"executable": false,
"type": "regular"
},
"info": {
"ca": {
"hash": "sha256-miJnClL0Ai/HAmX1G/pz7P2TIaeFjP5D/VN1rhYf354=",
"method": "nar"
},
"deriver": null,
"narHash": "sha256-miJnClL0Ai/HAmX1G/pz7P2TIaeFjP5D/VN1rhYf354=",
"narSize": 136,
"references": [],
"registrationTime": null,
"signatures": [],
"storeDir": "/nix/store",
"ultimate": false,
"version": 2
}
},
"k09ldq9fvxb6vfwq0cmv6j1jgqx08y1n-main": {
"contents": {
"contents": "I depend on /nix/store/4k79i02avcckr96r97lqnswn75fi1gv7-dependency",
"executable": false,
"type": "regular"
},
"info": {
"ca": {
"hash": "sha256-CBfMK3HkqiXjpI8HNL1spWD/US4RnQHwI67Ojl50XoQ=",
"method": "nar"
},
"deriver": null,
"narHash": "sha256-CBfMK3HkqiXjpI8HNL1spWD/US4RnQHwI67Ojl50XoQ=",
"narSize": 184,
"references": [
"4k79i02avcckr96r97lqnswn75fi1gv7-dependency"
],
"registrationTime": null,
"signatures": [],
"storeDir": "/nix/store",
"ultimate": false,
"version": 2
}
}
},
"derivations": {}
}

View File

@@ -85,6 +85,7 @@ sources = files(
'store-reference.cc',
'uds-remote-store.cc',
'worker-protocol.cc',
'worker-substitution.cc',
'write-derivation.cc',
)
@@ -123,6 +124,7 @@ if get_option('benchmarks')
'bench-main.cc',
'derivation-parser-bench.cc',
'ref-scan-bench.cc',
'register-valid-paths-bench.cc',
)
benchmark_exe = executable(

View File

@@ -44,54 +44,45 @@ TEST_P(RealisationJsonTest, to_json)
writeJsonTest(name, value);
}
Realisation simple{
{
.outPath = StorePath{"g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv"},
},
{
.drvHash = Hash::parseExplicitFormatUnprefixed(
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
HashAlgorithm::SHA256,
HashFormat::Base16),
.outputName = "foo",
},
};
INSTANTIATE_TEST_SUITE_P(
RealisationJSON,
RealisationJsonTest,
([] {
Realisation simple{
{
.outPath = StorePath{"g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv"},
},
{
.drvHash = Hash::parseExplicitFormatUnprefixed(
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
HashAlgorithm::SHA256,
HashFormat::Base16),
.outputName = "foo",
},
};
return ::testing::Values(
std::pair{
"simple",
simple,
},
std::pair{
"with-signature",
[&] {
auto r = simple;
// FIXME actually sign properly
r.signatures = {"asdfasdfasdf"};
return r;
}()},
std::pair{
"with-dependent-realisations",
[&] {
auto r = simple;
r.dependentRealisations = {{
{
.drvHash = Hash::parseExplicitFormatUnprefixed(
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
HashAlgorithm::SHA256,
HashFormat::Base16),
.outputName = "foo",
},
StorePath{"g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv"},
}};
return r;
}(),
});
}
::testing::Values(
std::pair{
"simple",
simple,
},
std::pair{
"with-signature",
[&] {
auto r = simple;
// FIXME actually sign properly
r.signatures = {"asdfasdfasdf"};
return r;
}(),
}));
()));
/**
* We no longer have a notion of "dependent realisations", but we still
* want to parse old realisation files. So make this just be a read test
* (no write direction), accordingly.
*/
TEST_F(RealisationTest, dependent_realisations_from_json)
{
readJsonTest("with-dependent-realisations", simple);
}
} // namespace nix

View File

@@ -0,0 +1,79 @@
#include <benchmark/benchmark.h>
#include "nix/store/derivations.hh"
#include "nix/store/local-store.hh"
#include "nix/store/store-open.hh"
#include "nix/util/file-system.hh"
#include "nix/util/hash.hh"
#include "nix/util/tests/test-data.hh"
#ifndef _WIN32
# include <filesystem>
# include <fstream>
using namespace nix;
static void BM_RegisterValidPathsDerivations(benchmark::State & state)
{
const int derivationCount = state.range(0);
for (auto _ : state) {
state.PauseTiming();
auto tmpRoot = createTempDir();
auto realStoreDir = tmpRoot / "nix/store";
std::filesystem::create_directories(realStoreDir);
std::shared_ptr<Store> store = openStore(fmt("local?root=%s", tmpRoot.string()));
auto localStore = std::dynamic_pointer_cast<LocalStore>(store);
if (!localStore)
throw Error("expected local store");
ValidPathInfos infos;
for (int i = 0; i < derivationCount; ++i) {
std::string drvName = fmt("register-valid-paths-bench-%d", i);
auto drvPath = StorePath::random(drvName + ".drv");
Derivation drv;
drv.name = drvName;
drv.outputs.emplace("out", DerivationOutput{DerivationOutput::Deferred{}});
drv.platform = "x86_64-linux";
drv.builder = "foo";
drv.env["out"] = "";
drv.fillInOutputPaths(*localStore);
auto drvContents = drv.unparse(*localStore, /*maskOutputs=*/false);
/* Create an on-disk store object without registering it
in the SQLite DB. LocalFSStore::getFSAccessor(path, false)
allows reading store objects based on their filesystem
presence alone. */
std::ofstream out(realStoreDir / std::string(drvPath.to_string()), std::ios::binary);
out.write(drvContents.data(), drvContents.size());
if (!out)
throw SysError("writing derivation to store");
ValidPathInfo info{drvPath, UnkeyedValidPathInfo(*localStore, Hash::dummy)};
info.narSize = drvContents.size();
infos.emplace(drvPath, std::move(info));
}
state.ResumeTiming();
localStore->registerValidPaths(infos);
state.PauseTiming();
localStore.reset();
store.reset();
std::filesystem::remove_all(tmpRoot);
state.ResumeTiming();
}
state.SetItemsProcessed(state.iterations() * derivationCount);
}
BENCHMARK(BM_RegisterValidPathsDerivations)->Arg(10);
#endif

View File

@@ -118,7 +118,7 @@ VERSIONED_CHARACTERIZATION_TEST(
},
}))
VERSIONED_CHARACTERIZATION_TEST(
VERSIONED_READ_CHARACTERIZATION_TEST(
ServeProtoTest,
realisation_with_deps,
"realisation-with-deps",
@@ -128,16 +128,6 @@ VERSIONED_CHARACTERIZATION_TEST(
{
.outPath = StorePath{"g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo"},
.signatures = {"asdf", "qwer"},
.dependentRealisations =
{
{
DrvOutput{
.drvHash = Hash::parseSRI("sha256-b4afnqKCO9oWXgYHb9DeQ2berSwOjS27rSd9TxXDc/U="),
.outputName = "quux",
},
StorePath{"g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo"},
},
},
},
{
.drvHash = Hash::parseSRI("sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="),

View File

@@ -171,7 +171,7 @@ VERSIONED_CHARACTERIZATION_TEST(
},
}))
VERSIONED_CHARACTERIZATION_TEST(
VERSIONED_READ_CHARACTERIZATION_TEST(
WorkerProtoTest,
realisation_with_deps,
"realisation-with-deps",
@@ -181,16 +181,6 @@ VERSIONED_CHARACTERIZATION_TEST(
{
.outPath = StorePath{"g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo"},
.signatures = {"asdf", "qwer"},
.dependentRealisations =
{
{
DrvOutput{
.drvHash = Hash::parseSRI("sha256-b4afnqKCO9oWXgYHb9DeQ2berSwOjS27rSd9TxXDc/U="),
.outputName = "quux",
},
StorePath{"g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo"},
},
},
},
{
.drvHash = Hash::parseSRI("sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="),

View File

@@ -0,0 +1,450 @@
#include <gtest/gtest.h>
#include <nlohmann/json.hpp>
#include "nix/store/build/worker.hh"
#include "nix/store/derivations.hh"
#include "nix/store/dummy-store-impl.hh"
#include "nix/store/globals.hh"
#include "nix/util/memory-source-accessor.hh"
#include "nix/store/tests/libstore.hh"
#include "nix/util/tests/json-characterization.hh"
namespace nix {
class WorkerSubstitutionTest : public LibStoreTest, public JsonCharacterizationTest<ref<DummyStore>>
{
std::filesystem::path unitTestData = getUnitTestData() / "worker-substitution";
protected:
ref<DummyStore> dummyStore;
ref<DummyStore> substituter;
WorkerSubstitutionTest()
: LibStoreTest([] {
auto config = make_ref<DummyStoreConfig>(DummyStoreConfig::Params{});
config->readOnly = false;
return config->openDummyStore();
}())
, dummyStore(store.dynamic_pointer_cast<DummyStore>())
, substituter([] {
auto config = make_ref<DummyStoreConfig>(DummyStoreConfig::Params{});
config->readOnly = false;
config->isTrusted = true;
return config->openDummyStore();
}())
{
}
public:
std::filesystem::path goldenMaster(std::string_view testStem) const override
{
return unitTestData / testStem;
}
static void SetUpTestSuite()
{
initLibStore(false);
}
};
TEST_F(WorkerSubstitutionTest, singleStoreObject)
{
// Add a store path to the substituter
auto pathInSubstituter = substituter->addToStore(
"hello",
SourcePath{
[] {
auto sc = make_ref<MemorySourceAccessor>();
sc->root = MemorySourceAccessor::File{MemorySourceAccessor::File::Regular{
.executable = false,
.contents = "Hello, world!",
}};
return sc;
}(),
},
ContentAddressMethod::Raw::NixArchive,
HashAlgorithm::SHA256);
// Snapshot the substituter (has one store object)
checkpointJson("single/substituter", substituter);
// Snapshot the destination store before (should be empty)
checkpointJson("../dummy-store/empty", dummyStore);
// The path should not exist in the destination store yet
ASSERT_FALSE(dummyStore->isValidPath(pathInSubstituter));
// Create a worker with our custom substituter
Worker worker{*dummyStore, *dummyStore};
// Override the substituters to use our dummy store substituter
ref<Store> substituerAsStore = substituter;
worker.getSubstituters = [substituerAsStore]() -> std::list<ref<Store>> { return {substituerAsStore}; };
// Create a substitution goal for the path
auto goal = worker.makePathSubstitutionGoal(pathInSubstituter);
// Run the worker with -j0 semantics (no local builds, only substitution)
// The worker.run() takes a set of goals
Goals goals;
goals.insert(upcast_goal(goal));
worker.run(goals);
// Snapshot the destination store after (should match the substituter)
checkpointJson("single/substituter", dummyStore);
// The path should now exist in the destination store
ASSERT_TRUE(dummyStore->isValidPath(pathInSubstituter));
// Verify the goal succeeded
ASSERT_EQ(upcast_goal(goal)->exitCode, Goal::ecSuccess);
}
TEST_F(WorkerSubstitutionTest, singleRootStoreObjectWithSingleDepStoreObject)
{
// First, add a dependency store path to the substituter
auto dependencyPath = substituter->addToStore(
"dependency",
SourcePath{
[] {
auto sc = make_ref<MemorySourceAccessor>();
sc->root = MemorySourceAccessor::File{MemorySourceAccessor::File::Regular{
.executable = false,
.contents = "I am a dependency",
}};
return sc;
}(),
},
ContentAddressMethod::Raw::NixArchive,
HashAlgorithm::SHA256);
// Now add a store path that references the dependency
auto mainPath = substituter->addToStore(
"main",
SourcePath{
[&] {
auto sc = make_ref<MemorySourceAccessor>();
// Include a reference to the dependency path in the contents
sc->root = MemorySourceAccessor::File{MemorySourceAccessor::File::Regular{
.executable = false,
.contents = "I depend on " + substituter->printStorePath(dependencyPath),
}};
return sc;
}(),
},
ContentAddressMethod::Raw::NixArchive,
HashAlgorithm::SHA256,
StorePathSet{dependencyPath});
// Snapshot the substituter (has two store objects)
checkpointJson("with-dep/substituter", substituter);
// Snapshot the destination store before (should be empty)
checkpointJson("../dummy-store/empty", dummyStore);
// Neither path should exist in the destination store yet
ASSERT_FALSE(dummyStore->isValidPath(dependencyPath));
ASSERT_FALSE(dummyStore->isValidPath(mainPath));
// Create a worker with our custom substituter
Worker worker{*dummyStore, *dummyStore};
// Override the substituters to use our dummy store substituter
ref<Store> substituterAsStore = substituter;
worker.getSubstituters = [substituterAsStore]() -> std::list<ref<Store>> { return {substituterAsStore}; };
// Create a substitution goal for the main path only
// The worker should automatically substitute the dependency as well
auto goal = worker.makePathSubstitutionGoal(mainPath);
// Run the worker
Goals goals;
goals.insert(upcast_goal(goal));
worker.run(goals);
// Snapshot the destination store after (should match the substituter)
checkpointJson("with-dep/substituter", dummyStore);
// Both paths should now exist in the destination store
ASSERT_TRUE(dummyStore->isValidPath(dependencyPath));
ASSERT_TRUE(dummyStore->isValidPath(mainPath));
// Verify the goal succeeded
ASSERT_EQ(upcast_goal(goal)->exitCode, Goal::ecSuccess);
}
TEST_F(WorkerSubstitutionTest, floatingDerivationOutput)
{
// Enable CA derivations experimental feature
experimentalFeatureSettings.set("extra-experimental-features", "ca-derivations");
// Create a CA floating output derivation
Derivation drv;
drv.name = "test-ca-drv";
drv.outputs = {
{
"out",
DerivationOutput{DerivationOutput::CAFloating{
.method = ContentAddressMethod::Raw::NixArchive,
.hashAlgo = HashAlgorithm::SHA256,
}},
},
};
// Write the derivation to the destination store
auto drvPath = writeDerivation(*dummyStore, drv);
// Snapshot the destination store before
checkpointJson("ca-drv/store-before", dummyStore);
// Compute the hash modulo of the derivation
// For CA floating derivations, the kind is Deferred since outputs aren't known until build
auto hashModulo = hashDerivationModulo(*dummyStore, drv, true);
ASSERT_EQ(hashModulo.kind, DrvHash::Kind::Deferred);
auto drvHash = hashModulo.hashes.at("out");
// Create the output store object
auto outputPath = substituter->addToStore(
"test-ca-drv-out",
SourcePath{
[] {
auto sc = make_ref<MemorySourceAccessor>();
sc->root = MemorySourceAccessor::File{MemorySourceAccessor::File::Regular{
.executable = false,
.contents = "I am the output of a CA derivation",
}};
return sc;
}(),
},
ContentAddressMethod::Raw::NixArchive,
HashAlgorithm::SHA256);
// Add the realisation (build trace) to the substituter
substituter->buildTrace.insert_or_assign(
drvHash,
std::map<std::string, UnkeyedRealisation>{
{
"out",
UnkeyedRealisation{
.outPath = outputPath,
},
},
});
// Snapshot the substituter
checkpointJson("ca-drv/substituter", substituter);
// The realisation should not exist in the destination store yet
DrvOutput drvOutput{drvHash, "out"};
ASSERT_FALSE(dummyStore->queryRealisation(drvOutput));
// Create a worker with our custom substituter
Worker worker{*dummyStore, *dummyStore};
// Override the substituters to use our dummy store substituter
ref<Store> substituterAsStore = substituter;
worker.getSubstituters = [substituterAsStore]() -> std::list<ref<Store>> { return {substituterAsStore}; };
// Create a derivation goal for the CA derivation output
// The worker should substitute the output rather than building
auto goal = worker.makeDerivationGoal(drvPath, drv, "out", bmNormal, true);
// Run the worker
Goals goals;
goals.insert(upcast_goal(goal));
worker.run(goals);
// Snapshot the destination store after
checkpointJson("ca-drv/store-after", dummyStore);
// The output path should now exist in the destination store
ASSERT_TRUE(dummyStore->isValidPath(outputPath));
// The realisation should now exist in the destination store
auto realisation = dummyStore->queryRealisation(drvOutput);
ASSERT_TRUE(realisation);
ASSERT_EQ(realisation->outPath, outputPath);
// Verify the goal succeeded
ASSERT_EQ(upcast_goal(goal)->exitCode, Goal::ecSuccess);
// Disable CA derivations experimental feature
experimentalFeatureSettings.set("extra-experimental-features", "");
}
/**
* Test for issue #11928: substituting a CA derivation output should not
* require fetching the output of an input derivation when that output
* is not referenced.
*/
TEST_F(WorkerSubstitutionTest, floatingDerivationOutputWithDepDrv)
{
// Enable CA derivations experimental feature
experimentalFeatureSettings.set("extra-experimental-features", "ca-derivations");
// Create the dependency CA floating derivation
Derivation depDrv;
depDrv.name = "dep-drv";
depDrv.outputs = {
{
"out",
DerivationOutput{DerivationOutput::CAFloating{
.method = ContentAddressMethod::Raw::NixArchive,
.hashAlgo = HashAlgorithm::SHA256,
}},
},
};
// Write the dependency derivation to the destination store
auto depDrvPath = writeDerivation(*dummyStore, depDrv);
// Compute the hash modulo for the dependency derivation
auto depHashModulo = hashDerivationModulo(*dummyStore, depDrv, true);
ASSERT_EQ(depHashModulo.kind, DrvHash::Kind::Deferred);
auto depDrvHash = depHashModulo.hashes.at("out");
// Create the output store object for the dependency in the substituter
auto depOutputPath = substituter->addToStore(
"dep-drv-out",
SourcePath{
[] {
auto sc = make_ref<MemorySourceAccessor>();
sc->root = MemorySourceAccessor::File{MemorySourceAccessor::File::Regular{
.executable = false,
.contents = "I am the dependency output",
}};
return sc;
}(),
},
ContentAddressMethod::Raw::NixArchive,
HashAlgorithm::SHA256);
// Add the realisation for the dependency to the substituter
substituter->buildTrace.insert_or_assign(
depDrvHash,
std::map<std::string, UnkeyedRealisation>{
{
"out",
UnkeyedRealisation{
.outPath = depOutputPath,
},
},
});
// Create the root CA floating derivation that depends on depDrv
Derivation rootDrv;
rootDrv.name = "root-drv";
rootDrv.outputs = {
{
"out",
DerivationOutput{DerivationOutput::CAFloating{
.method = ContentAddressMethod::Raw::NixArchive,
.hashAlgo = HashAlgorithm::SHA256,
}},
},
};
// Add the dependency derivation as an input
rootDrv.inputDrvs = {.map = {{depDrvPath, {.value = {"out"}}}}};
// Write the root derivation to the destination store
auto rootDrvPath = writeDerivation(*dummyStore, rootDrv);
// Snapshot the destination store before
checkpointJson("issue-11928/store-before", dummyStore);
// Compute the hash modulo for the root derivation
auto rootHashModulo = hashDerivationModulo(*dummyStore, rootDrv, true);
ASSERT_EQ(rootHashModulo.kind, DrvHash::Kind::Deferred);
auto rootDrvHash = rootHashModulo.hashes.at("out");
// Create the output store object for the root derivation
// Note: it does NOT reference the dependency's output
auto rootOutputPath = substituter->addToStore(
"root-drv-out",
SourcePath{
[] {
auto sc = make_ref<MemorySourceAccessor>();
sc->root = MemorySourceAccessor::File{MemorySourceAccessor::File::Regular{
.executable = false,
.contents =
"I am the root output. "
"I don't reference anything because the other derivation's output is just needed at build time.",
}};
return sc;
}(),
},
ContentAddressMethod::Raw::NixArchive,
HashAlgorithm::SHA256);
// The DrvOutputs for both derivations
DrvOutput depDrvOutput{depDrvHash, "out"};
DrvOutput rootDrvOutput{rootDrvHash, "out"};
// Add the realisation for the root derivation to the substituter
substituter->buildTrace.insert_or_assign(
rootDrvHash,
std::map<std::string, UnkeyedRealisation>{
{
"out",
UnkeyedRealisation{
.outPath = rootOutputPath,
},
},
});
// Snapshot the substituter
// Note: it has realisations for both drvs, but only the root's output store object
checkpointJson("issue-11928/substituter", substituter);
// The realisations should not exist in the destination store yet
ASSERT_FALSE(dummyStore->queryRealisation(depDrvOutput));
ASSERT_FALSE(dummyStore->queryRealisation(rootDrvOutput));
// Create a worker with our custom substituter
Worker worker{*dummyStore, *dummyStore};
// Override the substituters to use our dummy store substituter
ref<Store> substituterAsStore = substituter;
worker.getSubstituters = [substituterAsStore]() -> std::list<ref<Store>> { return {substituterAsStore}; };
// Create a derivation goal for the root derivation output
// The worker should substitute the output rather than building
auto goal = worker.makeDerivationGoal(rootDrvPath, rootDrv, "out", bmNormal, false);
// Run the worker
Goals goals;
goals.insert(upcast_goal(goal));
worker.run(goals);
// Snapshot the destination store after
checkpointJson("issue-11928/store-after", dummyStore);
// The root output path should now exist in the destination store
ASSERT_TRUE(dummyStore->isValidPath(rootOutputPath));
// The root realisation should now exist in the destination store
auto rootRealisation = dummyStore->queryRealisation(rootDrvOutput);
ASSERT_TRUE(rootRealisation);
ASSERT_EQ(rootRealisation->outPath, rootOutputPath);
// #11928: The dependency's REALISATION should be fetched, because
// it is needed to resolve the underlying derivation. Currently the
// realisation is not fetched (bug). Once fixed: Change
// depRealisation ASSERT_FALSE to ASSERT_TRUE and uncomment the
// ASSERT_EQ
auto depRealisation = dummyStore->queryRealisation(depDrvOutput);
ASSERT_FALSE(depRealisation);
// ASSERT_EQ(depRealisation->outPath, depOutputPath);
// The dependency's OUTPUT is correctly not fetched (not referenced by root output)
ASSERT_FALSE(dummyStore->isValidPath(depOutputPath));
// Verify the goal succeeded
ASSERT_EQ(upcast_goal(goal)->exitCode, Goal::ecSuccess);
// Disable CA derivations experimental feature
experimentalFeatureSettings.set("extra-experimental-features", "");
}
} // namespace nix

View File

@@ -988,7 +988,7 @@ Path DerivationBuildingGoal::openLogFile()
logFileSink = std::make_shared<FdSink>(fdLogFile.get());
if (settings.compressLog)
logSink = std::shared_ptr<CompressionSink>(makeCompressionSink("bzip2", *logFileSink));
logSink = std::shared_ptr<CompressionSink>(makeCompressionSink(CompressionAlgo::bzip2, *logFileSink));
else
logSink = logFileSink;

View File

@@ -1,4 +1,5 @@
#include "nix/store/build/derivation-goal.hh"
#include "nix/store/build/drv-output-substitution-goal.hh"
#include "nix/store/build/derivation-building-goal.hh"
#include "nix/store/build/derivation-resolution-goal.hh"
#ifndef _WIN32 // TODO enable build hook on Windows
@@ -100,9 +101,24 @@ Goal::Co DerivationGoal::haveDerivation(bool storeDerivation)
through substitutes. If that doesn't work, we'll build
them. */
if (settings.useSubstitutes && drvOptions.substitutesAllowed()) {
if (!checkResult)
waitees.insert(upcast_goal(worker.makeDrvOutputSubstitutionGoal(DrvOutput{outputHash, wantedOutput})));
else {
if (!checkResult) {
DrvOutput id{outputHash, wantedOutput};
auto g = worker.makeDrvOutputSubstitutionGoal(id);
waitees.insert(g);
co_await await(std::move(waitees));
if (nrFailed == 0) {
waitees.insert(upcast_goal(worker.makePathSubstitutionGoal(g->outputInfo->outPath)));
co_await await(std::move(waitees));
trace("output path substituted");
if (nrFailed == 0)
worker.store.registerDrvOutput({*g->outputInfo, id});
else
debug("The output path of the derivation output '%s' could not be substituted", id.to_string());
}
} else {
auto * cap = getDerivationCA(*drv);
waitees.insert(upcast_goal(worker.makePathSubstitutionGoal(
checkResult->first.outPath,
@@ -210,11 +226,6 @@ Goal::Co DerivationGoal::haveDerivation(bool storeDerivation)
.outputName = wantedOutput,
}};
newRealisation.signatures.clear();
if (!drv->type().isFixed()) {
auto & drvStore = worker.evalStore.isValidPath(drvPath) ? worker.evalStore : worker.store;
newRealisation.dependentRealisations =
drvOutputReferences(worker.store, *drv, realisation.outPath, &drvStore);
}
worker.store.signRealisation(newRealisation);
worker.store.registerDrvOutput(newRealisation);
}

View File

@@ -3,8 +3,6 @@
#include "nix/store/build/worker.hh"
#include "nix/store/build/substitution-goal.hh"
#include "nix/util/callback.hh"
#include "nix/store/store-open.hh"
#include "nix/store/globals.hh"
namespace nix {
@@ -21,11 +19,11 @@ Goal::Co DrvOutputSubstitutionGoal::init()
trace("init");
/* If the derivation already exists, were done */
if (worker.store.queryRealisation(id)) {
if ((outputInfo = worker.store.queryRealisation(id))) {
co_return amDone(ecSuccess);
}
auto subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
auto subs = worker.getSubstituters();
bool substituterFailed = false;
@@ -79,12 +77,6 @@ Goal::Co DrvOutputSubstitutionGoal::init()
worker.childTerminated(this);
/*
* The realisation corresponding to the given output id.
* Will be filled once we can get it.
*/
std::shared_ptr<const UnkeyedRealisation> outputInfo;
try {
outputInfo = promise->get_future().get();
} catch (std::exception & e) {
@@ -95,45 +87,6 @@ Goal::Co DrvOutputSubstitutionGoal::init()
if (!outputInfo)
continue;
bool failed = false;
Goals waitees;
for (const auto & [depId, depPath] : outputInfo->dependentRealisations) {
if (depId != id) {
if (auto localOutputInfo = worker.store.queryRealisation(depId);
localOutputInfo && localOutputInfo->outPath != depPath) {
warn(
"substituter '%s' has an incompatible realisation for '%s', ignoring.\n"
"Local: %s\n"
"Remote: %s",
sub->config.getHumanReadableURI(),
depId.to_string(),
worker.store.printStorePath(localOutputInfo->outPath),
worker.store.printStorePath(depPath));
failed = true;
break;
}
waitees.insert(worker.makeDrvOutputSubstitutionGoal(depId));
}
}
if (failed)
continue;
waitees.insert(worker.makePathSubstitutionGoal(outputInfo->outPath));
co_await await(std::move(waitees));
trace("output path substituted");
if (nrFailed > 0) {
debug("The output path of the derivation output '%s' could not be substituted", id.to_string());
co_return amDone(nrNoSubstituters > 0 ? ecNoSubstituters : ecFailed);
}
worker.store.registerDrvOutput({*outputInfo, id});
trace("finished");
co_return amDone(ecSuccess);
}

View File

@@ -1,5 +1,4 @@
#include "nix/store/build/worker.hh"
#include "nix/store/store-open.hh"
#include "nix/store/build/substitution-goal.hh"
#include "nix/store/nar-info.hh"
#include "nix/util/finally.hh"
@@ -60,7 +59,7 @@ Goal::Co PathSubstitutionGoal::init()
throw Error(
"cannot substitute path '%s' - no write access to the Nix store", worker.store.printStorePath(storePath));
auto subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
auto subs = worker.getSubstituters();
bool substituterFailed = false;
std::optional<Error> lastStoresException = std::nullopt;

View File

@@ -1,5 +1,6 @@
#include "nix/store/local-store.hh"
#include "nix/store/machines.hh"
#include "nix/store/store-open.hh"
#include "nix/store/build/worker.hh"
#include "nix/store/build/substitution-goal.hh"
#include "nix/store/build/drv-output-substitution-goal.hh"
@@ -21,6 +22,7 @@ Worker::Worker(Store & store, Store & evalStore)
, actSubstitutions(*logger, actCopyPaths)
, store(store)
, evalStore(evalStore)
, getSubstituters{[] { return settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>{}; }}
{
nrLocalBuilds = 0;
nrSubstitutions = 0;

View File

@@ -341,8 +341,12 @@ DerivationOptions<SingleDerivedPath> derivationOptionsFromStructuredAttrs(
if (parsed) {
auto * e = optionalValueAt(parsed->structuredAttrs, "exportReferencesGraph");
if (!e || !e->is_object())
if (!e)
return ret;
if (!e->is_object()) {
warn("'exportReferencesGraph' in structured attrs is not a JSON object, ignoring");
return ret;
}
for (auto & [key, storePathsJson] : getObject(*e)) {
StringSet ss;
flatten(storePathsJson, ss);

View File

@@ -30,8 +30,6 @@
#include <thread>
#include <regex>
using namespace std::string_literals;
namespace nix {
const unsigned int RETRY_TIME_MS_DEFAULT = 250;
@@ -77,8 +75,9 @@ struct curlFileTransfer : public FileTransfer
CURL * req = 0;
// buffer to accompany the `req` above
char errbuf[CURL_ERROR_SIZE];
bool active = false; // whether the handle has been added to the multi object
bool paused = false; // whether the request has been paused previously
bool active = false; // whether the handle has been added to the multi object
bool paused = false; // whether the request has been paused previously
bool enqueued = false; // whether the request has been added the incoming queue
std::string statusMsg;
unsigned int attempt = 0;
@@ -176,7 +175,7 @@ struct curlFileTransfer : public FileTransfer
curl_easy_cleanup(req);
}
try {
if (!done)
if (!done && enqueued)
fail(FileTransferError(
Interrupted, {}, "%s of '%s' was interrupted", Uncolored(request.noun()), request.uri));
} catch (...) {
@@ -887,6 +886,7 @@ struct curlFileTransfer : public FileTransfer
if (state->isQuitting())
throw nix::Error("cannot enqueue download request because the download thread is shutting down");
state->incoming.push(item);
item->enqueued = true; /* Now any exceptions should be reported via the callback. */
}
wakeupMulti();

View File

@@ -14,11 +14,14 @@ namespace nix {
class Worker;
/**
* Substitution of a derivation output.
* This is done in three steps:
* 1. Fetch the output info from a substituter
* 2. Substitute the corresponding output path
* 3. Register the output info
* Fetch a `Realisation` (drv output name -> output path) from a
* substituter.
*
* If the output store object itself should also be substituted, that is
* the responsibility of the caller to do so.
*
* @todo rename this `BuidlTraceEntryGoal`, which will make sense
* especially once `Realisation` is renamed to `BuildTraceEntry`.
*/
class DrvOutputSubstitutionGoal : public Goal
{
@@ -31,6 +34,12 @@ class DrvOutputSubstitutionGoal : public Goal
public:
DrvOutputSubstitutionGoal(const DrvOutput & id, Worker & worker);
/**
* The realisation corresponding to the given output id.
* Will be filled once we can get it.
*/
std::shared_ptr<const UnkeyedRealisation> outputInfo;
Co init();
std::string key() override;

View File

@@ -8,6 +8,7 @@
#include "nix/store/realisation.hh"
#include "nix/util/muxable-pipe.hh"
#include <functional>
#include <future>
#include <thread>
@@ -171,6 +172,14 @@ public:
Store & store;
Store & evalStore;
/**
* Function to get the substituters to use for path substitution.
*
* Defaults to `getDefaultSubstituters`. This allows tests to
* inject custom substituters.
*/
std::function<std::list<ref<Store>>()> getSubstituters;
#ifndef _WIN32 // TODO Enable building on Windows
std::unique_ptr<HookInstance> hook;
#endif

View File

@@ -420,7 +420,7 @@ private:
uint64_t queryValidPathId(State & state, const StorePath & path);
uint64_t addValidPath(State & state, const ValidPathInfo & info, bool checkOutputs = true);
uint64_t addValidPath(State & state, const ValidPathInfo & info);
void invalidatePath(State & state, const StorePath & path);

View File

@@ -33,6 +33,22 @@ private:
public:
PathLocks();
PathLocks(const std::set<std::filesystem::path> & paths, const std::string & waitMsg = "");
PathLocks(PathLocks && other) noexcept
: fds(std::exchange(other.fds, {}))
, deletePaths(other.deletePaths)
{
}
PathLocks & operator=(PathLocks && other) noexcept
{
fds = std::exchange(other.fds, {});
deletePaths = other.deletePaths;
return *this;
}
PathLocks(const PathLocks &) = delete;
PathLocks & operator=(const PathLocks &) = delete;
bool lockPaths(const std::set<std::filesystem::path> & _paths, const std::string & waitMsg = "", bool wait = true);
~PathLocks();
void unlock();
@@ -45,6 +61,10 @@ struct FdLock
bool acquired = false;
FdLock(Descriptor desc, LockType lockType, bool wait, std::string_view waitMsg);
FdLock(const FdLock &) = delete;
FdLock & operator=(const FdLock &) = delete;
FdLock(FdLock &&) = delete;
FdLock & operator=(FdLock &&) = delete;
~FdLock()
{

View File

@@ -56,14 +56,6 @@ struct UnkeyedRealisation
StringSet signatures;
/**
* The realisations that are required for the current one to be valid.
*
* When importing this realisation, the store will first check that all its
* dependencies exist, and map to the correct output path
*/
std::map<DrvOutput, StorePath> dependentRealisations;
std::string fingerprint(const DrvOutput & key) const;
void sign(const DrvOutput & key, const Signer &);
@@ -87,10 +79,6 @@ struct Realisation : UnkeyedRealisation
bool isCompatibleWith(const UnkeyedRealisation & other) const;
static std::set<Realisation> closure(Store &, const std::set<Realisation> &);
static void closure(Store &, const std::set<Realisation> &, std::set<Realisation> & res);
bool operator==(const Realisation &) const = default;
auto operator<=>(const Realisation &) const = default;
};
@@ -154,10 +142,6 @@ struct RealisedPath
*/
const StorePath & path() const &;
void closure(Store & store, Set & ret) const;
static void closure(Store & store, const Set & startPaths, Set & ret);
Set closure(Store & store) const;
bool operator==(const RealisedPath &) const = default;
auto operator<=>(const RealisedPath &) const = default;
};

View File

@@ -1007,9 +1007,6 @@ decodeValidPathInfo(const Store & store, std::istream & str, std::optional<HashR
const ContentAddress * getDerivationCA(const BasicDerivation & drv);
std::map<DrvOutput, StorePath>
drvOutputReferences(Store & store, const Derivation & drv, const StorePath & outputPath, Store * evalStore = nullptr);
template<>
struct json_avoids_null<TrustedFlag> : std::true_type
{};

View File

@@ -110,8 +110,6 @@ struct LocalStore::State::Stmts
SQLiteStmt QueryAllRealisedOutputs;
SQLiteStmt QueryPathFromHashPart;
SQLiteStmt QueryValidPaths;
SQLiteStmt QueryRealisationReferences;
SQLiteStmt AddRealisationReference;
};
LocalStore::LocalStore(ref<const Config> config)
@@ -390,21 +388,6 @@ LocalStore::LocalStore(ref<const Config> config)
where drvPath = ?
;
)");
state->stmts->QueryRealisationReferences.create(
state->db,
R"(
select drvPath, outputName from Realisations
join RealisationsRefs on realisationReference = Realisations.id
where referrer = ?;
)");
state->stmts->AddRealisationReference.create(
state->db,
R"(
insert or replace into RealisationsRefs (referrer, realisationReference)
values (
(select id from Realisations where drvPath = ? and outputName = ?),
(select id from Realisations where drvPath = ? and outputName = ?));
)");
}
}
@@ -654,25 +637,6 @@ void LocalStore::registerDrvOutput(const Realisation & info)
concatStringsSep(" ", info.signatures))
.exec();
}
for (auto & [outputId, depPath] : info.dependentRealisations) {
auto localRealisation = queryRealisationCore_(*state, outputId);
if (!localRealisation)
throw Error(
"unable to register the derivation '%s' as it "
"depends on the non existent '%s'",
info.id.to_string(),
outputId.to_string());
if (localRealisation->second.outPath != depPath)
throw Error(
"unable to register the derivation '%s' as it "
"depends on a realisation of '%s' that doesnt"
"match what we have locally",
info.id.to_string(),
outputId.to_string());
state->stmts->AddRealisationReference
.use()(info.id.strHash())(info.id.outputName)(outputId.strHash())(outputId.outputName)
.exec();
}
});
}
@@ -683,7 +647,7 @@ void LocalStore::cacheDrvOutputMapping(
[&]() { state.stmts->AddDerivationOutput.use()(deriver)(outputName) (printStorePath(output)).exec(); });
}
uint64_t LocalStore::addValidPath(State & state, const ValidPathInfo & info, bool checkOutputs)
uint64_t LocalStore::addValidPath(State & state, const ValidPathInfo & info)
{
if (info.ca.has_value() && !info.isContentAddressed(*this))
throw Error(
@@ -704,17 +668,16 @@ uint64_t LocalStore::addValidPath(State & state, const ValidPathInfo & info, boo
efficiently query whether a path is an output of some
derivation. */
if (info.path.isDerivation()) {
auto drv = readInvalidDerivation(info.path);
auto parsedDrv = readInvalidDerivation(info.path);
/* Verify that the output paths in the derivation are correct
(i.e., follow the scheme for computing output paths from
derivations). Note that if this throws an error, then the
DB transaction is rolled back, so the path validity
registration above is undone. */
if (checkOutputs)
drv.checkInvariants(*this, info.path);
parsedDrv.checkInvariants(*this, info.path);
for (auto & i : drv.outputsAndOptPaths(*this)) {
for (auto & i : parsedDrv.outputsAndOptPaths(*this)) {
/* Floating CA derivations have indeterminate output paths until
they are built, so don't register anything in that case */
if (i.second.second)
@@ -965,7 +928,7 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
if (isValidPath_(*state, i.path))
updatePathInfo(*state, i);
else
addValidPath(*state, i, false);
addValidPath(*state, i);
paths.insert(i.path);
}
@@ -975,15 +938,6 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
state->stmts->AddReference.use()(referrer)(queryValidPathId(*state, j)).exec();
}
/* Check that the derivation outputs are correct. We can't do
this in addValidPath() above, because the references might
not be valid yet. */
for (auto & [_, i] : infos)
if (i.path.isDerivation()) {
// FIXME: inefficient; we already loaded the derivation in addValidPath().
readInvalidDerivation(i.path).checkInvariants(*this, i.path);
}
/* Do a topological sort of the paths. This will throw an
error if a cycle is detected and roll back the
transaction. Cycles can only occur when a derivation
@@ -1606,21 +1560,6 @@ std::optional<const UnkeyedRealisation> LocalStore::queryRealisation_(LocalStore
return std::nullopt;
auto [realisationDbId, res] = *maybeCore;
std::map<DrvOutput, StorePath> dependentRealisations;
auto useRealisationRefs(state.stmts->QueryRealisationReferences.use()(realisationDbId));
while (useRealisationRefs.next()) {
auto depId = DrvOutput{
Hash::parseAnyPrefixed(useRealisationRefs.getStr(0)),
useRealisationRefs.getStr(1),
};
auto dependentRealisation = queryRealisationCore_(state, depId);
assert(dependentRealisation); // Enforced by the db schema
auto outputPath = dependentRealisation->second.outPath;
dependentRealisations.insert({depId, outputPath});
}
res.dependentRealisations = dependentRealisations;
return {res};
}

View File

@@ -23,9 +23,9 @@ void Store::computeFSClosure(
bool includeOutputs,
bool includeDerivers)
{
std::function<std::set<StorePath>(const StorePath & path, std::future<ref<const ValidPathInfo>> &)> queryDeps;
std::function<asio::awaitable<StorePathSet>(const StorePath & path)> queryDeps;
if (flipDirection)
queryDeps = [&](const StorePath & path, std::future<ref<const ValidPathInfo>> & fut) {
queryDeps = [this, includeOutputs, includeDerivers](const StorePath & path) -> asio::awaitable<StorePathSet> {
StorePathSet res;
StorePathSet referrers;
queryReferrers(path, referrers);
@@ -41,12 +41,14 @@ void Store::computeFSClosure(
for (auto & [_, maybeOutPath] : queryPartialDerivationOutputMap(path))
if (maybeOutPath && isValidPath(*maybeOutPath))
res.insert(*maybeOutPath);
return res;
co_return res;
};
else
queryDeps = [&](const StorePath & path, std::future<ref<const ValidPathInfo>> & fut) {
queryDeps = [this, includeOutputs, includeDerivers](const StorePath & path) -> asio::awaitable<StorePathSet> {
StorePathSet res;
auto info = fut.get();
auto info = co_await callbackToAwaitable<ref<const ValidPathInfo>>(
[this, path](Callback<ref<const ValidPathInfo>> cb) { queryPathInfo(path, std::move(cb)); });
for (auto & ref : info->references)
if (ref != path)
res.insert(ref);
@@ -58,25 +60,9 @@ void Store::computeFSClosure(
if (includeDerivers && info->deriver && isValidPath(*info->deriver))
res.insert(*info->deriver);
return res;
co_return res;
};
computeClosure<StorePath>(
startPaths,
paths_,
[&](const StorePath & path, std::function<void(std::promise<std::set<StorePath>> &)> processEdges) {
std::promise<std::set<StorePath>> promise;
std::function<void(std::future<ref<const ValidPathInfo>>)> getDependencies =
[&](std::future<ref<const ValidPathInfo>> fut) {
try {
promise.set_value(queryDeps(path, fut));
} catch (...) {
promise.set_exception(std::current_exception());
}
};
queryPathInfo(path, getDependencies);
processEdges(promise);
});
computeClosure<StorePath>(startPaths, paths_, queryDeps);
}
void Store::computeFSClosure(
@@ -334,65 +320,6 @@ StorePaths Store::topoSortPaths(const StorePathSet & paths)
result);
}
std::map<DrvOutput, StorePath>
drvOutputReferences(const std::set<Realisation> & inputRealisations, const StorePathSet & pathReferences)
{
std::map<DrvOutput, StorePath> res;
for (const auto & input : inputRealisations) {
if (pathReferences.count(input.outPath)) {
res.insert({input.id, input.outPath});
}
}
return res;
}
std::map<DrvOutput, StorePath>
drvOutputReferences(Store & store, const Derivation & drv, const StorePath & outputPath, Store * evalStore_)
{
auto & evalStore = evalStore_ ? *evalStore_ : store;
std::set<Realisation> inputRealisations;
auto accumRealisations = [&](this auto & self,
const StorePath & inputDrv,
const DerivedPathMap<StringSet>::ChildNode & inputNode) -> void {
if (!inputNode.value.empty()) {
auto outputHashes = staticOutputHashes(evalStore, evalStore.readDerivation(inputDrv));
for (const auto & outputName : inputNode.value) {
auto outputHash = get(outputHashes, outputName);
if (!outputHash)
throw Error(
"output '%s' of derivation '%s' isn't realised", outputName, store.printStorePath(inputDrv));
DrvOutput key{*outputHash, outputName};
auto thisRealisation = store.queryRealisation(key);
if (!thisRealisation)
throw Error(
"output '%s' of derivation '%s' isnt built", outputName, store.printStorePath(inputDrv));
inputRealisations.insert({*thisRealisation, std::move(key)});
}
}
if (!inputNode.value.empty()) {
auto d = makeConstantStorePathRef(inputDrv);
for (const auto & [outputName, childNode] : inputNode.childMap) {
SingleDerivedPath next = SingleDerivedPath::Built{d, outputName};
self(
// TODO deep resolutions for dynamic derivations, issue #8947, would go here.
resolveDerivedPath(store, next, evalStore_),
childNode);
}
}
};
for (const auto & [inputDrv, inputNode] : drv.inputDrvs.map)
accumRealisations(inputDrv, inputNode);
auto info = store.queryPathInfo(outputPath);
return drvOutputReferences(Realisation::closure(store, inputRealisations), info->references);
}
OutputPathMap resolveDerivedPath(Store & store, const DerivedPath::Built & bfd, Store * evalStore_)
{
auto drvPath = resolveDerivedPath(store, *bfd.drvPath, evalStore_);

View File

@@ -1,6 +1,5 @@
#include "nix/store/realisation.hh"
#include "nix/store/store-api.hh"
#include "nix/util/closure.hh"
#include "nix/util/signature/local-keys.hh"
#include "nix/util/json-utils.hh"
#include <nlohmann/json.hpp>
@@ -26,41 +25,6 @@ std::string DrvOutput::to_string() const
return strHash() + "!" + outputName;
}
std::set<Realisation> Realisation::closure(Store & store, const std::set<Realisation> & startOutputs)
{
std::set<Realisation> res;
Realisation::closure(store, startOutputs, res);
return res;
}
void Realisation::closure(Store & store, const std::set<Realisation> & startOutputs, std::set<Realisation> & res)
{
auto getDeps = [&](const Realisation & current) -> std::set<Realisation> {
std::set<Realisation> res;
for (auto & [currentDep, _] : current.dependentRealisations) {
if (auto currentRealisation = store.queryRealisation(currentDep))
res.insert({*currentRealisation, currentDep});
else
throw Error("Unrealised derivation '%s'", currentDep.to_string());
}
return res;
};
computeClosure<Realisation>(
startOutputs,
res,
[&](const Realisation & current, std::function<void(std::promise<std::set<Realisation>> &)> processEdges) {
std::promise<std::set<Realisation>> promise;
try {
auto res = getDeps(current);
promise.set_value(res);
} catch (...) {
promise.set_exception(std::current_exception());
}
return processEdges(promise);
});
}
std::string UnkeyedRealisation::fingerprint(const DrvOutput & key) const
{
nlohmann::json serialized = Realisation{*this, key};
@@ -99,43 +63,7 @@ const StorePath & RealisedPath::path() const &
bool Realisation::isCompatibleWith(const UnkeyedRealisation & other) const
{
if (outPath == other.outPath) {
if (dependentRealisations.empty() != other.dependentRealisations.empty()) {
warn(
"Encountered a realisation for '%s' with an empty set of "
"dependencies. This is likely an artifact from an older Nix. "
"Ill try to fix the realisation if I can",
id.to_string());
return true;
} else if (dependentRealisations == other.dependentRealisations) {
return true;
}
}
return false;
}
void RealisedPath::closure(Store & store, const RealisedPath::Set & startPaths, RealisedPath::Set & ret)
{
// FIXME: This only builds the store-path closure, not the real realisation
// closure
StorePathSet initialStorePaths, pathsClosure;
for (auto & path : startPaths)
initialStorePaths.insert(path.path());
store.computeFSClosure(initialStorePaths, pathsClosure);
ret.insert(startPaths.begin(), startPaths.end());
ret.insert(pathsClosure.begin(), pathsClosure.end());
}
void RealisedPath::closure(Store & store, RealisedPath::Set & ret) const
{
RealisedPath::closure(store, {*this}, ret);
}
RealisedPath::Set RealisedPath::closure(Store & store) const
{
RealisedPath::Set ret;
closure(store, ret);
return ret;
return outPath == other.outPath;
}
} // namespace nix
@@ -162,27 +90,19 @@ UnkeyedRealisation adl_serializer<UnkeyedRealisation>::from_json(const json & js
if (auto signaturesOpt = optionalValueAt(json, "signatures"))
signatures = *signaturesOpt;
std::map<DrvOutput, StorePath> dependentRealisations;
if (auto jsonDependencies = optionalValueAt(json, "dependentRealisations"))
for (auto & [jsonDepId, jsonDepOutPath] : getObject(*jsonDependencies))
dependentRealisations.insert({DrvOutput::parse(jsonDepId), jsonDepOutPath});
return UnkeyedRealisation{
.outPath = valueAt(json, "outPath"),
.signatures = signatures,
.dependentRealisations = dependentRealisations,
};
}
void adl_serializer<UnkeyedRealisation>::to_json(json & json, const UnkeyedRealisation & r)
{
auto jsonDependentRealisations = nlohmann::json::object();
for (auto & [depId, depOutPath] : r.dependentRealisations)
jsonDependentRealisations.emplace(depId.to_string(), depOutPath);
json = {
{"outPath", r.outPath},
{"signatures", r.signatures},
{"dependentRealisations", jsonDependentRealisations},
// back-compat
{"dependentRealisations", json::object()},
};
}

View File

@@ -292,7 +292,7 @@ std::vector<KeyedBuildResult> RestrictedStore::buildPathsWithResults(
next->computeFSClosure(newPaths, closure);
for (auto & path : closure)
goal.addDependency(path);
for (auto & real : Realisation::closure(*next, newRealisations))
for (auto & real : newRealisations)
goal.addedDrvOutputs.insert(real.id);
return results;

View File

@@ -915,36 +915,21 @@ std::map<StorePath, StorePath> copyPaths(
SubstituteFlag substitute)
{
StorePathSet storePaths;
std::set<Realisation> toplevelRealisations;
std::vector<const Realisation *> realisations;
for (auto & path : paths) {
storePaths.insert(path.path());
if (auto * realisation = std::get_if<Realisation>(&path.raw)) {
experimentalFeatureSettings.require(Xp::CaDerivations);
toplevelRealisations.insert(*realisation);
realisations.push_back(realisation);
}
}
auto pathsMap = copyPaths(srcStore, dstStore, storePaths, repair, checkSigs, substitute);
try {
// Copy the realisation closure
processGraph<Realisation>(
Realisation::closure(srcStore, toplevelRealisations),
[&](const Realisation & current) -> std::set<Realisation> {
std::set<Realisation> children;
for (const auto & [drvOutput, _] : current.dependentRealisations) {
auto currentChild = srcStore.queryRealisation(drvOutput);
if (!currentChild)
throw Error(
"incomplete realisation closure: '%s' is a "
"dependency of '%s' but isn't registered",
drvOutput.to_string(),
current.id.to_string());
children.insert({*currentChild, drvOutput});
}
return children;
},
[&](const Realisation & current) -> void { dstStore.registerDrvOutput(current, checkSigs); });
// Copy the realisations. TODO batch this
for (const auto * realisation : realisations)
dstStore.registerDrvOutput(*realisation, checkSigs);
} catch (MissingExperimentalFeature & e) {
// Don't fail if the remote doesn't support CA derivations is it might
// not be within our control to change that, and we might still want
@@ -1055,8 +1040,19 @@ void copyClosure(
if (&srcStore == &dstStore)
return;
RealisedPath::Set closure;
RealisedPath::closure(srcStore, paths, closure);
StorePathSet closure0;
for (auto & path : paths) {
if (auto * opaquePath = std::get_if<OpaquePath>(&path.raw)) {
closure0.insert(opaquePath->path);
}
}
StorePathSet closure1;
srcStore.computeFSClosure(closure0, closure1);
RealisedPath::Set closure = paths;
for (auto && path : closure1)
closure.insert({std::move(path)});
copyPaths(srcStore, dstStore, closure, repair, checkSigs, substitute);
}

View File

@@ -83,6 +83,31 @@ void checkpointJson(CharacterizationTest & test, PathView testStem, const T & go
}
}
/**
* Specialization for when we need to do "JSON -> `ref<T>`" in one
* direction, but "`const T &` -> JSON" in the other direction.
*/
template<typename T>
void checkpointJson(CharacterizationTest & test, PathView testStem, const ref<T> & got)
{
using namespace nlohmann;
auto file = test.goldenMaster(Path{testStem} + ".json");
json gotJson = static_cast<json>(*got);
if (testAccept()) {
std::filesystem::create_directories(file.parent_path());
writeFile(file, gotJson.dump(2) + "\n");
ADD_FAILURE() << "Updating golden master " << file;
} else {
json expectedJson = json::parse(readFile(file));
ASSERT_EQ(gotJson, expectedJson);
ref<T> expected = adl_serializer<ref<T>>::from_json(expectedJson);
ASSERT_EQ(*got, *expected);
}
}
/**
* Mixin class for writing characterization tests for `nlohmann::json`
* conversions for a given type.

View File

@@ -20,10 +20,8 @@ TEST(closure, correctClosure)
set<string> aClosure;
set<string> expectedClosure = {"A", "B", "C", "F", "G"};
computeClosure<string>(
{"A"}, aClosure, [&](const string currentNode, function<void(promise<set<string>> &)> processEdges) {
promise<set<string>> promisedNodes;
promisedNodes.set_value(testGraph[currentNode]);
processEdges(promisedNodes);
{"A"}, aClosure, [&](const std::string & currentNode) -> asio::awaitable<std::set<std::string>> {
co_return testGraph[currentNode];
});
ASSERT_EQ(aClosure, expectedClosure);
@@ -37,31 +35,7 @@ TEST(closure, properlyHandlesDirectExceptions)
set<string> aClosure;
EXPECT_THROW(
computeClosure<string>(
{"A"},
aClosure,
[&](const string currentNode, function<void(promise<set<string>> &)> processEdges) { throw TestExn(); }),
TestExn);
}
TEST(closure, properlyHandlesExceptionsInPromise)
{
struct TestExn
{};
set<string> aClosure;
EXPECT_THROW(
computeClosure<string>(
{"A"},
aClosure,
[&](const string currentNode, function<void(promise<set<string>> &)> processEdges) {
promise<set<string>> promise;
try {
throw TestExn();
} catch (...) {
promise.set_exception(std::current_exception());
}
processEdges(promise);
}),
{"A"}, aClosure, [&](const std::string &) -> asio::awaitable<std::set<std::string>> { throw TestExn(); }),
TestExn);
}

View File

@@ -388,14 +388,13 @@ TEST(openFileEnsureBeneathNoSymlinks, works)
TEST(createAnonymousTempFile, works)
{
auto fd = createAnonymousTempFile();
auto fd_ = fromDescriptorReadOnly(fd.get());
writeFull(fd.get(), "test");
lseek(fd_, 0, SEEK_SET);
lseek(fd.get(), 0, SEEK_SET);
FdSource source{fd.get()};
EXPECT_EQ(source.drain(), "test");
lseek(fd_, 0, SEEK_END);
lseek(fd.get(), 0, SEEK_END);
writeFull(fd.get(), "test");
lseek(fd_, 0, SEEK_SET);
lseek(fd.get(), 0, SEEK_SET);
EXPECT_EQ(source.drain(), "testtest");
}
@@ -406,9 +405,8 @@ TEST(createAnonymousTempFile, works)
TEST(FdSource, restartWorks)
{
auto fd = createAnonymousTempFile();
auto fd_ = fromDescriptorReadOnly(fd.get());
writeFull(fd.get(), "hello world");
lseek(fd_, 0, SEEK_SET);
lseek(fd.get(), 0, SEEK_SET);
FdSource source{fd.get()};
EXPECT_EQ(source.drain(), "hello world");
source.restart();
@@ -416,4 +414,11 @@ TEST(FdSource, restartWorks)
EXPECT_EQ(source.drain(), "");
}
TEST(createTempDir, works)
{
auto tmpDir = createTempDir();
nix::AutoDelete delTmpDir(tmpDir, /*recursive=*/true);
ASSERT_TRUE(std::filesystem::is_directory(tmpDir));
}
} // namespace nix

View File

@@ -82,7 +82,7 @@ protected:
void TearDown() override
{
delTmpDir.release();
delTmpDir.reset();
}
};
@@ -91,7 +91,9 @@ TEST_F(FSSourceAccessorTest, works)
{
RestoreSink sink(false);
sink.dstPath = tmpDir;
#ifndef _WIN32
sink.dirFd = openDirectory(tmpDir);
#endif
sink.createDirectory(CanonPath("subdir"));
sink.createRegularFile(CanonPath("file1"), [](CreateRegularFileSink & crf) { crf("content1"); });
sink.createRegularFile(CanonPath("subdir/file2"), [](CreateRegularFileSink & crf) { crf("content2"); });

View File

@@ -103,8 +103,7 @@ void SourceAccessor::dumpPath(const CanonPath & path, Sink & sink, PathFilter &
time_t dumpPathAndGetMtime(const Path & path, Sink & sink, PathFilter & filter)
{
SourcePath path2 = {
makeFSSourceAccessor(std::filesystem::path{}, /*trackLastModified=*/true), CanonPath(absPath(path))};
auto path2 = PosixSourceAccessor::createAtRoot(path, /*trackLastModified=*/true);
path2.dumpPath(sink, filter);
return path2.accessor->getLastModified().value();
}

View File

@@ -69,24 +69,54 @@ struct ArchiveDecompressionSource : Source
}
};
/* These strings are a part of the public API in store parameters and such. Do not change!
Happens to match enum names. */
#define NIX_FOR_EACH_LA_ALGO(MACRO) \
MACRO(bzip2) \
MACRO(compress) \
MACRO(grzip) \
MACRO(gzip) \
MACRO(lrzip) \
MACRO(lz4) \
MACRO(lzip) \
MACRO(lzma) \
MACRO(lzop) \
MACRO(xz) \
MACRO(zstd)
struct ArchiveCompressionSink : CompressionSink
{
Sink & nextSink;
struct archive * archive;
ArchiveCompressionSink(Sink & nextSink, std::string format, bool parallel, int level = COMPRESSION_LEVEL_DEFAULT)
ArchiveCompressionSink(
Sink & nextSink, CompressionAlgo method, bool parallel, int level = COMPRESSION_LEVEL_DEFAULT)
: nextSink(nextSink)
{
archive = archive_write_new();
if (!archive)
throw Error("failed to initialize libarchive");
check(archive_write_add_filter_by_name(archive, format.c_str()), "couldn't initialize compression (%s)");
auto [addFilter, format] = [method]() -> std::pair<int (*)(struct archive *), const char *> {
switch (method) {
case CompressionAlgo::none:
case CompressionAlgo::brotli:
unreachable();
#define NIX_DEF_LA_ALGO_CASE(algo) \
case CompressionAlgo::algo: \
return {archive_write_add_filter_##algo, #algo};
NIX_FOR_EACH_LA_ALGO(NIX_DEF_LA_ALGO_CASE)
#undef NIX_DEF_LA_ALGO_CASE
}
unreachable();
}();
check(addFilter(archive), "couldn't initialize compression (%s)");
check(archive_write_set_format_raw(archive));
if (parallel)
check(archive_write_set_filter_option(archive, format.c_str(), "threads", "0"));
check(archive_write_set_filter_option(archive, format, "threads", "0"));
if (level != COMPRESSION_LEVEL_DEFAULT)
check(archive_write_set_filter_option(
archive, format.c_str(), "compression-level", std::to_string(level).c_str()));
check(archive_write_set_filter_option(archive, format, "compression-level", std::to_string(level).c_str()));
// disable internal buffering
check(archive_write_set_bytes_per_block(archive, 0));
// disable output padding
@@ -289,19 +319,52 @@ struct BrotliCompressionSink : ChunkedCompressionSink
}
};
/* Parses a *compression* method into the corresponding enum. This is only used
in the *compression* case and user interface. Content-Encoding should not use
these. */
static CompressionAlgo parseNixCompressionAlgoString(std::string_view method)
{
static const std::unordered_map<std::string_view, CompressionAlgo> lookupTable = {
{"none", CompressionAlgo::none},
{"br", CompressionAlgo::brotli},
#define NIX_DEF_LA_ALGO_NAME(algo) {#algo, CompressionAlgo::algo},
NIX_FOR_EACH_LA_ALGO(NIX_DEF_LA_ALGO_NAME)
#undef NIX_DEF_LA_ALGO_NAME
};
if (auto it = lookupTable.find(method); it != lookupTable.end())
return it->second;
static const StringSet allNames = [&]() {
StringSet res;
for (auto & [name, _] : lookupTable)
res.emplace(name);
return res;
}();
throw UnknownCompressionMethod(
Suggestions::bestMatches(allNames, method), "unknown compression method '%s'", method);
}
ref<CompressionSink> makeCompressionSink(const std::string & method, Sink & nextSink, const bool parallel, int level)
{
std::vector<std::string> la_supports = {
"bzip2", "compress", "grzip", "gzip", "lrzip", "lz4", "lzip", "lzma", "lzop", "xz", "zstd"};
if (std::find(la_supports.begin(), la_supports.end(), method) != la_supports.end()) {
return make_ref<ArchiveCompressionSink>(nextSink, method, parallel, level);
}
if (method == "none")
return makeCompressionSink(parseNixCompressionAlgoString(method), nextSink, parallel, level);
}
ref<CompressionSink> makeCompressionSink(CompressionAlgo method, Sink & nextSink, const bool parallel, int level)
{
switch (method) {
case CompressionAlgo::none:
return make_ref<NoneSink>(nextSink);
else if (method == "br")
case CompressionAlgo::brotli:
return make_ref<BrotliCompressionSink>(nextSink);
else
throw UnknownCompressionMethod("unknown compression method '%s'", method);
/* Everything else is supported via libarchive. */
#define NIX_DEF_LA_ALGO_CASE(algo) case CompressionAlgo::algo:
NIX_FOR_EACH_LA_ALGO(NIX_DEF_LA_ALGO_CASE)
return make_ref<ArchiveCompressionSink>(nextSink, method, parallel, level);
#undef NIX_DEF_LA_ALGO_CASE
}
unreachable();
}
std::string compress(const std::string & method, std::string_view in, const bool parallel, int level)

View File

@@ -115,9 +115,6 @@ Path canonPath(PathView path, bool resolveSymlinks)
if (!isAbsolute(path))
throw Error("not an absolute path: '%1%'", path);
// For Windows
auto rootName = std::filesystem::path{path}.root_name();
/* This just exists because we cannot set the target of `remaining`
(the callback parameter) directly to a newly-constructed string,
since it is `std::string_view`. */
@@ -147,8 +144,6 @@ Path canonPath(PathView path, bool resolveSymlinks)
}
});
if (!rootName.empty())
ret = rootName.string() + std::move(ret);
return ret;
}
@@ -380,14 +375,6 @@ void syncParent(const Path & path)
fd.fsync();
}
#ifdef __FreeBSD__
# define MOUNTEDPATHS_PARAM , std::set<Path> & mountedPaths
# define MOUNTEDPATHS_ARG , mountedPaths
#else
# define MOUNTEDPATHS_PARAM
# define MOUNTEDPATHS_ARG
#endif
void recursiveSync(const Path & path)
{
/* If it's a file or symlink, just fsync and return. */
@@ -432,129 +419,6 @@ void recursiveSync(const Path & path)
}
}
static void _deletePath(
Descriptor parentfd,
const std::filesystem::path & path,
uint64_t & bytesFreed,
std::exception_ptr & ex MOUNTEDPATHS_PARAM)
{
#ifndef _WIN32
checkInterrupt();
# ifdef __FreeBSD__
// In case of emergency (unmount fails for some reason) not recurse into mountpoints.
// This prevents us from tearing up the nullfs-mounted nix store.
if (mountedPaths.find(path) != mountedPaths.end()) {
return;
}
# endif
std::string name(path.filename());
assert(name != "." && name != ".." && !name.empty());
struct stat st;
if (fstatat(parentfd, name.c_str(), &st, AT_SYMLINK_NOFOLLOW) == -1) {
if (errno == ENOENT)
return;
throw SysError("getting status of %1%", path);
}
if (!S_ISDIR(st.st_mode)) {
/* We are about to delete a file. Will it likely free space? */
switch (st.st_nlink) {
/* Yes: last link. */
case 1:
bytesFreed += st.st_size;
break;
/* Maybe: yes, if 'auto-optimise-store' or manual optimisation
was performed. Instead of checking for real let's assume
it's an optimised file and space will be freed.
In worst case we will double count on freed space for files
with exactly two hardlinks for unoptimised packages.
*/
case 2:
bytesFreed += st.st_size;
break;
/* No: 3+ links. */
default:
break;
}
}
if (S_ISDIR(st.st_mode)) {
/* Make the directory accessible. */
const auto PERM_MASK = S_IRUSR | S_IWUSR | S_IXUSR;
if ((st.st_mode & PERM_MASK) != PERM_MASK) {
if (fchmodat(parentfd, name.c_str(), st.st_mode | PERM_MASK, 0) == -1)
throw SysError("chmod %1%", path);
}
int fd = openat(parentfd, name.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
if (fd == -1)
throw SysError("opening directory %1%", path);
AutoCloseDir dir(fdopendir(fd));
if (!dir)
throw SysError("opening directory %1%", path);
struct dirent * dirent;
while (errno = 0, dirent = readdir(dir.get())) { /* sic */
checkInterrupt();
std::string childName = dirent->d_name;
if (childName == "." || childName == "..")
continue;
_deletePath(dirfd(dir.get()), path / childName, bytesFreed, ex MOUNTEDPATHS_ARG);
}
if (errno)
throw SysError("reading directory %1%", path);
}
int flags = S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0;
if (unlinkat(parentfd, name.c_str(), flags) == -1) {
if (errno == ENOENT)
return;
try {
throw SysError("cannot unlink %1%", path);
} catch (...) {
if (!ex)
ex = std::current_exception();
else
ignoreExceptionExceptInterrupt();
}
}
#else
// TODO implement
throw UnimplementedError("_deletePath");
#endif
}
static void _deletePath(const std::filesystem::path & path, uint64_t & bytesFreed MOUNTEDPATHS_PARAM)
{
assert(path.is_absolute());
assert(path.parent_path() != path);
AutoCloseFD dirfd = toDescriptor(open(path.parent_path().string().c_str(), O_RDONLY));
if (!dirfd) {
if (errno == ENOENT)
return;
throw SysError("opening directory %s", path.parent_path());
}
std::exception_ptr ex;
_deletePath(dirfd.get(), path, bytesFreed, ex MOUNTEDPATHS_ARG);
if (ex)
std::rethrow_exception(ex);
}
void deletePath(const std::filesystem::path & path)
{
uint64_t dummy;
deletePath(path, dummy);
}
void createDir(const Path & path, mode_t mode)
{
if (mkdir(
@@ -577,25 +441,6 @@ void createDirs(const std::filesystem::path & path)
}
}
void deletePath(const std::filesystem::path & path, uint64_t & bytesFreed)
{
// Activity act(*logger, lvlDebug, "recursively deleting path '%1%'", path);
#ifdef __FreeBSD__
std::set<Path> mountedPaths;
struct statfs * mntbuf;
int count;
if ((count = getmntinfo(&mntbuf, MNT_WAIT)) < 0) {
throw SysError("getmntinfo");
}
for (int i = 0; i < count; i++) {
mountedPaths.emplace(mntbuf[i].f_mntonname);
}
#endif
bytesFreed = 0;
_deletePath(path, bytesFreed MOUNTEDPATHS_ARG);
}
//////////////////////////////////////////////////////////////////////
AutoDelete::AutoDelete()
@@ -672,11 +517,6 @@ void AutoUnmount::cancel()
//////////////////////////////////////////////////////////////////////
std::filesystem::path defaultTempDir()
{
return getEnvNonEmpty("TMPDIR").value_or("/tmp");
}
std::filesystem::path createTempDir(const std::filesystem::path & tmpRoot, const std::string & prefix, mode_t mode)
{
while (1) {
@@ -713,17 +553,27 @@ AutoCloseFD createAnonymousTempFile()
{
AutoCloseFD fd;
#ifdef O_TMPFILE
fd = ::open(defaultTempDir().c_str(), O_TMPFILE | O_CLOEXEC | O_RDWR, S_IWUSR | S_IRUSR);
if (!fd)
throw SysError("creating anonymous temporary file");
#else
static std::atomic_flag tmpfileUnsupported{};
if (!tmpfileUnsupported.test()) /* Try with O_TMPFILE first. */ {
/* Use O_EXCL, because the file is never supposed to be linked into filesystem. */
fd = ::open(defaultTempDir().c_str(), O_TMPFILE | O_CLOEXEC | O_RDWR | O_EXCL, S_IWUSR | S_IRUSR);
if (!fd) {
/* Not supported by the filesystem or the kernel. */
if (errno == EOPNOTSUPP || errno == EISDIR)
tmpfileUnsupported.test_and_set(); /* Set flag and fall through to createTempFile. */
else
throw SysError("creating anonymous temporary file");
} else {
return fd; /* Successfully created. */
}
}
#endif
auto [fd2, path] = createTempFile("nix-anonymous");
if (!fd2)
throw SysError("creating temporary file '%s'", path);
fd = std::move(fd2);
# ifndef _WIN32
#ifndef _WIN32
unlink(requireCString(path)); /* We only care about the file descriptor. */
# endif
#endif
return fd;
}

View File

@@ -11,6 +11,24 @@ class AutoRemoveJail
bool del;
public:
AutoRemoveJail(int jid);
AutoRemoveJail(const AutoRemoveJail &) = delete;
AutoRemoveJail & operator=(const AutoRemoveJail &) = delete;
AutoRemoveJail(AutoRemoveJail && other) noexcept
: jid(other.jid)
, del(other.del)
{
other.cancel();
}
AutoRemoveJail & operator=(AutoRemoveJail && other) noexcept
{
jid = other.jid;
del = other.del;
other.cancel();
return *this;
}
AutoRemoveJail();
~AutoRemoveJail();
void cancel();

View File

@@ -0,0 +1,68 @@
#pragma once
///@file
#include "nix/util/callback.hh"
#include "nix/util/ref.hh"
#include "nix/util/signals.hh"
#include <boost/asio/io_context.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <concepts>
namespace nix {
namespace asio = boost::asio;
template<typename T, std::invocable<Callback<T>> F, typename CompletionToken>
auto callbackToAwaitable(F && initiate, CompletionToken && token)
{
return asio::async_initiate<CompletionToken, void(std::future<T>)>(
[initiate = std::forward<F>(initiate)](auto handler) mutable {
auto executor = asio::get_associated_executor(handler);
auto done = std::make_shared<std::atomic<bool>>(false);
auto h = std::make_shared<decltype(handler)>(std::move(handler));
if (auto slot = asio::get_associated_cancellation_slot(*h); slot.is_connected()) {
std::weak_ptr wh = h; /* To handle the cyclic ownership. */
std::weak_ptr wdone = done;
slot.assign([executor, wh, wdone](asio::cancellation_type /*don't care*/) {
auto h = wh.lock();
auto done = wdone.lock();
if (!h || !done || done->exchange(true))
return; /* Gracefully die. */
/* Doesn't need to be kept alive for get_future() since it shares the ownership. */
std::promise<T> p;
p.set_exception(std::make_exception_ptr(Interrupted("interrupted by user")));
asio::post(executor, [h, fut = p.get_future()]() mutable { std::move (*h)(std::move(fut)); });
});
}
initiate(Callback<T>([executor, done, h](std::future<T> fut) mutable {
if (done->exchange(true))
/* Early return for cooperative cancellation. The callback has been caller
later than we've been cancelled. In practice we'll get an error, the handler
has already been posted by the cancellation handler. */
return;
asio::post(executor, [h, fut = std::move(fut)]() mutable { std::move (*h)(std::move(fut)); });
}));
},
std::forward<CompletionToken>(token));
}
/**
* Convert a completion handler callback into a stackless coroutine. The
* callback can be invoked on any thread and the completion handler will be
* marshalled to the coroutines executer.
*/
template<typename T, std::invocable<Callback<T>> F>
asio::awaitable<T> callbackToAwaitable(F && initiate)
{
auto fut = co_await callbackToAwaitable<T>(std::forward<F>(initiate), asio::use_awaitable);
co_return fut.get();
}
} // namespace nix

View File

@@ -1,73 +1,167 @@
#pragma once
///@file
#include <set>
#include <future>
#include "nix/util/sync.hh"
#include "nix/util/async.hh"
#include "nix/util/ref.hh"
using std::set;
#include <boost/asio/io_context.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/executor_work_guard.hpp>
#include <queue>
#include <set>
namespace nix {
template<typename T>
using GetEdgesAsync = std::function<void(const T &, std::function<void(std::promise<set<T>> &)>)>;
using GetEdgesAsync = std::function<asio::awaitable<std::set<T>>(const T & elt)>;
template<typename T>
void computeClosure(const set<T> startElts, set<T> & res, GetEdgesAsync<T> getEdgesAsync)
template<typename T, typename CompletionToken>
auto computeClosure(std::set<T> startElts, std::set<T> & res, GetEdgesAsync<T> getEdges, CompletionToken && token)
{
struct State
{
size_t pending;
set<T> & res;
std::exception_ptr exc;
};
auto initiator = [&res, startElts = std::move(startElts), getEdges = std::move(getEdges)](auto handler) {
auto executor = asio::make_strand(asio::get_associated_executor(handler));
Sync<State> state_(State{0, res, 0});
std::condition_variable done;
auto enqueue = [&](this auto & enqueue, const T & current) -> void {
/* Hand-rolled dynamic async graph traversal. ASIO/Cobalt and standard
* C++ will get channels and task groups at some point, but this will
* have to suffice for now. */
struct State : std::enable_shared_from_this<State>
{
auto state(state_.lock());
if (state->exc)
return;
if (!state->res.insert(current).second)
return;
state->pending++;
decltype(executor) executor;
decltype(getEdges) getEdges;
decltype(handler) handler;
std::set<T> & res;
/**
* Needed to keep the ctx.run() alive because actual work might be happening on another thread
* (like with FileTransfer case).
*/
asio::executor_work_guard<decltype(State::executor)> workGuard;
std::size_t pending = 0;
/**
* Whether the completion handler has been called.
*/
bool done = false;
/**
* Amount of coroutines currently in flight.
*/
std::size_t inFlight = 0;
/**
* Maximum number of concurrent coroutines. Implements primitive rate limiting.
*/
std::size_t maxConcurrent = 64;
/**
* Nodes to handle next.
*/
std::queue<T> todo;
State(
decltype(executor) executor_, decltype(getEdges) getEdges, decltype(handler) handler, std::set<T> & res)
: executor(executor_)
, getEdges(std::move(getEdges))
, handler(std::move(handler))
, res(res)
, workGuard(asio::make_work_guard(executor_))
{
}
void complete(std::exception_ptr ex)
{
if (std::exchange(done, true))
return;
workGuard.reset(); /* We are done and we can release the lock. */
asio::post(executor, [state = this->shared_from_this(), ex] { state->handler(ex); });
}
void enqueue(const std::set<T> & elts)
{
for (const auto & elt : elts)
enqueue(elt);
}
void spawnWorker(const T & elt)
{
++inFlight;
auto state = this->shared_from_this();
asio::post(executor, [state = this->shared_from_this(), elt = std::move(elt)] {
asio::co_spawn(
state->executor,
[state, elt]() -> asio::awaitable<void> {
try {
state->enqueue(co_await state->getEdges(elt));
} catch (...) {
state->complete(std::current_exception());
}
state->onWorkDone();
},
asio::detached);
});
}
void onWorkDone()
{
--inFlight;
--pending;
if (!todo.empty()) {
auto next = std::move(todo.front());
todo.pop();
asio::post(executor, [state = this->shared_from_this(), next = std::move(next)]() mutable {
state->spawnWorker(std::move(next));
});
} else if (pending == 0) {
complete(std::exception_ptr{});
}
}
void enqueue(const T & elt)
{
if (done)
return;
if (!res.insert(elt).second)
return;
++pending;
if (inFlight < maxConcurrent) {
spawnWorker(elt);
} else {
todo.push(elt);
}
}
};
auto state = make_ref<State>(executor, std::move(getEdges), std::move(handler), res);
if (startElts.empty()) {
/* No work to do. */
state->complete(std::exception_ptr{});
return;
}
getEdgesAsync(current, [&](std::promise<set<T>> & prom) {
try {
auto children = prom.get_future().get();
for (auto & child : children)
enqueue(child);
{
auto state(state_.lock());
assert(state->pending);
if (!--state->pending)
done.notify_one();
}
} catch (...) {
auto state(state_.lock());
if (!state->exc)
state->exc = std::current_exception();
assert(state->pending);
if (!--state->pending)
done.notify_one();
};
});
asio::post(executor, [state, startElts = std::move(startElts)] { state->enqueue(startElts); });
};
for (auto & startElt : startElts)
enqueue(startElt);
return asio::async_initiate<CompletionToken, void(std::exception_ptr)>(std::move(initiator), token);
}
{
auto state(state_.lock());
while (state->pending)
state.wait(done);
if (state->exc)
std::rethrow_exception(state->exc);
}
template<typename T>
void computeClosure(std::set<T> startElts, std::set<T> & res, GetEdgesAsync<T> getEdges)
{
asio::io_context ctx;
std::exception_ptr ex = nullptr;
computeClosure(
std::move(startElts),
res,
std::move(getEdges),
asio::bind_executor(ctx.get_executor(), [&](std::exception_ptr ex2) { ex = ex2; }));
ctx.run();
if (ex)
std::rethrow_exception(ex);
}
} // namespace nix

View File

@@ -16,6 +16,22 @@ struct CompressionSink : BufferedSink, FinishSink
using FinishSink::finish;
};
enum class CompressionAlgo {
none,
brotli,
bzip2,
compress,
grzip,
gzip,
lrzip,
lz4,
lzip,
lzma,
lzop,
xz,
zstd,
};
std::string decompress(const std::string & method, std::string_view in);
std::unique_ptr<FinishSink> makeDecompressionSink(const std::string & method, Sink & nextSink);
@@ -25,6 +41,9 @@ std::string compress(const std::string & method, std::string_view in, const bool
ref<CompressionSink>
makeCompressionSink(const std::string & method, Sink & nextSink, const bool parallel = false, int level = -1);
ref<CompressionSink>
makeCompressionSink(CompressionAlgo method, Sink & nextSink, const bool parallel = false, int level = -1);
MakeError(UnknownCompressionMethod, Error);
MakeError(CompressionError, Error);

View File

@@ -261,4 +261,14 @@ Descriptor openFileEnsureBeneathNoSymlinks(Descriptor dirFd, const CanonPath & p
MakeError(EndOfFile, Error);
#ifdef _WIN32
/**
* Windows specific replacement for POSIX `lseek` that operates on a `HANDLE` and not
* a file descriptor.
*/
off_t lseek(Descriptor fd, off_t offset, int whence);
#endif
} // namespace nix

View File

@@ -40,6 +40,11 @@ struct UnixPathTrait
{
return path.rfind('/', from);
}
static size_t rootNameLen(StringView)
{
return 0;
}
};
/**
@@ -83,6 +88,18 @@ struct WindowsPathTrait
size_t p2 = path.rfind(preferredSep, from);
return p1 == String::npos ? p2 : p2 == String::npos ? p1 : std::max(p1, p2);
}
static size_t rootNameLen(StringView path)
{
if (path.size() >= 2 && path[1] == ':') {
char driveLetter = path[0];
if ((driveLetter >= 'A' && driveLetter <= 'Z') || (driveLetter >= 'a' && driveLetter <= 'z'))
return 2;
}
/* TODO: This needs to also handle UNC paths.
* https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats#unc-paths */
return 0;
}
};
template<typename CharT>
@@ -116,6 +133,11 @@ typename PathDict::String canonPathInner(typename PathDict::StringView remaining
typename PathDict::String result;
result.reserve(256);
if (auto rootNameLength = PathDict::rootNameLen(remaining)) {
result += remaining.substr(0, rootNameLength); /* Copy drive letter verbatim. */
remaining.remove_prefix(rootNameLength);
}
while (true) {
/* Skip slashes. */

View File

@@ -362,6 +362,8 @@ std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix = "nix");
/**
* Return `TMPDIR`, or the default temporary directory if unset or empty.
* Uses GetTempPathW on windows which respects TMP, TEMP, USERPROFILE env variables.
* Does not resolve symlinks and the returned path might not be directory or exist at all.
*/
std::filesystem::path defaultTempDir();
@@ -480,8 +482,23 @@ class AutoUnmount
Path path;
bool del;
public:
AutoUnmount(Path &);
AutoUnmount();
AutoUnmount(Path &);
AutoUnmount(const AutoUnmount &) = delete;
AutoUnmount(AutoUnmount && other) noexcept
: path(std::move(other.path))
, del(std::exchange(other.del, false))
{
}
AutoUnmount & operator=(AutoUnmount && other) noexcept
{
path = std::move(other.path);
del = std::exchange(other.del, false);
return *this;
}
~AutoUnmount();
void cancel();
};

View File

@@ -10,6 +10,7 @@ headers = files(
'args.hh',
'args/root.hh',
'array-from-string-literal.hh',
'async.hh',
'base-n.hh',
'base-nix-32.hh',
'callback.hh',
@@ -65,6 +66,7 @@ headers = files(
'signals.hh',
'signature/local-keys.hh',
'signature/signer.hh',
'socket.hh',
'sort.hh',
'source-accessor.hh',
'source-path.hh',

View File

@@ -43,6 +43,36 @@ public:
std::optional<std::filesystem::path> getPhysicalPath(const CanonPath & path) override;
/**
* Create a `PosixSourceAccessor` and `SourcePath` corresponding to
* some native path.
*
* @param Whether the accessor should return a non-null getLastModified.
* When true the accessor must be used only by a single thread.
*
* The `PosixSourceAccessor` is rooted as far up the tree as
* possible, (e.g. on Windows it could scoped to a drive like
* `C:\`). This allows more `..` parent accessing to work.
*
* @note When `path` is trusted user input, canonicalize it using
* `std::filesystem::canonical`, `makeParentCanonical`, `std::filesystem::weakly_canonical`, etc,
* as appropriate for the use case. At least weak canonicalization is
* required for the `SourcePath` to do anything useful at the location it
* points to.
*
* @note A canonicalizing behavior is not built in `createAtRoot` so that
* callers do not accidentally introduce symlink-related security vulnerabilities.
* Furthermore, `createAtRoot` does not know whether the file pointed to by
* `path` should be resolved if it is itself a symlink. In other words,
* `createAtRoot` can not decide between aforementioned `canonical`, `makeParentCanonical`, etc. for its callers.
*
* See
* [`std::filesystem::path::root_path`](https://en.cppreference.com/w/cpp/filesystem/path/root_path)
* and
* [`std::filesystem::path::relative_path`](https://en.cppreference.com/w/cpp/filesystem/path/relative_path).
*/
static SourcePath createAtRoot(const std::filesystem::path & path, bool trackLastModified = false);
std::optional<std::time_t> getLastModified() override
{
return trackLastModified ? std::optional{mtime} : std::nullopt;

View File

@@ -159,6 +159,8 @@ struct FdSink : BufferedSink
}
FdSink(FdSink &&) = default;
FdSink(const FdSink &) = delete;
FdSink & operator=(const FdSink &) = delete;
FdSink & operator=(FdSink && s)
{
@@ -200,8 +202,10 @@ struct FdSource : BufferedSource, RestartableSource
}
FdSource(FdSource &&) = default;
FdSource & operator=(FdSource && s) = default;
FdSource(const FdSource &) = delete;
FdSource & operator=(const FdSource & s) = delete;
~FdSource() = default;
bool good() override;
void restart() override;
@@ -452,6 +456,11 @@ struct LambdaSink : Sink
{
}
LambdaSink(LambdaSink &&) = delete;
LambdaSink(const LambdaSink &) = delete;
LambdaSink & operator=(LambdaSink &&) = delete;
LambdaSink & operator=(const LambdaSink &) = delete;
~LambdaSink()
{
cleanupFun();
@@ -628,6 +637,11 @@ struct FramedSource : Source
{
}
FramedSource(FramedSource &&) = delete;
FramedSource(const FramedSource &) = delete;
FramedSource & operator=(FramedSource &&) = delete;
FramedSource & operator=(const FramedSource &) = delete;
~FramedSource()
{
try {
@@ -685,6 +699,11 @@ struct FramedSink : nix::BufferedSink
{
}
FramedSink(FramedSink &&) = delete;
FramedSink(const FramedSink &) = delete;
FramedSink & operator=(FramedSink &&) = delete;
FramedSink & operator=(const FramedSink &) = delete;
~FramedSink()
{
try {

View File

@@ -0,0 +1,61 @@
#pragma once
///@file
#include "nix/util/file-descriptor.hh"
#ifdef _WIN32
# include <winsock2.h>
#endif
namespace nix {
/**
* Often we want to use `Descriptor`, but Windows makes a slightly
* stronger file descriptor vs socket distinction, at least at the level
* of C types.
*/
using Socket =
#ifdef _WIN32
SOCKET
#else
int
#endif
;
#ifdef _WIN32
/**
* Windows gives this a different name
*/
# define SHUT_WR SD_SEND
# define SHUT_RDWR SD_BOTH
#endif
/**
* Convert a `Descriptor` to a `Socket`
*
* This is a no-op except on Windows.
*/
static inline Socket toSocket(Descriptor fd)
{
#ifdef _WIN32
return reinterpret_cast<Socket>(fd);
#else
return fd;
#endif
}
/**
* Convert a `Socket` to a `Descriptor`
*
* This is a no-op except on Windows.
*/
static inline Descriptor fromSocket(Socket fd)
{
#ifdef _WIN32
return reinterpret_cast<Descriptor>(fd);
#else
return fd;
#endif
}
} // namespace nix

View File

@@ -69,13 +69,10 @@ public:
{
}
public:
Lock(Lock && l)
: s(l.s)
{
unreachable();
}
Lock(Lock && l) = delete;
Lock(const Lock & l) = delete;
Lock & operator=(Lock && l) = delete;
Lock & operator=(const Lock & l) = delete;
~Lock() {}
@@ -110,6 +107,8 @@ public:
struct WriteLock : Lock<WL>
{
using Lock<WL>::Lock;
T * operator->()
{
return &WriteLock::s->data;
@@ -131,6 +130,8 @@ public:
struct ReadLock : Lock<RL>
{
using Lock<RL>::Lock;
const T * operator->()
{
return &ReadLock::s->data;

View File

@@ -3,10 +3,8 @@
#include "nix/util/types.hh"
#include "nix/util/file-descriptor.hh"
#include "nix/util/socket.hh"
#ifdef _WIN32
# include <winsock2.h>
#endif
#include <unistd.h>
#include <filesystem>
@@ -23,55 +21,6 @@ AutoCloseFD createUnixDomainSocket();
*/
AutoCloseFD createUnixDomainSocket(const Path & path, mode_t mode);
/**
* Often we want to use `Descriptor`, but Windows makes a slightly
* stronger file descriptor vs socket distinction, at least at the level
* of C types.
*/
using Socket =
#ifdef _WIN32
SOCKET
#else
int
#endif
;
#ifdef _WIN32
/**
* Windows gives this a different name
*/
# define SHUT_WR SD_SEND
# define SHUT_RDWR SD_BOTH
#endif
/**
* Convert a `Socket` to a `Descriptor`
*
* This is a no-op except on Windows.
*/
static inline Socket toSocket(Descriptor fd)
{
#ifdef _WIN32
return reinterpret_cast<Socket>(fd);
#else
return fd;
#endif
}
/**
* Convert a `Socket` to a `Descriptor`
*
* This is a no-op except on Windows.
*/
static inline Descriptor fromSocket(Socket fd)
{
#ifdef _WIN32
return reinterpret_cast<Descriptor>(fd);
#else
return fd;
#endif
}
/**
* Bind a Unix domain socket to a path.
*/

View File

@@ -384,6 +384,11 @@ struct MaintainCount
counter += delta;
}
MaintainCount(MaintainCount &&) = delete;
MaintainCount(const MaintainCount &) = delete;
MaintainCount & operator=(MaintainCount &&) = delete;
MaintainCount & operator=(const MaintainCount &) = delete;
~MaintainCount()
{
counter -= delta;

View File

@@ -50,6 +50,11 @@ public:
writer.openElement(name, attrs);
}
XMLOpenElement(XMLOpenElement &&) = delete;
XMLOpenElement(const XMLOpenElement &) = delete;
XMLOpenElement & operator=(XMLOpenElement &&) = delete;
XMLOpenElement & operator=(const XMLOpenElement &) = delete;
~XMLOpenElement()
{
writer.closeElement();

View File

@@ -1,4 +1,5 @@
#include "nix/util/nar-accessor.hh"
#include "nix/util/file-descriptor.hh"
#include "nix/util/archive.hh"
#include <map>
@@ -281,7 +282,7 @@ GetNarBytes seekableGetNarBytes(const Path & path)
GetNarBytes seekableGetNarBytes(Descriptor fd)
{
return [fd](uint64_t offset, uint64_t length) {
if (::lseek(fromDescriptorReadOnly(fd), offset, SEEK_SET) == -1)
if (lseek(fd, offset, SEEK_SET) == -1)
throw SysError("seeking in file");
std::string buf(length, 0);

View File

@@ -1,5 +1,7 @@
#include "nix/util/posix-source-accessor.hh"
#include "nix/util/source-path.hh"
#include "nix/util/signals.hh"
#include "nix/util/sync.hh"
#include <boost/unordered/concurrent_flat_map.hpp>
@@ -18,6 +20,15 @@ PosixSourceAccessor::PosixSourceAccessor()
{
}
SourcePath PosixSourceAccessor::createAtRoot(const std::filesystem::path & path, bool trackLastModified)
{
std::filesystem::path path2 = absPath(path);
return {
make_ref<PosixSourceAccessor>(path2.root_path(), trackLastModified),
CanonPath{path2.relative_path().string()},
};
}
std::filesystem::path PosixSourceAccessor::makeAbsPath(const CanonPath & path)
{
return root.empty() ? (std::filesystem::path{path.abs()})

View File

@@ -1,6 +1,8 @@
#include "nix/util/serialise.hh"
#include "nix/util/file-descriptor.hh"
#include "nix/util/compression.hh"
#include "nix/util/signals.hh"
#include "nix/util/socket.hh"
#include "nix/util/util.hh"
#include <cstring>
@@ -11,7 +13,6 @@
#ifdef _WIN32
# include <fileapi.h>
# include <winsock2.h>
# include "nix/util/windows-error.hh"
#else
# include <poll.h>
@@ -162,11 +163,11 @@ size_t FdSource::readUnbuffered(char * data, size_t len)
_good = false;
throw SysError("reading from file");
}
#endif
if (n == 0) {
_good = false;
throw EndOfFile(std::string(*endOfFileError));
}
#endif
read += n;
return n;
}
@@ -184,20 +185,20 @@ bool FdSource::hasData()
while (true) {
fd_set fds;
FD_ZERO(&fds);
int fd_ = fromDescriptorReadOnly(fd);
FD_SET(fd_, &fds);
Socket sock = toSocket(fd);
FD_SET(sock, &fds);
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 0;
auto n = select(fd_ + 1, &fds, nullptr, nullptr, &timeout);
auto n = select(sock + 1, &fds, nullptr, nullptr, &timeout);
if (n < 0) {
if (errno == EINTR)
continue;
throw SysError("polling file descriptor");
}
return FD_ISSET(fd, &fds);
return FD_ISSET(sock, &fds);
}
}
@@ -207,8 +208,7 @@ void FdSource::restart()
throw Error("can't seek to the start of a file");
buffer.reset();
read = bufPosIn = bufPosOut = 0;
int fd_ = fromDescriptorReadOnly(fd);
if (lseek(fd_, 0, SEEK_SET) == -1)
if (lseek(fd, 0, SEEK_SET) == -1)
throw SysError("seeking to the start of a file");
}

View File

@@ -9,6 +9,9 @@
#include <unistd.h>
#include "nix/util/file-system.hh"
#include "nix/util/environment-variables.hh"
#include "nix/util/signals.hh"
#include "nix/util/util.hh"
#include "util-unix-config-private.hh"
@@ -19,6 +22,11 @@ Descriptor openDirectory(const std::filesystem::path & path)
return open(path.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC);
}
std::filesystem::path defaultTempDir()
{
return getEnvNonEmpty("TMPDIR").value_or("/tmp");
}
void setWriteTime(
const std::filesystem::path & path, time_t accessedTime, time_t modificationTime, std::optional<bool> optIsSymlink)
{
@@ -66,4 +74,154 @@ void setWriteTime(
#endif
}
#ifdef __FreeBSD__
# define MOUNTEDPATHS_PARAM , std::set<Path> & mountedPaths
# define MOUNTEDPATHS_ARG , mountedPaths
#else
# define MOUNTEDPATHS_PARAM
# define MOUNTEDPATHS_ARG
#endif
static void _deletePath(
Descriptor parentfd,
const std::filesystem::path & path,
uint64_t & bytesFreed,
std::exception_ptr & ex MOUNTEDPATHS_PARAM)
{
#ifndef _WIN32
checkInterrupt();
# ifdef __FreeBSD__
// In case of emergency (unmount fails for some reason) not recurse into mountpoints.
// This prevents us from tearing up the nullfs-mounted nix store.
if (mountedPaths.find(path) != mountedPaths.end()) {
return;
}
# endif
std::string name(path.filename());
assert(name != "." && name != ".." && !name.empty());
struct stat st;
if (fstatat(parentfd, name.c_str(), &st, AT_SYMLINK_NOFOLLOW) == -1) {
if (errno == ENOENT)
return;
throw SysError("getting status of %1%", path);
}
if (!S_ISDIR(st.st_mode)) {
/* We are about to delete a file. Will it likely free space? */
switch (st.st_nlink) {
/* Yes: last link. */
case 1:
bytesFreed += st.st_size;
break;
/* Maybe: yes, if 'auto-optimise-store' or manual optimisation
was performed. Instead of checking for real let's assume
it's an optimised file and space will be freed.
In worst case we will double count on freed space for files
with exactly two hardlinks for unoptimised packages.
*/
case 2:
bytesFreed += st.st_size;
break;
/* No: 3+ links. */
default:
break;
}
}
if (S_ISDIR(st.st_mode)) {
/* Make the directory accessible. */
const auto PERM_MASK = S_IRUSR | S_IWUSR | S_IXUSR;
if ((st.st_mode & PERM_MASK) != PERM_MASK) {
if (fchmodat(parentfd, name.c_str(), st.st_mode | PERM_MASK, 0) == -1)
throw SysError("chmod %1%", path);
}
int fd = openat(parentfd, name.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
if (fd == -1)
throw SysError("opening directory %1%", path);
AutoCloseDir dir(fdopendir(fd));
if (!dir)
throw SysError("opening directory %1%", path);
struct dirent * dirent;
while (errno = 0, dirent = readdir(dir.get())) { /* sic */
checkInterrupt();
std::string childName = dirent->d_name;
if (childName == "." || childName == "..")
continue;
_deletePath(dirfd(dir.get()), path / childName, bytesFreed, ex MOUNTEDPATHS_ARG);
}
if (errno)
throw SysError("reading directory %1%", path);
}
int flags = S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0;
if (unlinkat(parentfd, name.c_str(), flags) == -1) {
if (errno == ENOENT)
return;
try {
throw SysError("cannot unlink %1%", path);
} catch (...) {
if (!ex)
ex = std::current_exception();
else
ignoreExceptionExceptInterrupt();
}
}
#else
// TODO implement
throw UnimplementedError("_deletePath");
#endif
}
static void _deletePath(const std::filesystem::path & path, uint64_t & bytesFreed MOUNTEDPATHS_PARAM)
{
assert(path.is_absolute());
assert(path.parent_path() != path);
AutoCloseFD dirfd = toDescriptor(open(path.parent_path().string().c_str(), O_RDONLY));
if (!dirfd) {
if (errno == ENOENT)
return;
throw SysError("opening directory %s", path.parent_path());
}
std::exception_ptr ex;
_deletePath(dirfd.get(), path, bytesFreed, ex MOUNTEDPATHS_ARG);
if (ex)
std::rethrow_exception(ex);
}
void deletePath(const std::filesystem::path & path)
{
uint64_t dummy;
deletePath(path, dummy);
}
void deletePath(const std::filesystem::path & path, uint64_t & bytesFreed)
{
// Activity act(*logger, lvlDebug, "recursively deleting path '%1%'", path);
#ifdef __FreeBSD__
std::set<Path> mountedPaths;
struct statfs * mntbuf;
int count;
if ((count = getmntinfo(&mntbuf, MNT_WAIT)) < 0) {
throw SysError("getmntinfo");
}
for (int i = 0; i < count; i++) {
mountedPaths.emplace(mntbuf[i].f_mntonname);
}
#endif
bytesFreed = 0;
_deletePath(path, bytesFreed MOUNTEDPATHS_ARG);
}
} // namespace nix

View File

@@ -27,6 +27,10 @@ private:
public:
MonitorFdHup(int fd);
MonitorFdHup(MonitorFdHup &&) = delete;
MonitorFdHup(const MonitorFdHup &) = delete;
MonitorFdHup & operator=(MonitorFdHup &&) = delete;
MonitorFdHup & operator=(const MonitorFdHup &) = delete;
~MonitorFdHup()
{

View File

@@ -141,6 +141,16 @@ struct InterruptCallbackImpl : InterruptCallback
{
InterruptCallbacks::Token token;
InterruptCallbackImpl(InterruptCallbacks::Token token)
: token(token)
{
}
InterruptCallbackImpl(InterruptCallbackImpl &&) = delete;
InterruptCallbackImpl(const InterruptCallbackImpl &) = delete;
InterruptCallbackImpl & operator=(InterruptCallbackImpl &&) = delete;
InterruptCallbackImpl & operator=(const InterruptCallbackImpl &) = delete;
~InterruptCallbackImpl() override
{
auto interruptCallbacks(_interruptCallbacks.lock());
@@ -153,11 +163,7 @@ std::unique_ptr<InterruptCallback> createInterruptCallback(std::function<void()>
auto interruptCallbacks(_interruptCallbacks.lock());
auto token = interruptCallbacks->nextToken++;
interruptCallbacks->callbacks.emplace(token, callback);
std::unique_ptr<InterruptCallbackImpl> res{new InterruptCallbackImpl{}};
res->token = token;
return std::unique_ptr<InterruptCallback>(res.release());
return std::make_unique<InterruptCallbackImpl>(token);
}
} // namespace nix

View File

@@ -5,13 +5,12 @@
#include "nix/util/windows-error.hh"
#include "nix/util/file-path.hh"
#ifdef _WIN32
# include <fileapi.h>
# include <error.h>
# include <namedpipeapi.h>
# include <namedpipeapi.h>
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#include <fileapi.h>
#include <error.h>
#include <namedpipeapi.h>
#include <namedpipeapi.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
namespace nix {
@@ -46,16 +45,16 @@ void writeFull(HANDLE handle, std::string_view s, bool allowInterrupts)
if (allowInterrupts)
checkInterrupt();
DWORD res;
# if _WIN32_WINNT >= 0x0600
#if _WIN32_WINNT >= 0x0600
auto path = handleToPath(handle); // debug; do it before because handleToPath changes lasterror
if (!WriteFile(handle, s.data(), s.size(), &res, NULL)) {
throw WinError("writing to file %1%:%2%", handle, path);
}
# else
#else
if (!WriteFile(handle, s.data(), s.size(), &res, NULL)) {
throw WinError("writing to file %1%", handle);
}
# endif
#endif
if (res > 0)
s.remove_prefix(res);
}
@@ -120,7 +119,7 @@ void Pipe::create()
//////////////////////////////////////////////////////////////////////
# if _WIN32_WINNT >= 0x0600
#if _WIN32_WINNT >= 0x0600
std::wstring windows::handleToFileName(HANDLE handle)
{
@@ -149,7 +148,37 @@ Path windows::handleToPath(HANDLE handle)
return os_string_to_string(handleToFileName(handle));
}
# endif
#endif
off_t lseek(HANDLE h, off_t offset, int whence)
{
DWORD method;
switch (whence) {
case SEEK_SET:
method = FILE_BEGIN;
break;
case SEEK_CUR:
method = FILE_CURRENT;
break;
case SEEK_END:
method = FILE_END;
break;
default:
throw Error("lseek: invalid whence %d", whence);
}
LARGE_INTEGER li;
li.QuadPart = offset;
LARGE_INTEGER newPos;
if (!SetFilePointerEx(h, li, &newPos, method)) {
/* Convert to a POSIX error, since caller code works with this as if it were
a POSIX lseek. */
errno = std::error_code(GetLastError(), std::system_category()).default_error_condition().value();
return -1;
}
return newPos.QuadPart;
}
} // namespace nix
#endif

View File

@@ -1,9 +1,11 @@
#include "nix/util/file-system.hh"
#include "nix/util/windows-error.hh"
#include "nix/util/logging.hh"
#ifdef _WIN32
namespace nix {
using namespace nix::windows;
void setWriteTime(
const std::filesystem::path & path, time_t accessedTime, time_t modificationTime, std::optional<bool> optIsSymlink)
{
@@ -28,5 +30,27 @@ Descriptor openDirectory(const std::filesystem::path & path)
NULL);
}
std::filesystem::path defaultTempDir()
{
wchar_t buf[MAX_PATH + 1];
DWORD len = GetTempPathW(MAX_PATH + 1, buf);
if (len == 0 || len > MAX_PATH)
throw WinError("getting default temporary directory");
return std::filesystem::path(buf);
}
void deletePath(const std::filesystem::path & path)
{
std::error_code ec;
std::filesystem::remove_all(path, ec);
if (ec && ec != std::errc::no_such_file_or_directory)
throw SysError(ec.default_error_condition().value(), "recursively deleting %1%", path);
}
void deletePath(const std::filesystem::path & path, uint64_t & bytesFreed)
{
bytesFreed = 0;
deletePath(path);
}
} // namespace nix
#endif

View File

@@ -38,7 +38,7 @@ struct CmdAddToStore : MixDryRun, StoreCommand
if (!namePart)
namePart = baseNameOf(path);
SourcePath sourcePath = makeFSSourceAccessor(makeParentCanonical(path));
auto sourcePath = PosixSourceAccessor::createAtRoot(makeParentCanonical(path));
auto storePath = dryRun ? store->computeStorePath(*namePart, sourcePath, caMethod, hashAlgo, {}).first
: store->addToStoreSlow(*namePart, sourcePath, caMethod, hashAlgo, {}).path;

View File

@@ -85,7 +85,9 @@ struct CmdHashBase : Command
return std::make_unique<HashSink>(hashAlgo);
};
auto makeSourcePath = [&]() -> SourcePath { return makeFSSourceAccessor(makeParentCanonical(path)); };
auto makeSourcePath = [&]() -> SourcePath {
return PosixSourceAccessor::createAtRoot(makeParentCanonical(path));
};
Hash h{HashAlgorithm::SHA256}; // throwaway def to appease C++
switch (mode) {

View File

@@ -191,7 +191,7 @@ static void opAdd(Strings opFlags, Strings opArgs)
throw UsageError("unknown flag");
for (auto & i : opArgs) {
SourcePath sourcePath = makeFSSourceAccessor(makeParentCanonical(i));
auto sourcePath = PosixSourceAccessor::createAtRoot(makeParentCanonical(i));
cout << fmt("%s\n", store->printStorePath(store->addToStore(std::string(baseNameOf(i)), sourcePath)));
}
}
@@ -215,7 +215,7 @@ static void opAddFixed(Strings opFlags, Strings opArgs)
opArgs.pop_front();
for (auto & i : opArgs) {
SourcePath sourcePath = makeFSSourceAccessor(makeParentCanonical(i));
auto sourcePath = PosixSourceAccessor::createAtRoot(makeParentCanonical(i));
std::cout << fmt(
"%s\n", store->printStorePath(store->addToStoreSlow(baseNameOf(i), sourcePath, method, hashAlgo).path));
}

View File

@@ -437,22 +437,23 @@ static void forwardStdioConnection(RemoteStore & store)
int from = conn->from.fd;
int to = conn->to.fd;
auto nfds = std::max(from, STDIN_FILENO) + 1;
Socket fromSock = toSocket(from), stdinSock = toSocket(getStandardInput());
auto nfds = std::max(fromSock, stdinSock) + 1;
while (true) {
fd_set fds;
FD_ZERO(&fds);
FD_SET(from, &fds);
FD_SET(STDIN_FILENO, &fds);
FD_SET(fromSock, &fds);
FD_SET(stdinSock, &fds);
if (select(nfds, &fds, nullptr, nullptr, nullptr) == -1)
throw SysError("waiting for data from client or server");
if (FD_ISSET(from, &fds)) {
if (FD_ISSET(fromSock, &fds)) {
auto res = splice(from, nullptr, STDOUT_FILENO, nullptr, SSIZE_MAX, SPLICE_F_MOVE);
if (res == -1)
throw SysError("splicing data from daemon socket to stdout");
else if (res == 0)
throw EndOfFile("unexpected EOF from daemon socket");
}
if (FD_ISSET(STDIN_FILENO, &fds)) {
if (FD_ISSET(stdinSock, &fds)) {
auto res = splice(STDIN_FILENO, nullptr, to, nullptr, SSIZE_MAX, SPLICE_F_MOVE);
if (res == -1)
throw SysError("splicing data from stdin to daemon socket");

View File

@@ -256,7 +256,7 @@ hashPath(char * algo, int base32, char * path)
PPCODE:
try {
Hash h = hashPath(
SourcePath{getFSSourceAccessor(), CanonPath(absPath(Path(path)))},
PosixSourceAccessor::createAtRoot(path),
FileIngestionMethod::NixArchive, parseHashAlgo(algo)).first;
auto s = h.to_string(base32 ? HashFormat::Nix32 : HashFormat::Base16, false);
XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
@@ -336,7 +336,7 @@ StoreWrapper::addToStore(char * srcPath, int recursive, char * algo)
auto method = recursive ? ContentAddressMethod::Raw::NixArchive : ContentAddressMethod::Raw::Flat;
auto path = THIS->store->addToStore(
std::string(baseNameOf(srcPath)),
SourcePath{getFSSourceAccessor(), CanonPath(absPath(Path(srcPath)))},
PosixSourceAccessor::createAtRoot(srcPath),
method, parseHashAlgo(algo));
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(path).c_str(), 0)));
} catch (Error & e) {

View File

@@ -25,4 +25,9 @@ nix build -f nondeterministic.nix dep2 --no-link
# If everything goes right, we should rebuild dep2 rather than fetch it from
# the cache (because that would mean duplicating `current-time` in the closure),
# and have `dep1 == dep2`.
# FIXME: Force the use of small-step resolutions only to fix this in a
# better way (#11896, #11928).
skipTest "temporarily broken because dependent realisations are removed"
nix build --substituters "$REMOTE_STORE" -f nondeterministic.nix toplevel --no-require-sigs --no-link

View File

@@ -22,7 +22,10 @@ nix copy --to "$REMOTE_STORE" --file ./content-addressed.nix
# Restart the build on an empty store, ensuring that we don't build
clearStore
buildDrvs --substitute --substituters "$REMOTE_STORE" --no-require-sigs -j0 transitivelyDependentCA
# FIXME: `dependentCA` should not need to be explicitly mentioned in
# this. Force the use of small-step resolutions only to allow not
# mentioning it explicitly again. (#11896, #11928).
buildDrvs --substitute --substituters "$REMOTE_STORE" --no-require-sigs -j0 transitivelyDependentCA dependentCA
# Check that the thing weve just substituted has its realisation stored
nix realisation info --file ./content-addressed.nix transitivelyDependentCA
# Check that its dependencies have it too

View File

@@ -50,3 +50,16 @@ expectStderr 0 nix-instantiate --expr "$hackyExpr" --eval --strict | grepQuiet "
# Check it works with the expected structured attrs
hacky=$(nix-instantiate --expr "$hackyExpr")
nix derivation show "$hacky" | jq --exit-status '.derivations."'"$(basename "$hacky")"'".structuredAttrs | . == {"a": 1}'
# Test warning for non-object exportReferencesGraph in structured attrs
# shellcheck disable=SC2016
expectStderr 0 nix-build --expr '
with import ./config.nix;
mkDerivation {
name = "export-graph-non-object";
__structuredAttrs = true;
exportReferencesGraph = [ "foo" "bar" ];
builder = "/bin/sh";
args = ["-c" "echo foo > ${builtins.placeholder "out"}"];
}
' | grepQuiet "warning:.*exportReferencesGraph.*not a JSON object"