Compare commits
55 Commits
makefs-sou
...
async-comp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1c0f416cb | ||
|
|
8104858643 | ||
|
|
a6c1d5637a | ||
|
|
27006cc8a9 | ||
|
|
06f21596a0 | ||
|
|
9254fab407 | ||
|
|
994324feda | ||
|
|
1f739961e5 | ||
|
|
cccfa385e6 | ||
|
|
9d2100a165 | ||
|
|
4769f3c0b2 | ||
|
|
ab354dc8f6 | ||
|
|
188cb798ad | ||
|
|
1aa7ab0dcf | ||
|
|
208ed3c538 | ||
|
|
b6add8dcc6 | ||
|
|
79750a3ccc | ||
|
|
30cd9e43e1 | ||
|
|
0695630eb5 | ||
|
|
89dc57f6aa | ||
|
|
f274a7273a | ||
|
|
675656ffba | ||
|
|
a5edc2d921 | ||
|
|
2f092870e4 | ||
|
|
b39da9c0c2 | ||
|
|
f536b25367 | ||
|
|
017fae3f14 | ||
|
|
018d6462de | ||
|
|
4a5d960952 | ||
|
|
8cf8a9151a | ||
|
|
4060ec3a8c | ||
|
|
e0830681e2 | ||
|
|
9f2795e588 | ||
|
|
12cee327a0 | ||
|
|
3b73dcba39 | ||
|
|
dfad4b1403 | ||
|
|
5f69fd3e8d | ||
|
|
47416968d2 | ||
|
|
ce38abb697 | ||
|
|
a38fc659cc | ||
|
|
d5d7594029 | ||
|
|
1fc5648204 | ||
|
|
d7e0bcaa51 | ||
|
|
4227d24bc3 | ||
|
|
7720dad11f | ||
|
|
1c63cf4001 | ||
|
|
e145632aef | ||
|
|
5cdf2a19bd | ||
|
|
bb74677b08 | ||
|
|
3cfac9b079 | ||
|
|
198628790b | ||
|
|
54d2268d84 | ||
|
|
8c74aadbf7 | ||
|
|
3a62be7227 | ||
|
|
af6326dfa4 |
2
.github/workflows/backport.yml
vendored
2
.github/workflows/backport.yml
vendored
@@ -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
|
||||
|
||||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -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) }}
|
||||
|
||||
@@ -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 : [
|
||||
|
||||
@@ -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) [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
-->
|
||||
|
||||
@@ -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',
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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":
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -107,6 +107,8 @@ private:
|
||||
Bindings & operator=(const Bindings &) = delete;
|
||||
Bindings & operator=(Bindings &&) = delete;
|
||||
|
||||
~Bindings() = default;
|
||||
|
||||
friend class BindingsBuilder;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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="),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
79
src/libstore-tests/register-valid-paths-bench.cc
Normal file
79
src/libstore-tests/register-valid-paths-bench.cc
Normal 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
|
||||
@@ -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="),
|
||||
|
||||
@@ -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="),
|
||||
|
||||
450
src/libstore-tests/worker-substitution.cc
Normal file
450
src/libstore-tests/worker-substitution.cc
Normal 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
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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, we’re 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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
{};
|
||||
|
||||
@@ -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 doesn’t"
|
||||
"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};
|
||||
}
|
||||
|
||||
|
||||
@@ -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' isn’t 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_);
|
||||
|
||||
@@ -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. "
|
||||
"I’ll 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()},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"); });
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
68
src/libutil/include/nix/util/async.hh
Normal file
68
src/libutil/include/nix/util/async.hh
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
61
src/libutil/include/nix/util/socket.hh
Normal file
61
src/libutil/include/nix/util/socket.hh
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()})
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 we’ve just substituted has its realisation stored
|
||||
nix realisation info --file ./content-addressed.nix transitivelyDependentCA
|
||||
# Check that its dependencies have it too
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user