Compare commits
84
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47677c645e | ||
|
|
02eb07cfd5 | ||
|
|
d75df91f74 | ||
|
|
a385c5935a | ||
|
|
dc0cace604 | ||
|
|
e0fd0ba211 | ||
|
|
c2b90d235f | ||
|
|
e55ec75619 | ||
|
|
869666cb65 | ||
|
|
a5c1e73fa8 | ||
|
|
bb161a96cf | ||
|
|
a510d17484 | ||
|
|
04f8a14833 | ||
|
|
e6f2af06e6 | ||
|
|
4d89844207 | ||
|
|
422550fd68 | ||
|
|
5d31e889d7 | ||
|
|
4f02255c20 | ||
|
|
0dc486a5bf | ||
|
|
0cc285f87b | ||
|
|
ca08f1217d | ||
|
|
690f07272e | ||
|
|
5fc6fcb310 | ||
|
|
b6884388a1 | ||
|
|
0582999bd1 | ||
|
|
398894b856 | ||
|
|
30a87b4cd5 | ||
|
|
72f91767a8 | ||
|
|
3bf8819fa2 | ||
|
|
c300efc0e1 | ||
|
|
cae260a158 | ||
|
|
04b591dc1d | ||
|
|
686120ee4a | ||
|
|
fabc9f29b8 | ||
|
|
c5949bfe31 | ||
|
|
7e677d15a4 | ||
|
|
af546be205 | ||
|
|
9aacf425dc | ||
|
|
9845637359 | ||
|
|
87fd6e0095 | ||
|
|
9896d309cb | ||
|
|
f2e7f8bab8 | ||
|
|
447212fa65 | ||
|
|
651cc0e5b4 | ||
|
|
dba615098d | ||
|
|
e3c289dbe9 | ||
|
|
e38410799b | ||
|
|
0edfea450b | ||
|
|
3cbbe22fab | ||
|
|
0a8888d1c7 | ||
|
|
7210ed1b87 | ||
|
|
c25c43d8c8 | ||
|
|
ac6974777e | ||
|
|
736b5d5913 | ||
|
|
95863b258b | ||
|
|
f1533160aa | ||
|
|
df49d37b71 | ||
|
|
ae628d4af2 | ||
|
|
040e783232 | ||
|
|
e727dbc3a3 | ||
|
|
b40369942c | ||
|
|
fca523d661 | ||
|
|
5e9db09761 | ||
|
|
e513cd2beb | ||
|
|
fb8eb539fc | ||
|
|
3d14567d0b | ||
|
|
925e08b858 | ||
|
|
5cbca85535 | ||
|
|
ecfe9345cf | ||
|
|
84543b459c | ||
|
|
e2d330aeed | ||
|
|
007211e7a2 | ||
|
|
7506d680ac | ||
|
|
38f550708d | ||
|
|
176e1058f1 | ||
|
|
91a74ba82a | ||
|
|
b66fd9ff4b | ||
|
|
278fddc317 | ||
|
|
49d61b2e4b | ||
|
|
1c080a8239 | ||
|
|
41a0b08e64 | ||
|
|
b016eb0895 | ||
|
|
f9a3bf6ccc | ||
|
|
3058029fba |
@@ -9,6 +9,10 @@ GTAGS
|
||||
# ccls
|
||||
/.ccls-cache
|
||||
|
||||
# auto-generated compilation database
|
||||
compile_commands.json
|
||||
rust-project.json
|
||||
|
||||
result
|
||||
result-*
|
||||
|
||||
@@ -29,3 +33,6 @@ buildtime.bin
|
||||
/.pre-commit-config.yaml
|
||||
/.nocontribmsg
|
||||
/release
|
||||
|
||||
# Rust build files when using Cargo (not actually supported for building but it spews the files anyway)
|
||||
/target/
|
||||
|
||||
Generated
+6
-22
@@ -2,12 +2,6 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "countme"
|
||||
version = "3.0.1"
|
||||
@@ -16,15 +10,15 @@ checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
|
||||
|
||||
[[package]]
|
||||
name = "dissimilar"
|
||||
version = "1.0.7"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86e3bdc80eee6e16b2b6b0f87fbc98c04bee3455e35174c0de1a125d0688c632"
|
||||
checksum = "59f8e79d1fbf76bdfbde321e902714bf6c49df88a7dda6fc682fc2979226962d"
|
||||
|
||||
[[package]]
|
||||
name = "expect-test"
|
||||
version = "1.4.1"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30d9eafeadd538e68fb28016364c9732d78e420b9ff8853fa5e4058861e9f8d3"
|
||||
checksum = "9e0be0a561335815e06dab7c62e50353134c796e7a6155402a64bcff66b6a5e0"
|
||||
dependencies = [
|
||||
"dissimilar",
|
||||
"once_cell",
|
||||
@@ -45,15 +39,6 @@ dependencies = [
|
||||
"rowan",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
@@ -71,13 +56,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rowan"
|
||||
version = "0.15.15"
|
||||
version = "0.15.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a58fa8a7ccff2aec4f39cc45bf5f985cec7125ab271cf681c279fd00192b49"
|
||||
checksum = "0a542b0253fa46e632d27a1dc5cf7b930de4df8659dc6e720b647fc72147ae3d"
|
||||
dependencies = [
|
||||
"countme",
|
||||
"hashbrown",
|
||||
"memoffset",
|
||||
"rustc-hash",
|
||||
"text-size",
|
||||
]
|
||||
@@ -0,0 +1,6 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["src/lix-doc"]
|
||||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
+6
-1
@@ -1,9 +1,14 @@
|
||||
# Usually "experimental" or "deprecated"
|
||||
kind:
|
||||
# "xp" or "dp"
|
||||
kindShort:
|
||||
|
||||
with builtins;
|
||||
with import ./utils.nix;
|
||||
|
||||
let
|
||||
showExperimentalFeature = name: doc: ''
|
||||
- [`${name}`](@docroot@/contributing/experimental-features.md#xp-feature-${name})
|
||||
- [`${name}`](@docroot@/contributing/${kind}-features.md#${kindShort}-feature-${name})
|
||||
'';
|
||||
in
|
||||
xps: indent " " (concatStrings (attrValues (mapAttrs showExperimentalFeature xps)))
|
||||
@@ -0,0 +1,18 @@
|
||||
# Usually "experimental" or "deprecated"
|
||||
_kind:
|
||||
# "xp" or "dp"
|
||||
kindShort:
|
||||
|
||||
with builtins;
|
||||
with import ./utils.nix;
|
||||
|
||||
let
|
||||
showFeature =
|
||||
name: doc:
|
||||
squash ''
|
||||
## [`${name}`]{#${kindShort}-feature-${name}}
|
||||
|
||||
${doc}
|
||||
'';
|
||||
in
|
||||
xps: (concatStringsSep "\n" (attrValues (mapAttrs showFeature xps)))
|
||||
@@ -1,13 +0,0 @@
|
||||
with builtins;
|
||||
with import ./utils.nix;
|
||||
|
||||
let
|
||||
showExperimentalFeature =
|
||||
name: doc:
|
||||
squash ''
|
||||
## [`${name}`]{#xp-feature-${name}}
|
||||
|
||||
${doc}
|
||||
'';
|
||||
in
|
||||
xps: (concatStringsSep "\n" (attrValues (mapAttrs showExperimentalFeature xps)))
|
||||
+12
-2
@@ -20,6 +20,8 @@ conf_file_json = custom_target(
|
||||
capture : true,
|
||||
output : 'conf-file.json',
|
||||
env : nix_env_for_docs,
|
||||
# FIXME: put the actual lib targets in here? meson have introspection challenge 2024 though.
|
||||
build_always_stale : true,
|
||||
)
|
||||
|
||||
nix_conf_file_md_body = custom_target(
|
||||
@@ -50,6 +52,8 @@ nix_exp_features_json = custom_target(
|
||||
command : [ nix, '__dump-xp-features' ],
|
||||
capture : true,
|
||||
output : 'xp-features.json',
|
||||
# FIXME: put the actual lib targets in here? meson have introspection challenge 2024 though.
|
||||
build_always_stale : true,
|
||||
)
|
||||
|
||||
language_json = custom_target(
|
||||
@@ -57,6 +61,8 @@ language_json = custom_target(
|
||||
output : 'language.json',
|
||||
capture : true,
|
||||
env : nix_env_for_docs,
|
||||
# FIXME: put the actual lib targets in here? meson have introspection challenge 2024 though.
|
||||
build_always_stale : true,
|
||||
)
|
||||
|
||||
nix3_cli_json = custom_target(
|
||||
@@ -64,6 +70,8 @@ nix3_cli_json = custom_target(
|
||||
capture : true,
|
||||
output : 'nix.json',
|
||||
env : nix_env_for_docs,
|
||||
# FIXME: put the actual lib targets in here? meson have introspection challenge 2024 though.
|
||||
build_always_stale : true,
|
||||
)
|
||||
|
||||
generate_manual_deps = files(
|
||||
@@ -72,9 +80,9 @@ generate_manual_deps = files(
|
||||
|
||||
# Generates builtins.md and builtin-constants.md.
|
||||
subdir('src/language')
|
||||
# Generates new-cli pages, experimental-features-shortlist.md, and conf-file.md.
|
||||
# Generates new-cli pages, {experimental,deprecated}-features-shortlist.md, and conf-file.md.
|
||||
subdir('src/command-ref')
|
||||
# Generates experimental-feature-descriptions.md.
|
||||
# Generates {experimental,deprecated}-feature-descriptions.md.
|
||||
subdir('src/contributing')
|
||||
# Generates rl-next-generated.md.
|
||||
subdir('src/release-notes')
|
||||
@@ -106,6 +114,8 @@ manual = custom_target(
|
||||
nix3_cli_files,
|
||||
experimental_features_shortlist_md,
|
||||
experimental_feature_descriptions_md,
|
||||
deprecated_features_shortlist_md,
|
||||
deprecated_feature_descriptions_md,
|
||||
conf_file_md,
|
||||
builtins_md,
|
||||
builtin_constants_md,
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
synopsis: "Build failures caused by `allowSubstitutes = false` while being the wrong system now produce a decent error"
|
||||
issues: [fj#484]
|
||||
cls: [1841]
|
||||
category: Fixes
|
||||
credits: jade
|
||||
---
|
||||
|
||||
Nix allows derivations to set `allowSubstitutes = false` in order to force them to be built locally without querying substituters for them.
|
||||
This is useful for derivations that are very fast to build (especially if they produce large output).
|
||||
However, this can shoot you in the foot if the derivation *has* to be substituted such as if the derivation is for another architecture, which is what `--always-allow-substitutes` is for.
|
||||
|
||||
Perhaps such derivations that are known to be impossible to build locally should ignore `allowSubstitutes` (irrespective of remote builders) in the future, but this at least reports the failure and solution directly.
|
||||
|
||||
```
|
||||
$ nix build -f fail.nix
|
||||
error: a 'unicornsandrainbows-linux' with features {} is required to build '/nix/store/...-meow.drv', but I am a 'x86_64-linux' with features {...}
|
||||
|
||||
Hint: the failing derivation has allowSubstitutes set to false, forcing it to be built rather than substituted.
|
||||
Passing --always-allow-substitutes to force substitution may resolve this failure if the path is available in a substituter.
|
||||
```
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
synopsis: Deprecated language features
|
||||
issues: [fj#437]
|
||||
cls: [1785, 1736, 1735, 1744]
|
||||
category: Breaking Changes
|
||||
credits: [piegames, horrors]
|
||||
---
|
||||
|
||||
A system for deprecation (and then the planned removal) of undesired language features has been put into place.
|
||||
It is controlled via feature flags much like experimental features, except that the deprecations are enabled default,
|
||||
and can be disabled via the flags for backwards compatibility (opt-out with `--extra-deprecated-features` or the Nix configuration file).
|
||||
|
||||
- `url-literals`: **URL literals** have long been obsolete and discouraged of use, and now they are officially deprecated.
|
||||
This means that all URLs must be properly put within quotes like all other strings.
|
||||
- `rec-set-overrides`: **__overrides** is an old arcane syntax which has not been in use for more than a decade.
|
||||
It is soft-deprecated with a warning only, with the plan to turn that into an error in a future release.
|
||||
- `ancient-let`: **The old `let` syntax** (`let { body = …; … }`) is soft-deprecated with a warning as well. Use the regular `let … in` instead.
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
synopsis: HTTP proxy environment variables are now respected for S3 binary cache stores
|
||||
issues: [fj#433]
|
||||
cls: [1788]
|
||||
category: Fixes
|
||||
credits: jade
|
||||
---
|
||||
|
||||
Due to "legacy reasons" (according to the AWS C++ SDK docs), the AWS SDK ignores system proxy configuration by default.
|
||||
We turned it back on.
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
synopsis: Relative and tilde paths in configuration
|
||||
issues: [fj#482]
|
||||
cls: [1851, 1863, 1864]
|
||||
category: Features
|
||||
credits: [9999years]
|
||||
---
|
||||
|
||||
[Configuration settings](@docroot@/command-ref/conf-file.md) can now refer to
|
||||
files with paths relative to the file they're written in or relative to your
|
||||
home directory (with `~/`).
|
||||
|
||||
This makes settings like
|
||||
[`repl-overlays`](@docroot@/command-ref/conf-file.md#conf-repl-overlays) and
|
||||
[`secret-key-files`](@docroot@/command-ref/conf-file.md#conf-repl-overlays)
|
||||
much easier to set, especially if you'd like to refer to files in an existing
|
||||
dotfiles repo cloned into your home directory.
|
||||
|
||||
If you put `repl-overlays = repl.nix` in your `~/.config/nix/nix.conf`, it'll
|
||||
load `~/.config/nix/repl.nix`. Similarly, you can set `repl-overlays =
|
||||
~/.dotfiles/repl.nix` to load a file relative to your home directory.
|
||||
|
||||
Configuration files can also
|
||||
[`include`](@docroot@/command-ref/conf-file.md#file-format) paths relative to
|
||||
your home directory.
|
||||
|
||||
Only user configuration files (like `$XDG_CONFIG_HOME/nix/nix.conf` or the
|
||||
files listed in `$NIX_USER_CONF_FILES`) can use tilde paths relative to your
|
||||
home directory. Configuration listed in the `$NIX_CONFIG` environment variable
|
||||
may not use relative paths.
|
||||
@@ -192,6 +192,7 @@
|
||||
- [Hacking](contributing/hacking.md)
|
||||
- [Testing](contributing/testing.md)
|
||||
- [Experimental Features](contributing/experimental-features.md)
|
||||
- [Deprecated Features](contributing/deprecated-features.md)
|
||||
- [CLI guideline](contributing/cli-guideline.md)
|
||||
- [C++ style guide](contributing/cxx.md)
|
||||
- [Release Notes](release-notes/release-notes.md)
|
||||
|
||||
@@ -1,23 +1,37 @@
|
||||
xp_features_json = custom_target(
|
||||
command : [nix, '__dump-xp-features'],
|
||||
capture : true,
|
||||
output : 'xp-features.json',
|
||||
)
|
||||
|
||||
experimental_features_shortlist_md = custom_target(
|
||||
command : nix_eval_for_docs + [
|
||||
'--expr',
|
||||
'import @INPUT0@ (builtins.fromJSON (builtins.readFile @INPUT1@))',
|
||||
'import @INPUT0@ "experimental" "xp" (builtins.fromJSON (builtins.readFile @INPUT1@))',
|
||||
],
|
||||
input : [
|
||||
'../../generate-xp-features-shortlist.nix',
|
||||
xp_features_json,
|
||||
'../../generate-features-shortlist.nix',
|
||||
nix_exp_features_json,
|
||||
],
|
||||
capture : true,
|
||||
output : 'experimental-features-shortlist.md',
|
||||
env : nix_env_for_docs,
|
||||
)
|
||||
|
||||
dp_features_json = custom_target(
|
||||
command : [nix, '__dump-dp-features'],
|
||||
capture : true,
|
||||
output : 'dp-features.json',
|
||||
)
|
||||
|
||||
deprecated_features_shortlist_md = custom_target(
|
||||
command : nix_eval_for_docs + [
|
||||
'--expr',
|
||||
'import @INPUT0@ "deprecated" "dp" (builtins.fromJSON (builtins.readFile @INPUT1@))',
|
||||
],
|
||||
input : [
|
||||
'../../generate-features-shortlist.nix',
|
||||
dp_features_json,
|
||||
],
|
||||
capture : true,
|
||||
output : 'deprecated-features-shortlist.md',
|
||||
env : nix_env_for_docs,
|
||||
)
|
||||
|
||||
# Intermediate step for manpage generation.
|
||||
# This splorks the output of generate-manpage.nix as JSON,
|
||||
# which gets written as a directory tree below.
|
||||
@@ -60,6 +74,7 @@ conf_file_md = custom_target(
|
||||
'../../utils.nix',
|
||||
conf_file_json,
|
||||
experimental_features_shortlist_md,
|
||||
deprecated_features_shortlist_md,
|
||||
],
|
||||
output : 'conf-file.md',
|
||||
env : nix_env_for_docs,
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
This section describes the notion of *deprecated features*, and how it fits into the big picture of the development of Lix.
|
||||
|
||||
# What are deprecated features?
|
||||
|
||||
Deprecated features are disabled by default, with the intent to eventually remove them.
|
||||
Users must explicitly enable them to keep using them, by toggling the associated [deprecated feature flags](@docroot@/command-ref/conf-file.md#conf-deprecated-features).
|
||||
This allows backwards compatibility and a graceful transition away from undesired features.
|
||||
|
||||
# Which features can be deprecated?
|
||||
|
||||
Undesired features should be soft-deprecated by yielding a warning when used for a significant amount of time before the can be deprecated.
|
||||
Legacy obsolete feature with little to no usage may go through this process faster.
|
||||
Deprecated features should have a migration path to a preferred alternative.
|
||||
|
||||
# Lifecycle of a deprecated feature
|
||||
|
||||
This description is not normative, but a feature removal may roughly happen like this:
|
||||
|
||||
1. Add a warning when the feature is being used.
|
||||
2. Disable the feature by default, putting it behind a deprecated feature flag.
|
||||
- If disabling the feature started out as an opt-in experimental feature, turn that experimental flag into a no-op or remove it entirely.
|
||||
For example, `--extra-experimental-features=no-url-literals` becomes `--extra-deprecated-features=url-literals`.
|
||||
3. Decide on a time frame for how long that feature will still be supported for backwards compatibility, and clearly communicate that in the error messages.
|
||||
- Sometimes, automatic migration to alternatives is possible, and such should be provided if possible
|
||||
- At least one NixOS release cycle should be the minimum
|
||||
4. Finally remove the feature entirely, only keeping the error message for those still using it.
|
||||
|
||||
# Relation to language versioning
|
||||
|
||||
Obviously, removing anything breaks backwards compatibility.
|
||||
In an ideal world, we'd have SemVer controls over the language and its features, cleanly allowing us to make breaking changes.
|
||||
See https://wiki.lix.systems/books/lix-contributors/page/language-versioning and [RFC 137](https://github.com/nixos/rfcs/pull/137) for efforts on that.
|
||||
However, we do not live in such an ideal world, and currently this goal is so far away, that "just disable it with some back-compat for a couple of years" is the most realistic solution, especially for comparatively minor changes.
|
||||
|
||||
# Currently available deprecated features
|
||||
|
||||
{{#include @generated@/contributing/deprecated-feature-descriptions.md}}
|
||||
@@ -4,12 +4,25 @@
|
||||
experimental_feature_descriptions_md = custom_target(
|
||||
command : nix_eval_for_docs + [
|
||||
'--expr',
|
||||
'import @INPUT0@ (builtins.fromJSON (builtins.readFile @INPUT1@))',
|
||||
'import @INPUT0@ "experimental" "xp" (builtins.fromJSON (builtins.readFile @INPUT1@))',
|
||||
],
|
||||
input : [
|
||||
'../../generate-xp-features.nix',
|
||||
xp_features_json,
|
||||
'../../generate-features.nix',
|
||||
nix_exp_features_json,
|
||||
],
|
||||
capture : true,
|
||||
output : 'experimental-feature-descriptions.md',
|
||||
)
|
||||
|
||||
deprecated_feature_descriptions_md = custom_target(
|
||||
command : nix_eval_for_docs + [
|
||||
'--expr',
|
||||
'import @INPUT0@ "deprecated" "dp" (builtins.fromJSON (builtins.readFile @INPUT1@))',
|
||||
],
|
||||
input : [
|
||||
'../../generate-features.nix',
|
||||
dp_features_json,
|
||||
],
|
||||
capture : true,
|
||||
output : 'deprecated-feature-descriptions.md',
|
||||
)
|
||||
|
||||
@@ -36,7 +36,10 @@ All users of the Lix daemon may do the following to bring things into the Nix st
|
||||
- Input-addressed, so they are run in the sandbox with no network access, with the following exceptions:
|
||||
|
||||
- The (poorly named, since it is not *just* about chroot) property `__noChroot` is set on the derivation and `sandbox` is set to `relaxed`.
|
||||
- On macOS, the derivation property `__darwinAllowLocalNetworking` allows network access to localhost from input-addressed derivations regardless of the `sandbox` setting value. This property exists with such semantics because macOS has no network namespace equivalent to isolate individual processes' localhost networking.
|
||||
- On macOS, the derivation property `__darwinAllowLocalNetworking` allows network access to localhost from input-addressed derivations regardless of the `sandbox` setting value.
|
||||
This property exists with such semantics because macOS has no network namespace equivalent to isolate individual processes' localhost networking.
|
||||
- On macOS, the derivation property `__sandboxProfile` accepts extra sandbox profile S-expressions, allowing derivations to bypass arbitrary parts of the sandbox without altogether disabling it.
|
||||
This is only permitted when `sandbox` is set to `relaxed`.
|
||||
- Output-addressed, so they are run with network access but their result must match an expected hash.
|
||||
|
||||
Trusted users may set any setting, including `sandbox = false`, so the sandbox state can be different at runtime from what is described in `nix.conf` for builds invoked with such settings.
|
||||
|
||||
@@ -77,12 +77,6 @@
|
||||
}
|
||||
```
|
||||
|
||||
Finally, as a convenience, *URIs* as defined in appendix B of
|
||||
[RFC 2396](http://www.ietf.org/rfc/rfc2396.txt) can be written *as
|
||||
is*, without quotes. For instance, the string
|
||||
`"http://example.org/foo.tar.bz2"` can also be written as
|
||||
`http://example.org/foo.tar.bz2`.
|
||||
|
||||
- <a id="type-number" href="#type-number">Number</a>
|
||||
|
||||
Numbers, which can be *integers* (like `123`) or *floating point*
|
||||
|
||||
+49
-9
@@ -30,6 +30,14 @@
|
||||
# FIXME: This hack should be removed when https://git.lix.systems/lix-project/lix/issues/359
|
||||
# is fixed.
|
||||
#
|
||||
# lix-doc is built with Meson in lix-doc/meson.build, and linked into libcmd in
|
||||
# src/libcmd/meson.build. When building outside the Nix sandbox, Meson will use the .wrap
|
||||
# files in subprojects/ to download and extract the dependency crates into subprojects/.
|
||||
# When building inside the Nix sandbox, Lix's derivation in package.nix uses a
|
||||
# fixed-output derivation to fetch those crates in advance instead, and then symlinks
|
||||
# them into subprojects/ with the same names that Meson uses when downloading them
|
||||
# itself -- perfect for --wrap-mode=nodownload, which mesonConfigurePhase uses.
|
||||
#
|
||||
# Unit tests are setup in tests/unit/meson.build, under the test suite "check".
|
||||
#
|
||||
# Functional tests are a bit more complicated. Generally they're defined in
|
||||
@@ -38,10 +46,11 @@
|
||||
# be placed in specific directories' meson.build files to create the right directory tree
|
||||
# in the build directory.
|
||||
|
||||
project('lix', 'cpp',
|
||||
project('lix', 'cpp', 'rust',
|
||||
version : run_command('bash', '-c', 'echo -n $(jq -r .version < ./version.json)$VERSION_SUFFIX', check : true).stdout().strip(),
|
||||
default_options : [
|
||||
'cpp_std=c++2a',
|
||||
'rust_std=2021',
|
||||
# TODO(Qyriad): increase the warning level
|
||||
'warning_level=1',
|
||||
'debug=true',
|
||||
@@ -138,6 +147,17 @@ if should_pch
|
||||
# Unlike basically everything else that takes a file, Meson requires the arguments to
|
||||
# cpp_pch : to be strings and doesn't accept files(). So absolute path it is.
|
||||
cpp_pch = [meson.project_source_root() / 'src/pch/precompiled-headers.hh']
|
||||
|
||||
# Saves about 400s (30% at time of writing) from compile time on-cpu, mostly
|
||||
# by removing instantiations of nlohmann from every single damned compilation
|
||||
# unit.
|
||||
# There is no equivalent in gcc.
|
||||
if cxx.get_id() == 'clang'
|
||||
add_project_arguments(
|
||||
'-fpch-instantiate-templates',
|
||||
language : 'cpp',
|
||||
)
|
||||
endif
|
||||
else
|
||||
cpp_pch = []
|
||||
endif
|
||||
@@ -322,13 +342,6 @@ pegtl = dependency(
|
||||
|
||||
nlohmann_json = dependency('nlohmann_json', required : true, include_type : 'system')
|
||||
|
||||
# lix-doc is a Rust project provided via buildInputs and unfortunately doesn't have any way to be detected.
|
||||
# Just declare it manually to resolve this.
|
||||
#
|
||||
# FIXME: build this with meson in the future after we drop Make (with which we
|
||||
# *absolutely* are not going to make it work)
|
||||
lix_doc = declare_dependency(link_args : [ '-llix_doc' ])
|
||||
|
||||
if is_freebsd
|
||||
libprocstat = declare_dependency(link_args : [ '-lprocstat' ])
|
||||
endif
|
||||
@@ -417,7 +430,7 @@ check_funcs = [
|
||||
'strsignal',
|
||||
'sysconf',
|
||||
]
|
||||
if target_machine.kernel() in ['linux', 'freebsd']
|
||||
if is_linux or is_freebsd
|
||||
# musl does not have close_range as of 2024-08-10
|
||||
# patch: https://www.openwall.com/lists/musl/2024/08/01/9
|
||||
check_funcs += [ 'close_range' ]
|
||||
@@ -543,6 +556,33 @@ if cxx.get_id() in ['clang', 'gcc']
|
||||
)
|
||||
endif
|
||||
|
||||
# Until Meson 1.5¹, we can't just give Meson a Cargo.lock file and be done with it.
|
||||
# Meson will *detect* what dependencies are needed from Cargo files; it just won't
|
||||
# fetch them. The Meson 1.5 feature essentially internally translates Cargo.lock entries
|
||||
# to .wrap files, and that translation is incredibly straightforward, so let's just
|
||||
# use a simple Python script to generate the .wrap files ourselves while we wait for
|
||||
# Meson 1.5. Weirdly, it seems Meson will only detect dependencies from other
|
||||
# dependency() calls, so we have to specify lix-doc's two top-level dependencies,
|
||||
# rnix and rowan, manually, and then their dependencies will be recursively translated
|
||||
# into more dependency() calls.
|
||||
#
|
||||
# When Meson translates a Cargo dependency, the string passed to `dependency()` follows
|
||||
# a fixed format, which is important as the .wrap files' basenames must match the string
|
||||
# passed to `dependency()` exactly.
|
||||
# In Meson 1.4, this format is `$packageName-rs`. Meson 1.5 changes this to
|
||||
# `$packageName-$shortenedVersionString-rs`, because of course it does, but we'll cross
|
||||
# that bridge when we get there...
|
||||
#
|
||||
# [1]: https://github.com/mesonbuild/meson/commit/9b8378985dbdc0112d11893dd42b33b7bc8d1e62
|
||||
# FIXME: remove (along with its generated wrap files) when we get rid of meson 1.4
|
||||
run_command(
|
||||
python,
|
||||
meson.project_source_root() / 'meson/cargo-lock-to-wraps.py',
|
||||
meson.project_source_root() / 'Cargo.lock',
|
||||
meson.project_source_root() / 'subprojects',
|
||||
check : true,
|
||||
)
|
||||
|
||||
if is_darwin
|
||||
configure_file(
|
||||
input : 'misc/launchd/org.nixos.nix-daemon.plist.in',
|
||||
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import tomllib
|
||||
import sys
|
||||
|
||||
DOWNLOAD_URI_FORMAT = 'https://crates.io/api/v1/crates/{crate}/{version}/download'
|
||||
|
||||
WRAP_TEMPLATE = """
|
||||
[wrap-file]
|
||||
method = cargo
|
||||
directory = {crate}-{version}
|
||||
source_url = {url}
|
||||
source_filename = {crate}-{version}.tar.gz
|
||||
source_hash = {hash}
|
||||
""".lstrip()
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('lockfile', help='path to the Cargo lockfile to generate wraps from')
|
||||
parser.add_argument('outdir', help="the 'subprojects' directory to write .wrap files to")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
with open(args.lockfile, 'rb') as f:
|
||||
lock_toml = tomllib.load(f)
|
||||
|
||||
for dependency in lock_toml['package']:
|
||||
try:
|
||||
hash = dependency['checksum']
|
||||
except KeyError:
|
||||
# The base package, e.g. lix-doc, won't have a checksum, and conveniently
|
||||
# the base package is also not something we want a wrap file for.
|
||||
# Doesn't that work out nicely?
|
||||
continue
|
||||
|
||||
crate = dependency['name']
|
||||
version = dependency['version']
|
||||
|
||||
url = DOWNLOAD_URI_FORMAT.format(crate=crate, version=version)
|
||||
|
||||
wrap_text = WRAP_TEMPLATE.format(crate=crate, version=version, url=url, hash=hash)
|
||||
with open(f'{args.outdir}/{crate}-rs.wrap', 'w') as f:
|
||||
f.write(wrap_text)
|
||||
Executable
+89
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Runs run-clang-tidy. A bit meta. Maybe it will replace run-clang-tidy one day
|
||||
because the run-clang-tidy UX is so questionable.
|
||||
"""
|
||||
|
||||
# I hereby dedicate this script to fuck you meson.
|
||||
# I cannot simply write my code to invoke a subprocess in a meson file because
|
||||
# Meson corrupts backslashes in command line args to subprocesses.
|
||||
# This is allegedly for "Windows support", but last time I checked Windows
|
||||
# neither needs nor wants you to corrupt its command lines.
|
||||
# https://github.com/mesonbuild/meson/issues/1564
|
||||
|
||||
import multiprocessing
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def default_concurrency():
|
||||
return min(multiprocessing.cpu_count(),
|
||||
int(os.environ.get("NIX_BUILD_CORES", "16")))
|
||||
|
||||
|
||||
def go(exe: str, plugin_path: Path, compile_commands_json_dir: Path, jobs: int,
|
||||
paths: list[Path], werror: bool, fix: bool):
|
||||
args = [
|
||||
# XXX: This explicitly invokes it with python because of a nixpkgs bug
|
||||
# where clang-unwrapped does not patch interpreters in run-clang-tidy.
|
||||
# However, making clang-unwrapped depend on python is also silly, so idk.
|
||||
sys.executable,
|
||||
exe,
|
||||
'-quiet',
|
||||
'-load',
|
||||
plugin_path,
|
||||
'-p',
|
||||
compile_commands_json_dir,
|
||||
'-j',
|
||||
str(jobs),
|
||||
'-header-filter',
|
||||
r'src/[^/]+/.*\.hh'
|
||||
]
|
||||
if werror:
|
||||
args += ['-warnings-as-errors', '*']
|
||||
if fix:
|
||||
args += ['-fix']
|
||||
args += ['--']
|
||||
args += paths
|
||||
os.execvp(sys.executable, args)
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
|
||||
ap = argparse.ArgumentParser(description='Runs run-clang-tidy for you')
|
||||
ap.add_argument('--jobs',
|
||||
'-j',
|
||||
type=int,
|
||||
default=default_concurrency(),
|
||||
help='Parallel linting jobs to run')
|
||||
ap.add_argument('--plugin-path',
|
||||
type=Path,
|
||||
help='Path to the Lix clang-tidy plugin')
|
||||
# FIXME: maybe we should integrate this so it just fixes the compdb for you and throws it in a tempdir?
|
||||
ap.add_argument(
|
||||
'--compdb-path',
|
||||
type=Path,
|
||||
help=
|
||||
'Path to the directory containing the fixed-up compilation database from clean_compdb'
|
||||
)
|
||||
ap.add_argument('--werror',
|
||||
action='store_true',
|
||||
help='Warnings get turned into errors')
|
||||
ap.add_argument('--fix',
|
||||
action='store_true',
|
||||
help='Apply fixes for warnings')
|
||||
ap.add_argument('--run-clang-tidy-path',
|
||||
default='run-clang-tidy',
|
||||
help='Path to run-clang-tidy')
|
||||
ap.add_argument('paths', nargs='*', help='Source paths to check')
|
||||
args = ap.parse_args()
|
||||
|
||||
go(args.run_clang_tidy_path, args.plugin_path, args.compdb_path, args.jobs,
|
||||
args.paths, args.werror, args.fix)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -13,8 +13,8 @@ def process_compdb(compdb: list[dict]) -> list[dict]:
|
||||
out = []
|
||||
eat_next = False
|
||||
for i, arg in enumerate(args):
|
||||
if arg == '-fpch-preprocess':
|
||||
# as used with gcc
|
||||
if arg in ['-fpch-preprocess', '-fpch-instantiate-templates']:
|
||||
# -fpch-preprocess as used with gcc, -fpch-instantiate-templates as used by clang
|
||||
continue
|
||||
elif arg == '-include-pch' or (arg == '-include' and args[i + 1] == 'precompiled-headers.hh'):
|
||||
# -include-pch some-pch (clang), or -include some-pch (gcc)
|
||||
@@ -30,7 +30,14 @@ def process_compdb(compdb: list[dict]) -> list[dict]:
|
||||
item['command'] = shlex.join(munch_command(shlex.split(item['command'])))
|
||||
return item
|
||||
|
||||
return [chomp(x) for x in compdb if not x['file'].endswith('precompiled-headers.hh')]
|
||||
def cmdfilter(item: dict) -> bool:
|
||||
file = item['file']
|
||||
return (
|
||||
not file.endswith('precompiled-headers.hh')
|
||||
and not file.endswith('.rs')
|
||||
)
|
||||
|
||||
return [chomp(x) for x in compdb if cmdfilter(x)]
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -58,26 +58,17 @@ build_all_generated_headers = custom_target(
|
||||
|
||||
if lix_clang_tidy_so_found
|
||||
run_clang_tidy_args = [
|
||||
'-load',
|
||||
lix_clang_tidy_so,
|
||||
'-p',
|
||||
# We have to workaround a run-clang-tidy bug too, so we must give the
|
||||
# directory name rather than the actual compdb file.
|
||||
# https://github.com/llvm/llvm-project/issues/101440
|
||||
meson.current_build_dir(),
|
||||
'-quiet',
|
||||
meson.current_source_dir() / 'clang-tidy-runner.py',
|
||||
'--run-clang-tidy-path', run_clang_tidy,
|
||||
'--compdb-path', meson.current_build_dir(),
|
||||
'--plugin-path', lix_clang_tidy_so,
|
||||
]
|
||||
run_target(
|
||||
'clang-tidy',
|
||||
command : [
|
||||
# XXX: This explicitly invokes it with python because of a nixpkgs bug
|
||||
# where clang-unwrapped does not patch interpreters in run-clang-tidy.
|
||||
# However, making clang-unwrapped depend on python is also silly, so idk.
|
||||
python,
|
||||
run_clang_tidy,
|
||||
run_clang_tidy_args,
|
||||
'-warnings-as-errors',
|
||||
'*',
|
||||
'--werror',
|
||||
],
|
||||
depends : [
|
||||
build_all_generated_headers,
|
||||
@@ -87,9 +78,8 @@ if lix_clang_tidy_so_found
|
||||
'clang-tidy-fix',
|
||||
command : [
|
||||
python,
|
||||
run_clang_tidy,
|
||||
run_clang_tidy_args,
|
||||
'-fix',
|
||||
'--fix',
|
||||
],
|
||||
depends : [
|
||||
build_all_generated_headers,
|
||||
|
||||
+18
-5
@@ -41,6 +41,8 @@
|
||||
pkg-config,
|
||||
python3,
|
||||
rapidcheck,
|
||||
rustPlatform,
|
||||
rustc,
|
||||
sqlite,
|
||||
toml11,
|
||||
util-linuxMinimal ? utillinuxMinimal,
|
||||
@@ -49,9 +51,6 @@
|
||||
|
||||
busybox-sandbox-shell,
|
||||
|
||||
# internal fork of nix-doc providing :doc in the repl
|
||||
lix-doc ? __forDefaults.lix-doc,
|
||||
|
||||
pname ? "lix",
|
||||
versionSuffix ? "",
|
||||
officialRelease ? __forDefaults.versionJson.official_release,
|
||||
@@ -83,7 +82,6 @@
|
||||
configureFlags = prev.configureFlags or [ ] ++ [ (lib.enableFeature true "sigstop") ];
|
||||
});
|
||||
|
||||
lix-doc = callPackage ./lix-doc/package.nix { };
|
||||
build-release-notes = callPackage ./maintainers/build-release-notes.nix { };
|
||||
},
|
||||
}:
|
||||
@@ -139,6 +137,8 @@ let
|
||||
./meson
|
||||
./scripts/meson.build
|
||||
./subprojects
|
||||
# Required for meson to generate Cargo wraps
|
||||
./Cargo.lock
|
||||
]);
|
||||
|
||||
functionalTestFiles = fileset.unions [
|
||||
@@ -219,6 +219,7 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
meson
|
||||
ninja
|
||||
cmake
|
||||
rustc
|
||||
]
|
||||
++ [
|
||||
(lib.getBin lowdown)
|
||||
@@ -258,7 +259,6 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
lowdown
|
||||
libsodium
|
||||
toml11
|
||||
lix-doc
|
||||
pegtl
|
||||
]
|
||||
++ lib.optionals hostPlatform.isLinux [
|
||||
@@ -288,8 +288,15 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
env = {
|
||||
BOOST_INCLUDEDIR = "${lib.getDev boost}/include";
|
||||
BOOST_LIBRARYDIR = "${lib.getLib boost}/lib";
|
||||
|
||||
# Meson allows referencing a /usr/share/cargo/registry shaped thing for subproject sources.
|
||||
# Turns out the Nix-generated Cargo dependencies are named the same as they
|
||||
# would be in a Cargo registry cache.
|
||||
MESON_PACKAGE_CACHE_DIR = finalAttrs.cargoDeps;
|
||||
};
|
||||
|
||||
cargoDeps = rustPlatform.importCargoLock { lockFile = ./Cargo.lock; };
|
||||
|
||||
preConfigure =
|
||||
lib.optionalString (!finalAttrs.dontBuild && !hostPlatform.isStatic) ''
|
||||
# Copy libboost_context so we don't get all of Boost in our closure.
|
||||
@@ -425,6 +432,10 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
pre-commit-checks,
|
||||
contribNotice,
|
||||
check-syscalls,
|
||||
|
||||
# debuggers
|
||||
gdb,
|
||||
rr,
|
||||
}:
|
||||
let
|
||||
glibcFix = lib.optionalAttrs (buildPlatform.isLinux && glibcLocales != null) {
|
||||
@@ -504,6 +515,8 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
]
|
||||
++ lib.optional (pre-commit-checks ? enabledPackages) pre-commit-checks.enabledPackages
|
||||
++ lib.optional (lib.meta.availableOn buildPlatform clangbuildanalyzer) clangbuildanalyzer
|
||||
++ lib.optional (!stdenv.isDarwin) gdb
|
||||
++ lib.optional (lib.meta.availableOn buildPlatform rr) rr
|
||||
++ finalAttrs.checkInputs;
|
||||
|
||||
shellHook = ''
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <iomanip>
|
||||
#if __APPLE__
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
@@ -18,6 +14,7 @@
|
||||
#include "build-result.hh"
|
||||
#include "store-api.hh"
|
||||
#include "derivations.hh"
|
||||
#include "strings.hh"
|
||||
#include "local-store.hh"
|
||||
#include "legacy.hh"
|
||||
#include "experimental-features.hh"
|
||||
|
||||
@@ -20,13 +20,15 @@ struct SingleBuiltPathBuilt {
|
||||
DECLARE_CMP(SingleBuiltPathBuilt);
|
||||
};
|
||||
|
||||
using _SingleBuiltPathRaw = std::variant<
|
||||
namespace built_path::detail {
|
||||
using SingleBuiltPathRaw = std::variant<
|
||||
DerivedPathOpaque,
|
||||
SingleBuiltPathBuilt
|
||||
>;
|
||||
}
|
||||
|
||||
struct SingleBuiltPath : _SingleBuiltPathRaw {
|
||||
using Raw = _SingleBuiltPathRaw;
|
||||
struct SingleBuiltPath : built_path::detail::SingleBuiltPathRaw {
|
||||
using Raw = built_path::detail::SingleBuiltPathRaw;
|
||||
using Raw::Raw;
|
||||
|
||||
using Opaque = DerivedPathOpaque;
|
||||
@@ -65,17 +67,19 @@ struct BuiltPathBuilt {
|
||||
DECLARE_CMP(BuiltPathBuilt);
|
||||
};
|
||||
|
||||
using _BuiltPathRaw = std::variant<
|
||||
namespace built_path::detail {
|
||||
using BuiltPathRaw = std::variant<
|
||||
DerivedPath::Opaque,
|
||||
BuiltPathBuilt
|
||||
>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A built path. Similar to a DerivedPath, but enriched with the corresponding
|
||||
* output path(s).
|
||||
*/
|
||||
struct BuiltPath : _BuiltPathRaw {
|
||||
using Raw = _BuiltPathRaw;
|
||||
struct BuiltPath : built_path::detail::BuiltPathRaw {
|
||||
using Raw = built_path::detail::BuiltPathRaw;
|
||||
using Raw::Raw;
|
||||
|
||||
using Opaque = DerivedPathOpaque;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "editor-for.hh"
|
||||
#include "environment-variables.hh"
|
||||
#include "source-path.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ libcmd = library(
|
||||
editline,
|
||||
lowdown,
|
||||
nlohmann_json,
|
||||
lix_doc
|
||||
liblix_doc,
|
||||
],
|
||||
cpp_pch : cpp_pch,
|
||||
install : true,
|
||||
|
||||
+1
-1
@@ -926,7 +926,7 @@ void NixRepl::loadFiles()
|
||||
|
||||
void NixRepl::loadReplOverlays()
|
||||
{
|
||||
if (!evalSettings.replOverlays) {
|
||||
if (evalSettings.replOverlays.get().empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "error.hh"
|
||||
#include "types.hh"
|
||||
#include "pos-idx.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -31,7 +31,7 @@ Value * EvalState::allocValue()
|
||||
#endif
|
||||
|
||||
nrValues++;
|
||||
return (Value *) p;
|
||||
return static_cast<Value *>(p);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,10 +54,10 @@ Env & EvalState::allocEnv(size_t size)
|
||||
void * p = *env1AllocCache;
|
||||
*env1AllocCache = GC_NEXT(p);
|
||||
GC_NEXT(p) = nullptr;
|
||||
env = (Env *) p;
|
||||
env = static_cast<Env *>(p);
|
||||
} else
|
||||
#endif
|
||||
env = (Env *) gcAllocBytes(sizeof(Env) + size * sizeof(Value *));
|
||||
env = static_cast<Env *>(gcAllocBytes(sizeof(Env) + size * sizeof(Value *)));
|
||||
|
||||
/* We assume that env->values has been cleared by the allocator; maybeThunk() and lookupVar fromWith expect this. */
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ struct EvalSettings : Config
|
||||
This is useful for debugging warnings in third-party Nix code.
|
||||
)"};
|
||||
|
||||
PathsSetting replOverlays{this, Paths(), "repl-overlays",
|
||||
PathsSetting<Paths> replOverlays{this, Paths(), "repl-overlays",
|
||||
R"(
|
||||
A list of files containing Nix expressions that can be used to add
|
||||
default bindings to [`nix
|
||||
@@ -185,6 +185,54 @@ struct EvalSettings : Config
|
||||
else
|
||||
{ }
|
||||
```
|
||||
|
||||
Here's a more elaborate `repl-overlay`, which provides the following
|
||||
variables:
|
||||
- The original, unmodified variables are aliased to `original`.
|
||||
- `legacyPackages.${system}` (if it exists) or `packages.${system}`
|
||||
(otherwise) is aliased to `pkgs`.
|
||||
- All attribute set variables with a `${system}` attribute are
|
||||
abbreviated in the same manner; e.g. `devShells.${system}` is
|
||||
shortened to `devShells`.
|
||||
|
||||
For example, the following attribute set:
|
||||
|
||||
```nix
|
||||
info: final: attrs: let
|
||||
# Equivalent to nixpkgs `lib.optionalAttrs`.
|
||||
optionalAttrs = predicate: attrs:
|
||||
if predicate
|
||||
then attrs
|
||||
else {};
|
||||
|
||||
# If `attrs.${oldName}.${info.currentSystem}` exists, alias `${newName}` to
|
||||
# it.
|
||||
collapseRenamed = oldName: newName:
|
||||
optionalAttrs (builtins.hasAttr oldName attrs
|
||||
&& builtins.hasAttr info.currentSystem attrs.${oldName})
|
||||
{
|
||||
${newName} = attrs.${oldName}.${info.currentSystem};
|
||||
};
|
||||
|
||||
# Alias `attrs.${oldName}.${info.currentSystem} to `${newName}`.
|
||||
collapse = name: collapseRenamed name name;
|
||||
|
||||
# Alias all `attrs` keys with an `${info.currentSystem}` attribute.
|
||||
collapseAll =
|
||||
builtins.foldl'
|
||||
(prev: name: prev // collapse name)
|
||||
{}
|
||||
(builtins.attrNames attrs);
|
||||
in
|
||||
# Preserve the original bindings as `original`.
|
||||
(optionalAttrs (! attrs ? original)
|
||||
{
|
||||
original = attrs;
|
||||
})
|
||||
// (collapseRenamed "packages" "pkgs")
|
||||
// (collapseRenamed "legacyPackages" "pkgs")
|
||||
// collapseAll
|
||||
```
|
||||
)"};
|
||||
};
|
||||
|
||||
|
||||
+15
-4
@@ -19,6 +19,7 @@
|
||||
#include "gc-small-vector.hh"
|
||||
#include "fetch-to-store.hh"
|
||||
#include "flake/flakeref.hh"
|
||||
#include "exit.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
@@ -249,6 +250,7 @@ EvalState::EvalState(
|
||||
.findFile = symbols.create("__findFile"),
|
||||
.nixPath = symbols.create("__nixPath"),
|
||||
.body = symbols.create("body"),
|
||||
.overrides = symbols.create("__overrides"),
|
||||
}
|
||||
, repair(NoRepair)
|
||||
, emptyBindings(0)
|
||||
@@ -2710,20 +2712,29 @@ Expr & EvalState::parseExprFromFile(const SourcePath & path, std::shared_ptr<Sta
|
||||
}
|
||||
|
||||
|
||||
Expr & EvalState::parseExprFromString(std::string s_, const SourcePath & basePath, std::shared_ptr<StaticEnv> & staticEnv, const ExperimentalFeatureSettings & xpSettings)
|
||||
Expr & EvalState::parseExprFromString(
|
||||
std::string s_,
|
||||
const SourcePath & basePath,
|
||||
std::shared_ptr<StaticEnv> & staticEnv,
|
||||
const FeatureSettings & featureSettings
|
||||
)
|
||||
{
|
||||
// NOTE this method (and parseStdin) must take care to *fully copy* their input
|
||||
// into their respective Pos::Origin until the parser stops overwriting its input
|
||||
// data.
|
||||
auto s = make_ref<std::string>(s_);
|
||||
s_.append("\0\0", 2);
|
||||
return *parse(s_.data(), s_.size(), Pos::String{.source = s}, basePath, staticEnv, xpSettings);
|
||||
return *parse(s_.data(), s_.size(), Pos::String{.source = s}, basePath, staticEnv, featureSettings);
|
||||
}
|
||||
|
||||
|
||||
Expr & EvalState::parseExprFromString(std::string s, const SourcePath & basePath, const ExperimentalFeatureSettings & xpSettings)
|
||||
Expr & EvalState::parseExprFromString(
|
||||
std::string s,
|
||||
const SourcePath & basePath,
|
||||
const FeatureSettings & featureSettings
|
||||
)
|
||||
{
|
||||
return parseExprFromString(std::move(s), basePath, staticBaseEnv, xpSettings);
|
||||
return parseExprFromString(std::move(s), basePath, staticBaseEnv, featureSettings);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+14
-4
@@ -12,6 +12,7 @@
|
||||
#include "experimental-features.hh"
|
||||
#include "search-path.hh"
|
||||
#include "repl-exit-status.hh"
|
||||
#include "backed-string-view.hh"
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
@@ -344,8 +345,17 @@ public:
|
||||
/**
|
||||
* Parse a Nix expression from the specified string.
|
||||
*/
|
||||
Expr & parseExprFromString(std::string s, const SourcePath & basePath, std::shared_ptr<StaticEnv> & staticEnv, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||
Expr & parseExprFromString(std::string s, const SourcePath & basePath, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||
Expr & parseExprFromString(
|
||||
std::string s,
|
||||
const SourcePath & basePath,
|
||||
std::shared_ptr<StaticEnv> & staticEnv,
|
||||
const FeatureSettings & xpSettings = featureSettings
|
||||
);
|
||||
Expr & parseExprFromString(
|
||||
std::string s,
|
||||
const SourcePath & basePath,
|
||||
const FeatureSettings & xpSettings = featureSettings
|
||||
);
|
||||
|
||||
Expr & parseStdin();
|
||||
|
||||
@@ -569,7 +579,7 @@ private:
|
||||
Pos::Origin origin,
|
||||
const SourcePath & basePath,
|
||||
std::shared_ptr<StaticEnv> & staticEnv,
|
||||
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||
const FeatureSettings & xpSettings = featureSettings);
|
||||
|
||||
/**
|
||||
* Current Nix call stack depth, used with `max-call-depth` setting to throw stack overflow hopefully before we run out of system stack.
|
||||
@@ -782,4 +792,4 @@ static constexpr std::string_view corepkgsPrefix{"/__corepkgs__/"};
|
||||
|
||||
}
|
||||
|
||||
#include "eval-inline.hh"
|
||||
#include "eval-inline.hh" // IWYU pragma: keep
|
||||
|
||||
@@ -342,8 +342,21 @@ static void updateOverrides(std::map<InputPath, FlakeInput> & overrideMap, const
|
||||
for (auto & [id, input] : overrides) {
|
||||
auto inputPath(inputPathPrefix);
|
||||
inputPath.push_back(id);
|
||||
// Do not override existing assignment from outer flake
|
||||
overrideMap.insert({inputPath, input});
|
||||
|
||||
/* Given
|
||||
*
|
||||
* { inputs.hydra.inputs.nix-eval-jobs.inputs.lix.follows = "lix"; }
|
||||
*
|
||||
* then `nix-eval-jobs` doesn't have an override.
|
||||
* It's neither replaced using follows nor by a different
|
||||
* URL. Thus no need to add it to overrides and thus re-fetch
|
||||
* it.
|
||||
*/
|
||||
if (input.ref || input.follows) {
|
||||
// Do not override existing assignment from outer flake
|
||||
overrideMap.insert({inputPath, input});
|
||||
}
|
||||
|
||||
updateOverrides(overrideMap, input.overrides, inputPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,7 @@ inline T * gcAllocType(size_t howMany = 1)
|
||||
// However, people can and do request zero sized allocations, so we need
|
||||
// to check that neither of our multiplicands were zero before complaining
|
||||
// about it.
|
||||
// NOLINTNEXTLINE(bugprone-sizeof-expression): yeah we only seem to alloc pointers with this. the calculation *is* correct though!
|
||||
auto checkedSz = checked::Checked<size_t>(howMany) * sizeof(T);
|
||||
size_t sz = checkedSz.valueWrapping();
|
||||
if (checkedSz.overflowed()) {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
namespace nix {
|
||||
|
||||
ExprBlackHole eBlackHole;
|
||||
Expr *eBlackHoleAddr = &eBlackHole;
|
||||
|
||||
// FIXME: remove, because *symbols* are abstract and do not have a single
|
||||
// textual representation; see printIdentifier()
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "eval-error.hh"
|
||||
#include "pos-idx.hh"
|
||||
#include "pos-table.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -48,7 +49,7 @@ protected:
|
||||
|
||||
public:
|
||||
struct AstSymbols {
|
||||
Symbol sub, lessThan, mul, div, or_, findFile, nixPath, body;
|
||||
Symbol sub, lessThan, mul, div, or_, findFile, nixPath, body, overrides;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ struct ExprState
|
||||
|
||||
std::unique_ptr<Expr> pipe(PosIdx pos, State & state, bool flip = false)
|
||||
{
|
||||
if (!state.xpSettings.isEnabled(Xp::PipeOperator))
|
||||
if (!state.featureSettings.isEnabled(Xp::PipeOperator))
|
||||
throw ParseError({
|
||||
.msg = HintFmt("Pipe operator is disabled"),
|
||||
.pos = state.positions[pos]
|
||||
@@ -656,10 +656,10 @@ template<> struct BuildAST<grammar::expr::path> : p::maybe_nothing {};
|
||||
|
||||
template<> struct BuildAST<grammar::expr::uri> {
|
||||
static void apply(const auto & in, ExprState & s, State & ps) {
|
||||
bool noURLLiterals = ps.xpSettings.isEnabled(Xp::NoUrlLiterals);
|
||||
if (noURLLiterals)
|
||||
bool URLLiterals = ps.featureSettings.isEnabled(Dep::UrlLiterals);
|
||||
if (!URLLiterals)
|
||||
throw ParseError({
|
||||
.msg = HintFmt("URL literals are disabled"),
|
||||
.msg = HintFmt("URL literals are deprecated, allow using them with --extra-deprecated-features=url-literals"),
|
||||
.pos = ps.positions[ps.at(in)]
|
||||
});
|
||||
s.pushExpr<ExprString>(ps.at(in), in.string());
|
||||
@@ -668,6 +668,16 @@ template<> struct BuildAST<grammar::expr::uri> {
|
||||
|
||||
template<> struct BuildAST<grammar::expr::ancient_let> : change_head<BindingsState> {
|
||||
static void success(const auto & in, BindingsState & b, ExprState & s, State & ps) {
|
||||
// Added 2024-09-18. Turn into an error at some point in the future.
|
||||
// See the documentation on deprecated features for more details.
|
||||
if (!ps.featureSettings.isEnabled(Dep::AncientLet))
|
||||
warn(
|
||||
"%s found at %s. This feature is deprecated and will be removed in the future. Use %s to silence this warning.",
|
||||
"let {",
|
||||
ps.positions[ps.at(in)],
|
||||
"--extra-deprecated-features ancient-let"
|
||||
);
|
||||
|
||||
b.attrs.pos = ps.at(in);
|
||||
b.attrs.recursive = true;
|
||||
s.pushExpr<ExprSelect>(b.attrs.pos, b.attrs.pos, std::make_unique<ExprAttrs>(std::move(b.attrs)), ps.s.body);
|
||||
@@ -676,6 +686,12 @@ template<> struct BuildAST<grammar::expr::ancient_let> : change_head<BindingsSta
|
||||
|
||||
template<> struct BuildAST<grammar::expr::rec_set> : change_head<BindingsState> {
|
||||
static void success(const auto & in, BindingsState & b, ExprState & s, State & ps) {
|
||||
// Before inserting new attrs, check for __override and throw an error
|
||||
// (the error will initially be a warning to ease migration)
|
||||
if (!featureSettings.isEnabled(Dep::RecSetOverrides) && b.attrs.attrs.contains(ps.s.overrides)) {
|
||||
ps.overridesFound(ps.at(in));
|
||||
}
|
||||
|
||||
b.attrs.pos = ps.at(in);
|
||||
b.attrs.recursive = true;
|
||||
s.pushExpr<ExprAttrs>(b.attrs.pos, std::move(b.attrs));
|
||||
@@ -858,7 +874,7 @@ Expr * EvalState::parse(
|
||||
Pos::Origin origin,
|
||||
const SourcePath & basePath,
|
||||
std::shared_ptr<StaticEnv> & staticEnv,
|
||||
const ExperimentalFeatureSettings & xpSettings)
|
||||
const FeatureSettings & featureSettings)
|
||||
{
|
||||
parser::State s = {
|
||||
symbols,
|
||||
@@ -866,7 +882,7 @@ Expr * EvalState::parse(
|
||||
basePath,
|
||||
positions.addOrigin(origin, length),
|
||||
exprSymbols,
|
||||
xpSettings
|
||||
featureSettings,
|
||||
};
|
||||
parser::ExprState x;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
///@file
|
||||
|
||||
#include "eval.hh"
|
||||
#include "logging.hh"
|
||||
|
||||
namespace nix::parser {
|
||||
|
||||
@@ -19,10 +20,11 @@ struct State
|
||||
SourcePath basePath;
|
||||
PosTable::Origin origin;
|
||||
const Expr::AstSymbols & s;
|
||||
const ExperimentalFeatureSettings & xpSettings;
|
||||
const FeatureSettings & featureSettings;
|
||||
|
||||
void dupAttr(const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos);
|
||||
void dupAttr(Symbol attr, const PosIdx pos, const PosIdx prevPos);
|
||||
void overridesFound(const PosIdx pos);
|
||||
void addAttr(ExprAttrs * attrs, AttrPath && attrPath, std::unique_ptr<Expr> e, const PosIdx pos);
|
||||
std::unique_ptr<Formals> validateFormals(std::unique_ptr<Formals> formals, PosIdx pos = noPos, Symbol arg = {});
|
||||
std::unique_ptr<Expr> stripIndentation(const PosIdx pos,
|
||||
@@ -58,6 +60,17 @@ inline void State::dupAttr(Symbol attr, const PosIdx pos, const PosIdx prevPos)
|
||||
});
|
||||
}
|
||||
|
||||
inline void State::overridesFound(const PosIdx pos) {
|
||||
// Added 2024-09-18. Turn into an error at some point in the future.
|
||||
// See the documentation on deprecated features for more details.
|
||||
warn(
|
||||
"%s found at %s. This feature is deprecated and will be removed in the future. Use %s to silence this warning.",
|
||||
"__overrides",
|
||||
positions[pos],
|
||||
"--extra-deprecated-features rec-set-overrides"
|
||||
);
|
||||
}
|
||||
|
||||
inline void State::addAttr(ExprAttrs * attrs, AttrPath && attrPath, std::unique_ptr<Expr> e, const PosIdx pos)
|
||||
{
|
||||
AttrPath::iterator i;
|
||||
@@ -123,6 +136,12 @@ inline void State::addAttr(ExprAttrs * attrs, AttrPath && attrPath, std::unique_
|
||||
dupAttr(attrPath, pos, j->second.pos);
|
||||
}
|
||||
} else {
|
||||
// Before inserting new attrs, check for __override and throw an error
|
||||
// (the error will initially be a warning to ease migration)
|
||||
if (attrs->recursive && !featureSettings.isEnabled(Dep::RecSetOverrides) && i->symbol == s.overrides) {
|
||||
overridesFound(pos);
|
||||
}
|
||||
|
||||
// This attr path is not defined. Let's create it.
|
||||
e->setName(i->symbol);
|
||||
attrs->attrs.emplace(std::piecewise_construct,
|
||||
|
||||
+14
-21
@@ -136,7 +136,9 @@ class ExternalValueBase
|
||||
|
||||
std::ostream & operator << (std::ostream & str, const ExternalValueBase & v);
|
||||
|
||||
extern ExprBlackHole eBlackHole;
|
||||
/** This is just the address of eBlackHole. It exists because eBlackHole has an
|
||||
* incomplete type at usage sites so is not possible to cast. */
|
||||
extern Expr *eBlackHoleAddr;
|
||||
|
||||
struct NewValueAs
|
||||
{
|
||||
@@ -196,6 +198,7 @@ private:
|
||||
public:
|
||||
|
||||
// Discount `using NewValueAs::*;`
|
||||
// NOLINTNEXTLINE(bugprone-macro-parentheses)
|
||||
#define USING_VALUETYPE(name) using name = NewValueAs::name
|
||||
USING_VALUETYPE(integer_t);
|
||||
USING_VALUETYPE(floating_t);
|
||||
@@ -473,7 +476,7 @@ public:
|
||||
/// Constructs an evil thunk, whose evaluation represents infinite recursion.
|
||||
explicit Value(blackhole_t)
|
||||
: internalType(tThunk)
|
||||
, thunk({ .env = nullptr, .expr = reinterpret_cast<Expr *>(&eBlackHole) })
|
||||
, thunk({ .env = nullptr, .expr = eBlackHoleAddr })
|
||||
{ }
|
||||
|
||||
Value(Value const & rhs) = default;
|
||||
@@ -513,7 +516,10 @@ public:
|
||||
// type() == nThunk
|
||||
inline bool isThunk() const { return internalType == tThunk; };
|
||||
inline bool isApp() const { return internalType == tApp; };
|
||||
inline bool isBlackhole() const;
|
||||
inline bool isBlackhole() const
|
||||
{
|
||||
return internalType == tThunk && thunk.expr == eBlackHoleAddr;
|
||||
}
|
||||
|
||||
// type() == nFunction
|
||||
inline bool isLambda() const { return internalType == tLambda; };
|
||||
@@ -669,11 +675,6 @@ public:
|
||||
|
||||
void mkStringMove(const char * s, const NixStringContext & context);
|
||||
|
||||
inline void mkString(const Symbol & s)
|
||||
{
|
||||
mkString(((const std::string &) s).c_str());
|
||||
}
|
||||
|
||||
void mkPath(const SourcePath & path);
|
||||
|
||||
inline void mkPath(const char * path)
|
||||
@@ -732,7 +733,11 @@ public:
|
||||
lambda.fun = f;
|
||||
}
|
||||
|
||||
inline void mkBlackhole();
|
||||
inline void mkBlackhole()
|
||||
{
|
||||
internalType = tThunk;
|
||||
thunk.expr = eBlackHoleAddr;
|
||||
}
|
||||
|
||||
void mkPrimOp(PrimOp * p);
|
||||
|
||||
@@ -832,18 +837,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
bool Value::isBlackhole() const
|
||||
{
|
||||
return internalType == tThunk && thunk.expr == (Expr*) &eBlackHole;
|
||||
}
|
||||
|
||||
void Value::mkBlackhole()
|
||||
{
|
||||
internalType = tThunk;
|
||||
thunk.expr = (Expr*) &eBlackHole;
|
||||
}
|
||||
|
||||
using ValueVector = GcVector<Value *>;
|
||||
using ValueMap = GcMap<Symbol, Value *>;
|
||||
using ValueVectorMap = std::map<Symbol, ValueVector>;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
template<> AcceptFlakeConfig BaseSetting<AcceptFlakeConfig>::parse(const std::string & str) const
|
||||
template<> AcceptFlakeConfig BaseSetting<AcceptFlakeConfig>::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||
{
|
||||
if (str == "true") return AcceptFlakeConfig::True;
|
||||
else if (str == "ask") return AcceptFlakeConfig::Ask;
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include "path.hh"
|
||||
#include "attrs.hh"
|
||||
#include "url.hh"
|
||||
#include "ref.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#include "progress-bar.hh"
|
||||
#include "file-system.hh"
|
||||
#include "sync.hh"
|
||||
#include "store-api.hh"
|
||||
#include "names.hh"
|
||||
#include "terminal.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
#include <map>
|
||||
#include <thread>
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "loggers.hh"
|
||||
#include "current-process.hh"
|
||||
#include "terminal.hh"
|
||||
#include "strings.hh"
|
||||
#include "exit.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <exception>
|
||||
|
||||
@@ -7,12 +7,10 @@
|
||||
#include "path.hh"
|
||||
#include "derived-path.hh"
|
||||
#include "processes.hh"
|
||||
#include "exit.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#include <locale>
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "nar-accessor.hh"
|
||||
#include "thread-pool.hh"
|
||||
#include "signals.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
#include <chrono>
|
||||
#include <regex>
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
#include "derivation-goal.hh"
|
||||
#include "hook-instance.hh"
|
||||
#include "worker.hh"
|
||||
#include "builtins.hh"
|
||||
#include "builtins/buildenv.hh"
|
||||
#include "references.hh"
|
||||
#include "finally.hh"
|
||||
#include "archive.hh"
|
||||
#include "compression.hh"
|
||||
#include "common-protocol.hh"
|
||||
#include "common-protocol-impl.hh"
|
||||
#include "topo-sort.hh"
|
||||
#include "common-protocol-impl.hh" // IWYU pragma: keep
|
||||
#include "local-store.hh" // TODO remove, along with remaining downcasts
|
||||
#include "logging-json.hh"
|
||||
#include "substitution-goal.hh"
|
||||
#include "drv-output-substitution-goal.hh"
|
||||
|
||||
#include <regex>
|
||||
#include <queue>
|
||||
#include "strings.hh"
|
||||
|
||||
#include <fstream>
|
||||
#include <sys/types.h>
|
||||
@@ -65,8 +58,8 @@
|
||||
namespace nix {
|
||||
|
||||
DerivationGoal::DerivationGoal(const StorePath & drvPath,
|
||||
const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
|
||||
: Goal(worker, DerivedPath::Built { .drvPath = makeConstantStorePathRef(drvPath), .outputs = wantedOutputs })
|
||||
const OutputsSpec & wantedOutputs, Worker & worker, bool isDependency, BuildMode buildMode)
|
||||
: Goal(worker, isDependency)
|
||||
, useDerivation(true)
|
||||
, drvPath(drvPath)
|
||||
, wantedOutputs(wantedOutputs)
|
||||
@@ -78,13 +71,13 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath,
|
||||
DerivedPath::Built { makeConstantStorePathRef(drvPath), wantedOutputs }.to_string(worker.store));
|
||||
trace("created");
|
||||
|
||||
mcExpectedBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.expectedBuilds);
|
||||
mcExpectedBuilds = worker.expectedBuilds.addTemporarily(1);
|
||||
}
|
||||
|
||||
|
||||
DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
|
||||
const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
|
||||
: Goal(worker, DerivedPath::Built { .drvPath = makeConstantStorePathRef(drvPath), .outputs = wantedOutputs })
|
||||
const OutputsSpec & wantedOutputs, Worker & worker, bool isDependency, BuildMode buildMode)
|
||||
: Goal(worker, isDependency)
|
||||
, useDerivation(false)
|
||||
, drvPath(drvPath)
|
||||
, wantedOutputs(wantedOutputs)
|
||||
@@ -98,7 +91,7 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation
|
||||
DerivedPath::Built { makeConstantStorePathRef(drvPath), drv.outputNames() }.to_string(worker.store));
|
||||
trace("created");
|
||||
|
||||
mcExpectedBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.expectedBuilds);
|
||||
mcExpectedBuilds = worker.expectedBuilds.addTemporarily(1);
|
||||
|
||||
/* Prevent the .chroot directory from being
|
||||
garbage-collected. (See isActiveTempFile() in gc.cc.) */
|
||||
@@ -127,6 +120,7 @@ std::string DerivationGoal::key()
|
||||
void DerivationGoal::killChild()
|
||||
{
|
||||
hook.reset();
|
||||
builderOutFD = nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -137,9 +131,9 @@ Goal::Finished DerivationGoal::timedOut(Error && ex)
|
||||
}
|
||||
|
||||
|
||||
Goal::WorkResult DerivationGoal::work()
|
||||
Goal::WorkResult DerivationGoal::work(bool inBuildSlot)
|
||||
{
|
||||
return (this->*state)();
|
||||
return (this->*state)(inBuildSlot);
|
||||
}
|
||||
|
||||
void DerivationGoal::addWantedOutputs(const OutputsSpec & outputs)
|
||||
@@ -163,7 +157,7 @@ void DerivationGoal::addWantedOutputs(const OutputsSpec & outputs)
|
||||
}
|
||||
|
||||
|
||||
Goal::WorkResult DerivationGoal::getDerivation()
|
||||
Goal::WorkResult DerivationGoal::getDerivation(bool inBuildSlot)
|
||||
{
|
||||
trace("init");
|
||||
|
||||
@@ -171,16 +165,16 @@ Goal::WorkResult DerivationGoal::getDerivation()
|
||||
exists. If it doesn't, it may be created through a
|
||||
substitute. */
|
||||
if (buildMode == bmNormal && worker.evalStore.isValidPath(drvPath)) {
|
||||
return loadDerivation();
|
||||
return loadDerivation(inBuildSlot);
|
||||
}
|
||||
|
||||
|
||||
state = &DerivationGoal::loadDerivation;
|
||||
return WaitForGoals{{worker.makePathSubstitutionGoal(drvPath)}};
|
||||
return WaitForGoals{{worker.goalFactory().makePathSubstitutionGoal(drvPath)}};
|
||||
}
|
||||
|
||||
|
||||
Goal::WorkResult DerivationGoal::loadDerivation()
|
||||
Goal::WorkResult DerivationGoal::loadDerivation(bool inBuildSlot)
|
||||
{
|
||||
trace("loading derivation");
|
||||
|
||||
@@ -207,11 +201,11 @@ Goal::WorkResult DerivationGoal::loadDerivation()
|
||||
}
|
||||
assert(drv);
|
||||
|
||||
return haveDerivation();
|
||||
return haveDerivation(inBuildSlot);
|
||||
}
|
||||
|
||||
|
||||
Goal::WorkResult DerivationGoal::haveDerivation()
|
||||
Goal::WorkResult DerivationGoal::haveDerivation(bool inBuildSlot)
|
||||
{
|
||||
trace("have derivation");
|
||||
|
||||
@@ -239,7 +233,7 @@ Goal::WorkResult DerivationGoal::haveDerivation()
|
||||
});
|
||||
}
|
||||
|
||||
return gaveUpOnSubstitution();
|
||||
return gaveUpOnSubstitution(inBuildSlot);
|
||||
}
|
||||
|
||||
for (auto & i : drv->outputsAndOptPaths(worker.store))
|
||||
@@ -268,34 +262,39 @@ Goal::WorkResult DerivationGoal::haveDerivation()
|
||||
through substitutes. If that doesn't work, we'll build
|
||||
them. */
|
||||
WaitForGoals result;
|
||||
if (settings.useSubstitutes && parsedDrv->substitutesAllowed())
|
||||
for (auto & [outputName, status] : initialOutputs) {
|
||||
if (!status.wanted) continue;
|
||||
if (!status.known)
|
||||
result.goals.insert(
|
||||
worker.makeDrvOutputSubstitutionGoal(
|
||||
DrvOutput{status.outputHash, outputName},
|
||||
buildMode == bmRepair ? Repair : NoRepair
|
||||
)
|
||||
);
|
||||
else {
|
||||
auto * cap = getDerivationCA(*drv);
|
||||
result.goals.insert(worker.makePathSubstitutionGoal(
|
||||
status.known->path,
|
||||
buildMode == bmRepair ? Repair : NoRepair,
|
||||
cap ? std::optional { *cap } : std::nullopt));
|
||||
if (settings.useSubstitutes) {
|
||||
if (parsedDrv->substitutesAllowed()) {
|
||||
for (auto & [outputName, status] : initialOutputs) {
|
||||
if (!status.wanted) continue;
|
||||
if (!status.known)
|
||||
result.goals.insert(
|
||||
worker.goalFactory().makeDrvOutputSubstitutionGoal(
|
||||
DrvOutput{status.outputHash, outputName},
|
||||
buildMode == bmRepair ? Repair : NoRepair
|
||||
)
|
||||
);
|
||||
else {
|
||||
auto * cap = getDerivationCA(*drv);
|
||||
result.goals.insert(worker.goalFactory().makePathSubstitutionGoal(
|
||||
status.known->path,
|
||||
buildMode == bmRepair ? Repair : NoRepair,
|
||||
cap ? std::optional { *cap } : std::nullopt));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
trace("skipping substitute because allowSubstitutes is false");
|
||||
}
|
||||
}
|
||||
|
||||
if (result.goals.empty()) { /* to prevent hang (no wake-up event) */
|
||||
return outputsSubstitutionTried();
|
||||
return outputsSubstitutionTried(inBuildSlot);
|
||||
} else {
|
||||
state = &DerivationGoal::outputsSubstitutionTried;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Goal::WorkResult DerivationGoal::outputsSubstitutionTried()
|
||||
Goal::WorkResult DerivationGoal::outputsSubstitutionTried(bool inBuildSlot)
|
||||
{
|
||||
trace("all outputs substituted (maybe)");
|
||||
|
||||
@@ -338,7 +337,7 @@ Goal::WorkResult DerivationGoal::outputsSubstitutionTried()
|
||||
|
||||
if (needRestart == NeedRestartForMoreOutputs::OutputsAddedDoNeed) {
|
||||
needRestart = NeedRestartForMoreOutputs::OutputsUnmodifedDontNeed;
|
||||
return haveDerivation();
|
||||
return haveDerivation(inBuildSlot);
|
||||
}
|
||||
|
||||
auto [allValid, validOutputs] = checkPathValidity();
|
||||
@@ -354,13 +353,13 @@ Goal::WorkResult DerivationGoal::outputsSubstitutionTried()
|
||||
worker.store.printStorePath(drvPath));
|
||||
|
||||
/* Nothing to wait for; tail call */
|
||||
return gaveUpOnSubstitution();
|
||||
return gaveUpOnSubstitution(inBuildSlot);
|
||||
}
|
||||
|
||||
|
||||
/* At least one of the output paths could not be
|
||||
produced using a substitute. So we have to build instead. */
|
||||
Goal::WorkResult DerivationGoal::gaveUpOnSubstitution()
|
||||
Goal::WorkResult DerivationGoal::gaveUpOnSubstitution(bool inBuildSlot)
|
||||
{
|
||||
WaitForGoals result;
|
||||
|
||||
@@ -375,7 +374,7 @@ Goal::WorkResult DerivationGoal::gaveUpOnSubstitution()
|
||||
|
||||
addWaiteeDerivedPath = [&](ref<SingleDerivedPath> inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
|
||||
if (!inputNode.value.empty())
|
||||
result.goals.insert(worker.makeGoal(
|
||||
result.goals.insert(worker.goalFactory().makeGoal(
|
||||
DerivedPath::Built {
|
||||
.drvPath = inputDrv,
|
||||
.outputs = inputNode.value,
|
||||
@@ -420,11 +419,11 @@ Goal::WorkResult DerivationGoal::gaveUpOnSubstitution()
|
||||
if (!settings.useSubstitutes)
|
||||
throw Error("dependency '%s' of '%s' does not exist, and substitution is disabled",
|
||||
worker.store.printStorePath(i), worker.store.printStorePath(drvPath));
|
||||
result.goals.insert(worker.makePathSubstitutionGoal(i));
|
||||
result.goals.insert(worker.goalFactory().makePathSubstitutionGoal(i));
|
||||
}
|
||||
|
||||
if (result.goals.empty()) {/* to prevent hang (no wake-up event) */
|
||||
return inputsRealised();
|
||||
return inputsRealised(inBuildSlot);
|
||||
} else {
|
||||
state = &DerivationGoal::inputsRealised;
|
||||
return result;
|
||||
@@ -476,9 +475,9 @@ Goal::WorkResult DerivationGoal::repairClosure()
|
||||
worker.store.printStorePath(i), worker.store.printStorePath(drvPath));
|
||||
auto drvPath2 = outputsToDrv.find(i);
|
||||
if (drvPath2 == outputsToDrv.end())
|
||||
result.goals.insert(worker.makePathSubstitutionGoal(i, Repair));
|
||||
result.goals.insert(worker.goalFactory().makePathSubstitutionGoal(i, Repair));
|
||||
else
|
||||
result.goals.insert(worker.makeGoal(
|
||||
result.goals.insert(worker.goalFactory().makeGoal(
|
||||
DerivedPath::Built {
|
||||
.drvPath = makeConstantStorePathRef(drvPath2->second),
|
||||
.outputs = OutputsSpec::All { },
|
||||
@@ -495,7 +494,7 @@ Goal::WorkResult DerivationGoal::repairClosure()
|
||||
}
|
||||
|
||||
|
||||
Goal::WorkResult DerivationGoal::closureRepaired()
|
||||
Goal::WorkResult DerivationGoal::closureRepaired(bool inBuildSlot)
|
||||
{
|
||||
trace("closure repaired");
|
||||
if (nrFailed > 0)
|
||||
@@ -505,7 +504,7 @@ Goal::WorkResult DerivationGoal::closureRepaired()
|
||||
}
|
||||
|
||||
|
||||
Goal::WorkResult DerivationGoal::inputsRealised()
|
||||
Goal::WorkResult DerivationGoal::inputsRealised(bool inBuildSlot)
|
||||
{
|
||||
trace("all inputs realised");
|
||||
|
||||
@@ -519,7 +518,7 @@ Goal::WorkResult DerivationGoal::inputsRealised()
|
||||
|
||||
if (retrySubstitution == RetrySubstitution::YesNeed) {
|
||||
retrySubstitution = RetrySubstitution::AlreadyRetried;
|
||||
return haveDerivation();
|
||||
return haveDerivation(inBuildSlot);
|
||||
}
|
||||
|
||||
/* Gather information necessary for computing the closure and/or
|
||||
@@ -581,7 +580,7 @@ Goal::WorkResult DerivationGoal::inputsRealised()
|
||||
worker.store.printStorePath(pathResolved),
|
||||
});
|
||||
|
||||
resolvedDrvGoal = worker.makeDerivationGoal(
|
||||
resolvedDrvGoal = worker.goalFactory().makeDerivationGoal(
|
||||
pathResolved, wantedOutputs, buildMode);
|
||||
|
||||
state = &DerivationGoal::resolvedFinished;
|
||||
@@ -650,10 +649,10 @@ Goal::WorkResult DerivationGoal::inputsRealised()
|
||||
slot to become available, since we don't need one if there is a
|
||||
build hook. */
|
||||
state = &DerivationGoal::tryToBuild;
|
||||
return ContinueImmediately{};
|
||||
return tryToBuild(inBuildSlot);
|
||||
}
|
||||
|
||||
Goal::WorkResult DerivationGoal::started()
|
||||
void DerivationGoal::started()
|
||||
{
|
||||
auto msg = fmt(
|
||||
buildMode == bmRepair ? "repairing outputs of '%s'" :
|
||||
@@ -663,11 +662,10 @@ Goal::WorkResult DerivationGoal::started()
|
||||
if (hook) msg += fmt(" on '%s'", machineName);
|
||||
act = std::make_unique<Activity>(*logger, lvlInfo, actBuild, msg,
|
||||
Logger::Fields{worker.store.printStorePath(drvPath), hook ? machineName : "", 1, 1});
|
||||
mcRunningBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.runningBuilds);
|
||||
return StillAlive{};
|
||||
mcRunningBuilds = worker.runningBuilds.addTemporarily(1);
|
||||
}
|
||||
|
||||
Goal::WorkResult DerivationGoal::tryToBuild()
|
||||
Goal::WorkResult DerivationGoal::tryToBuild(bool inBuildSlot)
|
||||
{
|
||||
trace("trying to build");
|
||||
|
||||
@@ -739,35 +737,45 @@ Goal::WorkResult DerivationGoal::tryToBuild()
|
||||
&& settings.maxBuildJobs.get() != 0;
|
||||
|
||||
if (!buildLocally) {
|
||||
switch (tryBuildHook()) {
|
||||
case rpAccept:
|
||||
/* Yes, it has started doing so. Wait until we get
|
||||
EOF from the hook. */
|
||||
actLock.reset();
|
||||
buildResult.startTime = time(0); // inexact
|
||||
state = &DerivationGoal::buildDone;
|
||||
return started();
|
||||
case rpPostpone:
|
||||
/* Not now; wait until at least one child finishes or
|
||||
the wake-up timeout expires. */
|
||||
if (!actLock)
|
||||
actLock = std::make_unique<Activity>(*logger, lvlTalkative, actBuildWaiting,
|
||||
fmt("waiting for a machine to build '%s'", Magenta(worker.store.printStorePath(drvPath))));
|
||||
outputLocks.unlock();
|
||||
return WaitForAWhile{};
|
||||
case rpDecline:
|
||||
/* We should do it ourselves. */
|
||||
break;
|
||||
auto hookReply = tryBuildHook(inBuildSlot);
|
||||
auto result = std::visit(
|
||||
overloaded{
|
||||
[&](HookReply::Accept & a) -> std::optional<WorkResult> {
|
||||
/* Yes, it has started doing so. Wait until we get
|
||||
EOF from the hook. */
|
||||
actLock.reset();
|
||||
buildResult.startTime = time(0); // inexact
|
||||
state = &DerivationGoal::buildDone;
|
||||
started();
|
||||
return WaitForWorld{std::move(a.fds), false};
|
||||
},
|
||||
[&](HookReply::Postpone) -> std::optional<WorkResult> {
|
||||
/* Not now; wait until at least one child finishes or
|
||||
the wake-up timeout expires. */
|
||||
if (!actLock)
|
||||
actLock = std::make_unique<Activity>(*logger, lvlTalkative, actBuildWaiting,
|
||||
fmt("waiting for a machine to build '%s'", Magenta(worker.store.printStorePath(drvPath))));
|
||||
outputLocks.unlock();
|
||||
return WaitForAWhile{};
|
||||
},
|
||||
[&](HookReply::Decline) -> std::optional<WorkResult> {
|
||||
/* We should do it ourselves. */
|
||||
return std::nullopt;
|
||||
},
|
||||
},
|
||||
hookReply);
|
||||
if (result) {
|
||||
return std::move(*result);
|
||||
}
|
||||
}
|
||||
|
||||
actLock.reset();
|
||||
|
||||
state = &DerivationGoal::tryLocalBuild;
|
||||
return ContinueImmediately{};
|
||||
return tryLocalBuild(inBuildSlot);
|
||||
}
|
||||
|
||||
Goal::WorkResult DerivationGoal::tryLocalBuild() {
|
||||
Goal::WorkResult DerivationGoal::tryLocalBuild(bool inBuildSlot) {
|
||||
throw Error(
|
||||
"unable to build with a primary store that isn't a local store; "
|
||||
"either pass a different '--store' or enable remote builds."
|
||||
@@ -822,14 +830,16 @@ void replaceValidPath(const Path & storePath, const Path & tmpPath)
|
||||
|
||||
int DerivationGoal::getChildStatus()
|
||||
{
|
||||
builderOutFD = nullptr;
|
||||
return hook->pid.kill();
|
||||
}
|
||||
|
||||
|
||||
void DerivationGoal::closeReadPipes()
|
||||
{
|
||||
hook->builderOut.readSide.reset();
|
||||
hook->fromHook.readSide.reset();
|
||||
hook->builderOut.reset();
|
||||
hook->fromHook.reset();
|
||||
builderOutFD = nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -925,7 +935,7 @@ void runPostBuildHook(
|
||||
proc.getStdout()->drainInto(sink);
|
||||
}
|
||||
|
||||
Goal::WorkResult DerivationGoal::buildDone()
|
||||
Goal::WorkResult DerivationGoal::buildDone(bool inBuildSlot)
|
||||
{
|
||||
trace("build done");
|
||||
|
||||
@@ -1045,7 +1055,7 @@ Goal::WorkResult DerivationGoal::buildDone()
|
||||
}
|
||||
}
|
||||
|
||||
Goal::WorkResult DerivationGoal::resolvedFinished()
|
||||
Goal::WorkResult DerivationGoal::resolvedFinished(bool inBuildSlot)
|
||||
{
|
||||
trace("resolved derivation finished");
|
||||
|
||||
@@ -1116,9 +1126,9 @@ Goal::WorkResult DerivationGoal::resolvedFinished()
|
||||
return done(status, std::move(builtOutputs));
|
||||
}
|
||||
|
||||
HookReply DerivationGoal::tryBuildHook()
|
||||
HookReply DerivationGoal::tryBuildHook(bool inBuildSlot)
|
||||
{
|
||||
if (!worker.hook.available || !useDerivation) return rpDecline;
|
||||
if (!worker.hook.available || !useDerivation) return HookReply::Decline{};
|
||||
|
||||
if (!worker.hook.instance)
|
||||
worker.hook.instance = std::make_unique<HookInstance>();
|
||||
@@ -1128,7 +1138,7 @@ HookReply DerivationGoal::tryBuildHook()
|
||||
/* Send the request to the hook. */
|
||||
worker.hook.instance->sink
|
||||
<< "try"
|
||||
<< (worker.getNrLocalBuilds() < settings.maxBuildJobs ? 1 : 0)
|
||||
<< (inBuildSlot ? 1 : 0)
|
||||
<< drv->platform
|
||||
<< worker.store.printStorePath(drvPath)
|
||||
<< parsedDrv->getRequiredSystemFeatures();
|
||||
@@ -1140,7 +1150,7 @@ HookReply DerivationGoal::tryBuildHook()
|
||||
while (true) {
|
||||
auto s = [&]() {
|
||||
try {
|
||||
return readLine(worker.hook.instance->fromHook.readSide.get());
|
||||
return readLine(worker.hook.instance->fromHook.get());
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "while reading the response from the build hook");
|
||||
throw;
|
||||
@@ -1161,14 +1171,14 @@ HookReply DerivationGoal::tryBuildHook()
|
||||
debug("hook reply is '%1%'", reply);
|
||||
|
||||
if (reply == "decline")
|
||||
return rpDecline;
|
||||
return HookReply::Decline{};
|
||||
else if (reply == "decline-permanently") {
|
||||
worker.hook.available = false;
|
||||
worker.hook.instance.reset();
|
||||
return rpDecline;
|
||||
return HookReply::Decline{};
|
||||
}
|
||||
else if (reply == "postpone")
|
||||
return rpPostpone;
|
||||
return HookReply::Postpone{};
|
||||
else if (reply != "accept")
|
||||
throw Error("bad hook reply '%s'", reply);
|
||||
|
||||
@@ -1176,9 +1186,9 @@ HookReply DerivationGoal::tryBuildHook()
|
||||
if (e.errNo == EPIPE) {
|
||||
printError(
|
||||
"build hook died unexpectedly: %s",
|
||||
chomp(drainFD(worker.hook.instance->fromHook.readSide.get())));
|
||||
chomp(drainFD(worker.hook.instance->fromHook.get())));
|
||||
worker.hook.instance.reset();
|
||||
return rpDecline;
|
||||
return HookReply::Decline{};
|
||||
} else
|
||||
throw;
|
||||
}
|
||||
@@ -1186,7 +1196,7 @@ HookReply DerivationGoal::tryBuildHook()
|
||||
hook = std::move(worker.hook.instance);
|
||||
|
||||
try {
|
||||
machineName = readLine(hook->fromHook.readSide.get());
|
||||
machineName = readLine(hook->fromHook.get());
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "while reading the machine name from the build hook");
|
||||
throw;
|
||||
@@ -1209,17 +1219,17 @@ HookReply DerivationGoal::tryBuildHook()
|
||||
}
|
||||
|
||||
hook->sink = FdSink();
|
||||
hook->toHook.writeSide.reset();
|
||||
hook->toHook.reset();
|
||||
|
||||
/* Create the log file and pipe. */
|
||||
Path logFile = openLogFile();
|
||||
|
||||
std::set<int> fds;
|
||||
fds.insert(hook->fromHook.readSide.get());
|
||||
fds.insert(hook->builderOut.readSide.get());
|
||||
worker.childStarted(shared_from_this(), fds, false, false);
|
||||
fds.insert(hook->fromHook.get());
|
||||
fds.insert(hook->builderOut.get());
|
||||
builderOutFD = &hook->builderOut;
|
||||
|
||||
return rpAccept;
|
||||
return HookReply::Accept{std::move(fds)};
|
||||
}
|
||||
|
||||
|
||||
@@ -1279,24 +1289,23 @@ void DerivationGoal::closeLogFile()
|
||||
}
|
||||
|
||||
|
||||
bool DerivationGoal::isReadDesc(int fd)
|
||||
{
|
||||
return fd == hook->builderOut.readSide.get();
|
||||
}
|
||||
|
||||
Goal::WorkResult DerivationGoal::handleChildOutput(int fd, std::string_view data)
|
||||
{
|
||||
assert(builderOutFD);
|
||||
|
||||
auto tooMuchLogs = [&] {
|
||||
killChild();
|
||||
return done(
|
||||
BuildResult::LogLimitExceeded, {},
|
||||
Error("%s killed after writing more than %d bytes of log output",
|
||||
getName(), settings.maxLogSize));
|
||||
};
|
||||
|
||||
// local & `ssh://`-builds are dealt with here.
|
||||
auto isWrittenToLog = isReadDesc(fd);
|
||||
if (isWrittenToLog)
|
||||
{
|
||||
if (fd == builderOutFD->get()) {
|
||||
logSize += data.size();
|
||||
if (settings.maxLogSize && logSize > settings.maxLogSize) {
|
||||
killChild();
|
||||
return done(
|
||||
BuildResult::LogLimitExceeded, {},
|
||||
Error("%s killed after writing more than %d bytes of log output",
|
||||
getName(), settings.maxLogSize));
|
||||
return tooMuchLogs();
|
||||
}
|
||||
|
||||
for (auto c : data)
|
||||
@@ -1311,9 +1320,10 @@ Goal::WorkResult DerivationGoal::handleChildOutput(int fd, std::string_view data
|
||||
}
|
||||
|
||||
if (logSink) (*logSink)(data);
|
||||
return StillAlive{};
|
||||
}
|
||||
|
||||
if (hook && fd == hook->fromHook.readSide.get()) {
|
||||
if (hook && fd == hook->fromHook.get()) {
|
||||
for (auto c : data)
|
||||
if (c == '\n') {
|
||||
auto json = parseJSONMessage(currentHookLine);
|
||||
@@ -1321,11 +1331,17 @@ Goal::WorkResult DerivationGoal::handleChildOutput(int fd, std::string_view data
|
||||
auto s = handleJSONLogMessage(*json, worker.act, hook->activities, true);
|
||||
// ensure that logs from a builder using `ssh-ng://` as protocol
|
||||
// are also available to `nix log`.
|
||||
if (s && !isWrittenToLog && logSink) {
|
||||
if (s && logSink) {
|
||||
const auto type = (*json)["type"];
|
||||
const auto fields = (*json)["fields"];
|
||||
if (type == resBuildLogLine) {
|
||||
(*logSink)((fields.size() > 0 ? fields[0].get<std::string>() : "") + "\n");
|
||||
const std::string logLine =
|
||||
(fields.size() > 0 ? fields[0].get<std::string>() : "") + "\n";
|
||||
logSize += logLine.size();
|
||||
if (settings.maxLogSize && logSize > settings.maxLogSize) {
|
||||
return tooMuchLogs();
|
||||
}
|
||||
(*logSink)(logLine);
|
||||
} else if (type == resSetPhase && ! fields.is_null()) {
|
||||
const auto phase = fields[0];
|
||||
if (! phase.is_null()) {
|
||||
@@ -1528,9 +1544,14 @@ Goal::Finished DerivationGoal::done(
|
||||
fs << worker.store.printStorePath(drvPath) << "\t" << buildResult.toString() << std::endl;
|
||||
}
|
||||
|
||||
if (ex && isDependency) {
|
||||
logError(ex->info());
|
||||
}
|
||||
|
||||
return Finished{
|
||||
.result = buildResult.success() ? ecSuccess : ecFailed,
|
||||
.ex = ex ? std::make_unique<Error>(std::move(*ex)) : nullptr,
|
||||
.exitCode = buildResult.success() ? ecSuccess : ecFailed,
|
||||
.result = buildResult,
|
||||
.ex = ex ? std::make_shared<Error>(std::move(*ex)) : nullptr,
|
||||
.permanentFailure = buildResult.status == BuildResult::PermanentFailure,
|
||||
.timedOut = buildResult.status == BuildResult::TimedOut,
|
||||
.hashMismatch = anyHashMismatchSeen,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "notifying-counter.hh"
|
||||
#include "parsed-derivations.hh"
|
||||
#include "lock.hh"
|
||||
#include "outputs-spec.hh"
|
||||
@@ -14,7 +15,21 @@ using std::map;
|
||||
|
||||
struct HookInstance;
|
||||
|
||||
typedef enum {rpAccept, rpDecline, rpPostpone} HookReply;
|
||||
struct HookReplyBase {
|
||||
struct [[nodiscard]] Accept {
|
||||
std::set<int> fds;
|
||||
};
|
||||
struct [[nodiscard]] Decline {};
|
||||
struct [[nodiscard]] Postpone {};
|
||||
};
|
||||
|
||||
struct [[nodiscard]] HookReply
|
||||
: HookReplyBase,
|
||||
std::variant<HookReplyBase::Accept, HookReplyBase::Decline, HookReplyBase::Postpone>
|
||||
{
|
||||
HookReply() = delete;
|
||||
using variant::variant;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unless we are repairing, we don't both to test validity and just assume it,
|
||||
@@ -186,17 +201,24 @@ struct DerivationGoal : public Goal
|
||||
*/
|
||||
std::unique_ptr<HookInstance> hook;
|
||||
|
||||
/**
|
||||
* Builder output is pulled from this file descriptor when not null.
|
||||
* Owned by the derivation goal or subclass, must not be reset until
|
||||
* the build has finished and no more output must be processed by us
|
||||
*/
|
||||
AutoCloseFD * builderOutFD = nullptr;
|
||||
|
||||
/**
|
||||
* The sort of derivation we are building.
|
||||
*/
|
||||
std::optional<DerivationType> derivationType;
|
||||
|
||||
typedef WorkResult (DerivationGoal::*GoalState)();
|
||||
typedef WorkResult (DerivationGoal::*GoalState)(bool inBuildSlot);
|
||||
GoalState state;
|
||||
|
||||
BuildMode buildMode;
|
||||
|
||||
std::unique_ptr<MaintainCount<uint64_t>> mcExpectedBuilds, mcRunningBuilds;
|
||||
NotifyingCounter<uint64_t>::Bump mcExpectedBuilds, mcRunningBuilds;
|
||||
|
||||
std::unique_ptr<Activity> act;
|
||||
|
||||
@@ -213,10 +235,10 @@ struct DerivationGoal : public Goal
|
||||
std::string machineName;
|
||||
|
||||
DerivationGoal(const StorePath & drvPath,
|
||||
const OutputsSpec & wantedOutputs, Worker & worker,
|
||||
const OutputsSpec & wantedOutputs, Worker & worker, bool isDependency,
|
||||
BuildMode buildMode = bmNormal);
|
||||
DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
|
||||
const OutputsSpec & wantedOutputs, Worker & worker,
|
||||
const OutputsSpec & wantedOutputs, Worker & worker, bool isDependency,
|
||||
BuildMode buildMode = bmNormal);
|
||||
virtual ~DerivationGoal() noexcept(false);
|
||||
|
||||
@@ -224,7 +246,7 @@ struct DerivationGoal : public Goal
|
||||
|
||||
std::string key() override;
|
||||
|
||||
WorkResult work() override;
|
||||
WorkResult work(bool inBuildSlot) override;
|
||||
|
||||
/**
|
||||
* Add wanted outputs to an already existing derivation goal.
|
||||
@@ -234,23 +256,23 @@ struct DerivationGoal : public Goal
|
||||
/**
|
||||
* The states.
|
||||
*/
|
||||
WorkResult getDerivation();
|
||||
WorkResult loadDerivation();
|
||||
WorkResult haveDerivation();
|
||||
WorkResult outputsSubstitutionTried();
|
||||
WorkResult gaveUpOnSubstitution();
|
||||
WorkResult closureRepaired();
|
||||
WorkResult inputsRealised();
|
||||
WorkResult tryToBuild();
|
||||
virtual WorkResult tryLocalBuild();
|
||||
WorkResult buildDone();
|
||||
WorkResult getDerivation(bool inBuildSlot);
|
||||
WorkResult loadDerivation(bool inBuildSlot);
|
||||
WorkResult haveDerivation(bool inBuildSlot);
|
||||
WorkResult outputsSubstitutionTried(bool inBuildSlot);
|
||||
WorkResult gaveUpOnSubstitution(bool inBuildSlot);
|
||||
WorkResult closureRepaired(bool inBuildSlot);
|
||||
WorkResult inputsRealised(bool inBuildSlot);
|
||||
WorkResult tryToBuild(bool inBuildSlot);
|
||||
virtual WorkResult tryLocalBuild(bool inBuildSlot);
|
||||
WorkResult buildDone(bool inBuildSlot);
|
||||
|
||||
WorkResult resolvedFinished();
|
||||
WorkResult resolvedFinished(bool inBuildSlot);
|
||||
|
||||
/**
|
||||
* Is the build hook willing to perform the build?
|
||||
*/
|
||||
HookReply tryBuildHook();
|
||||
HookReply tryBuildHook(bool inBuildSlot);
|
||||
|
||||
virtual int getChildStatus();
|
||||
|
||||
@@ -290,8 +312,6 @@ struct DerivationGoal : public Goal
|
||||
virtual void cleanupPostOutputsRegisteredModeCheck();
|
||||
virtual void cleanupPostOutputsRegisteredModeNonCheck();
|
||||
|
||||
virtual bool isReadDesc(int fd);
|
||||
|
||||
/**
|
||||
* Callback used by the worker to write to the log.
|
||||
*/
|
||||
@@ -328,7 +348,7 @@ struct DerivationGoal : public Goal
|
||||
|
||||
WorkResult repairClosure();
|
||||
|
||||
WorkResult started();
|
||||
void started();
|
||||
|
||||
Finished done(
|
||||
BuildResult::Status status,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "drv-output-substitution-goal.hh"
|
||||
#include "build-result.hh"
|
||||
#include "finally.hh"
|
||||
#include "worker.hh"
|
||||
#include "substitution-goal.hh"
|
||||
@@ -9,9 +10,10 @@ namespace nix {
|
||||
DrvOutputSubstitutionGoal::DrvOutputSubstitutionGoal(
|
||||
const DrvOutput & id,
|
||||
Worker & worker,
|
||||
bool isDependency,
|
||||
RepairFlag repair,
|
||||
std::optional<ContentAddress> ca)
|
||||
: Goal(worker, DerivedPath::Opaque { StorePath::dummy })
|
||||
: Goal(worker, isDependency)
|
||||
, id(id)
|
||||
{
|
||||
state = &DrvOutputSubstitutionGoal::init;
|
||||
@@ -20,32 +22,28 @@ DrvOutputSubstitutionGoal::DrvOutputSubstitutionGoal(
|
||||
}
|
||||
|
||||
|
||||
Goal::WorkResult DrvOutputSubstitutionGoal::init()
|
||||
Goal::WorkResult DrvOutputSubstitutionGoal::init(bool inBuildSlot)
|
||||
{
|
||||
trace("init");
|
||||
|
||||
/* If the derivation already exists, we’re done */
|
||||
if (worker.store.queryRealisation(id)) {
|
||||
return Finished{ecSuccess};
|
||||
return Finished{ecSuccess, std::move(buildResult)};
|
||||
}
|
||||
|
||||
subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
|
||||
return tryNext();
|
||||
return tryNext(inBuildSlot);
|
||||
}
|
||||
|
||||
Goal::WorkResult DrvOutputSubstitutionGoal::tryNext()
|
||||
Goal::WorkResult DrvOutputSubstitutionGoal::tryNext(bool inBuildSlot)
|
||||
{
|
||||
trace("trying next substituter");
|
||||
|
||||
/* Make sure that we are allowed to start a substitution. Note that even
|
||||
if maxSubstitutionJobs == 0, we still allow a substituter to run. This
|
||||
prevents infinite waiting. */
|
||||
if (worker.runningSubstitutions >= std::max(1U, settings.maxSubstitutionJobs.get())) {
|
||||
if (!inBuildSlot) {
|
||||
return WaitForSlot{};
|
||||
}
|
||||
|
||||
maintainRunningSubstitutions =
|
||||
std::make_unique<MaintainCount<uint64_t>>(worker.runningSubstitutions);
|
||||
maintainRunningSubstitutions = worker.runningSubstitutions.addTemporarily(1);
|
||||
|
||||
if (subs.size() == 0) {
|
||||
/* None left. Terminate this goal and let someone else deal
|
||||
@@ -59,7 +57,7 @@ Goal::WorkResult DrvOutputSubstitutionGoal::tryNext()
|
||||
/* Hack: don't indicate failure if there were no substituters.
|
||||
In that case the calling derivation should just do a
|
||||
build. */
|
||||
return Finished{substituterFailed ? ecFailed : ecNoSubstituters};
|
||||
return Finished{substituterFailed ? ecFailed : ecNoSubstituters, std::move(buildResult)};
|
||||
}
|
||||
|
||||
sub = subs.front();
|
||||
@@ -78,13 +76,11 @@ Goal::WorkResult DrvOutputSubstitutionGoal::tryNext()
|
||||
return sub->queryRealisation(id);
|
||||
});
|
||||
|
||||
worker.childStarted(shared_from_this(), {downloadState->outPipe.readSide.get()}, true, false);
|
||||
|
||||
state = &DrvOutputSubstitutionGoal::realisationFetched;
|
||||
return StillAlive{};
|
||||
return WaitForWorld{{downloadState->outPipe.readSide.get()}, true};
|
||||
}
|
||||
|
||||
Goal::WorkResult DrvOutputSubstitutionGoal::realisationFetched()
|
||||
Goal::WorkResult DrvOutputSubstitutionGoal::realisationFetched(bool inBuildSlot)
|
||||
{
|
||||
worker.childTerminated(this);
|
||||
maintainRunningSubstitutions.reset();
|
||||
@@ -97,7 +93,7 @@ Goal::WorkResult DrvOutputSubstitutionGoal::realisationFetched()
|
||||
}
|
||||
|
||||
if (!outputInfo) {
|
||||
return tryNext();
|
||||
return tryNext(inBuildSlot);
|
||||
}
|
||||
|
||||
WaitForGoals result;
|
||||
@@ -114,23 +110,23 @@ Goal::WorkResult DrvOutputSubstitutionGoal::realisationFetched()
|
||||
worker.store.printStorePath(localOutputInfo->outPath),
|
||||
worker.store.printStorePath(depPath)
|
||||
);
|
||||
return tryNext();
|
||||
return tryNext(inBuildSlot);
|
||||
}
|
||||
result.goals.insert(worker.makeDrvOutputSubstitutionGoal(depId));
|
||||
result.goals.insert(worker.goalFactory().makeDrvOutputSubstitutionGoal(depId));
|
||||
}
|
||||
}
|
||||
|
||||
result.goals.insert(worker.makePathSubstitutionGoal(outputInfo->outPath));
|
||||
result.goals.insert(worker.goalFactory().makePathSubstitutionGoal(outputInfo->outPath));
|
||||
|
||||
if (result.goals.empty()) {
|
||||
return outPathValid();
|
||||
return outPathValid(inBuildSlot);
|
||||
} else {
|
||||
state = &DrvOutputSubstitutionGoal::outPathValid;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Goal::WorkResult DrvOutputSubstitutionGoal::outPathValid()
|
||||
Goal::WorkResult DrvOutputSubstitutionGoal::outPathValid(bool inBuildSlot)
|
||||
{
|
||||
assert(outputInfo);
|
||||
trace("output path substituted");
|
||||
@@ -138,7 +134,8 @@ Goal::WorkResult DrvOutputSubstitutionGoal::outPathValid()
|
||||
if (nrFailed > 0) {
|
||||
debug("The output path of the derivation output '%s' could not be substituted", id.to_string());
|
||||
return Finished{
|
||||
nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed
|
||||
nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed,
|
||||
std::move(buildResult),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -149,7 +146,7 @@ Goal::WorkResult DrvOutputSubstitutionGoal::outPathValid()
|
||||
Goal::WorkResult DrvOutputSubstitutionGoal::finished()
|
||||
{
|
||||
trace("finished");
|
||||
return Finished{ecSuccess};
|
||||
return Finished{ecSuccess, std::move(buildResult)};
|
||||
}
|
||||
|
||||
std::string DrvOutputSubstitutionGoal::key()
|
||||
@@ -159,9 +156,9 @@ std::string DrvOutputSubstitutionGoal::key()
|
||||
return "a$" + std::string(id.to_string());
|
||||
}
|
||||
|
||||
Goal::WorkResult DrvOutputSubstitutionGoal::work()
|
||||
Goal::WorkResult DrvOutputSubstitutionGoal::work(bool inBuildSlot)
|
||||
{
|
||||
return (this->*state)();
|
||||
return (this->*state)(inBuildSlot);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "notifying-counter.hh"
|
||||
#include "store-api.hh"
|
||||
#include "goal.hh"
|
||||
#include "realisation.hh"
|
||||
@@ -40,7 +41,7 @@ class DrvOutputSubstitutionGoal : public Goal {
|
||||
*/
|
||||
std::shared_ptr<Store> sub;
|
||||
|
||||
std::unique_ptr<MaintainCount<uint64_t>> maintainRunningSubstitutions;
|
||||
NotifyingCounter<uint64_t>::Bump maintainRunningSubstitutions;
|
||||
|
||||
struct DownloadState
|
||||
{
|
||||
@@ -56,22 +57,28 @@ class DrvOutputSubstitutionGoal : public Goal {
|
||||
bool substituterFailed = false;
|
||||
|
||||
public:
|
||||
DrvOutputSubstitutionGoal(const DrvOutput& id, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
||||
DrvOutputSubstitutionGoal(
|
||||
const DrvOutput & id,
|
||||
Worker & worker,
|
||||
bool isDependency,
|
||||
RepairFlag repair = NoRepair,
|
||||
std::optional<ContentAddress> ca = std::nullopt
|
||||
);
|
||||
|
||||
typedef WorkResult (DrvOutputSubstitutionGoal::*GoalState)();
|
||||
typedef WorkResult (DrvOutputSubstitutionGoal::*GoalState)(bool inBuildSlot);
|
||||
GoalState state;
|
||||
|
||||
WorkResult init();
|
||||
WorkResult tryNext();
|
||||
WorkResult realisationFetched();
|
||||
WorkResult outPathValid();
|
||||
WorkResult init(bool inBuildSlot);
|
||||
WorkResult tryNext(bool inBuildSlot);
|
||||
WorkResult realisationFetched(bool inBuildSlot);
|
||||
WorkResult outPathValid(bool inBuildSlot);
|
||||
WorkResult finished();
|
||||
|
||||
Finished timedOut(Error && ex) override { abort(); };
|
||||
|
||||
std::string key() override;
|
||||
|
||||
WorkResult work() override;
|
||||
WorkResult work(bool inBuildSlot) override;
|
||||
|
||||
JobCategory jobCategory() const override {
|
||||
return JobCategory::Substitution;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "substitution-goal.hh"
|
||||
#include "derivation-goal.hh"
|
||||
#include "local-store.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -9,20 +10,21 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
|
||||
{
|
||||
Worker worker(*this, evalStore ? *evalStore : *this);
|
||||
|
||||
Goals goals;
|
||||
for (auto & br : reqs)
|
||||
goals.insert(worker.makeGoal(br, buildMode));
|
||||
|
||||
worker.run(goals);
|
||||
auto goals = worker.run([&](GoalFactory & gf) {
|
||||
Goals goals;
|
||||
for (auto & br : reqs)
|
||||
goals.insert(gf.makeGoal(br, buildMode));
|
||||
return goals;
|
||||
});
|
||||
|
||||
StringSet failed;
|
||||
std::optional<Error> ex;
|
||||
std::shared_ptr<Error> ex;
|
||||
for (auto & i : goals) {
|
||||
if (i->ex) {
|
||||
if (ex)
|
||||
logError(i->ex->info());
|
||||
else
|
||||
ex = std::move(*i->ex);
|
||||
ex = i->ex;
|
||||
}
|
||||
if (i->exitCode != Goal::ecSuccess) {
|
||||
if (auto i2 = dynamic_cast<DerivationGoal *>(i.get()))
|
||||
@@ -47,17 +49,17 @@ std::vector<KeyedBuildResult> Store::buildPathsWithResults(
|
||||
std::shared_ptr<Store> evalStore)
|
||||
{
|
||||
Worker worker(*this, evalStore ? *evalStore : *this);
|
||||
|
||||
Goals goals;
|
||||
std::vector<std::pair<const DerivedPath &, GoalPtr>> state;
|
||||
|
||||
for (const auto & req : reqs) {
|
||||
auto goal = worker.makeGoal(req, buildMode);
|
||||
goals.insert(goal);
|
||||
state.push_back({req, goal});
|
||||
}
|
||||
|
||||
worker.run(goals);
|
||||
auto goals = worker.run([&](GoalFactory & gf) {
|
||||
Goals goals;
|
||||
for (const auto & req : reqs) {
|
||||
auto goal = gf.makeGoal(req, buildMode);
|
||||
goals.insert(goal);
|
||||
state.push_back({req, goal});
|
||||
}
|
||||
return goals;
|
||||
});
|
||||
|
||||
std::vector<KeyedBuildResult> results;
|
||||
|
||||
@@ -71,10 +73,12 @@ BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivat
|
||||
BuildMode buildMode)
|
||||
{
|
||||
Worker worker(*this, *this);
|
||||
auto goal = worker.makeBasicDerivationGoal(drvPath, drv, OutputsSpec::All {}, buildMode);
|
||||
|
||||
try {
|
||||
worker.run(Goals{goal});
|
||||
auto goals = worker.run([&](GoalFactory & gf) -> Goals {
|
||||
return Goals{gf.makeBasicDerivationGoal(drvPath, drv, OutputsSpec::All{}, buildMode)};
|
||||
});
|
||||
auto goal = *goals.begin();
|
||||
return goal->buildResult.restrictTo(DerivedPath::Built {
|
||||
.drvPath = makeConstantStorePathRef(drvPath),
|
||||
.outputs = OutputsSpec::All {},
|
||||
@@ -94,10 +98,10 @@ void Store::ensurePath(const StorePath & path)
|
||||
if (isValidPath(path)) return;
|
||||
|
||||
Worker worker(*this, *this);
|
||||
GoalPtr goal = worker.makePathSubstitutionGoal(path);
|
||||
Goals goals = {goal};
|
||||
|
||||
worker.run(goals);
|
||||
auto goals =
|
||||
worker.run([&](GoalFactory & gf) { return Goals{gf.makePathSubstitutionGoal(path)}; });
|
||||
auto goal = *goals.begin();
|
||||
|
||||
if (goal->exitCode != Goal::ecSuccess) {
|
||||
if (goal->ex) {
|
||||
@@ -112,23 +116,27 @@ void Store::ensurePath(const StorePath & path)
|
||||
void Store::repairPath(const StorePath & path)
|
||||
{
|
||||
Worker worker(*this, *this);
|
||||
GoalPtr goal = worker.makePathSubstitutionGoal(path, Repair);
|
||||
Goals goals = {goal};
|
||||
|
||||
worker.run(goals);
|
||||
auto goals = worker.run([&](GoalFactory & gf) {
|
||||
return Goals{gf.makePathSubstitutionGoal(path, Repair)};
|
||||
});
|
||||
auto goal = *goals.begin();
|
||||
|
||||
if (goal->exitCode != Goal::ecSuccess) {
|
||||
/* Since substituting the path didn't work, if we have a valid
|
||||
deriver, then rebuild the deriver. */
|
||||
auto info = queryPathInfo(path);
|
||||
if (info->deriver && isValidPath(*info->deriver)) {
|
||||
goals.clear();
|
||||
goals.insert(worker.makeGoal(DerivedPath::Built {
|
||||
.drvPath = makeConstantStorePathRef(*info->deriver),
|
||||
// FIXME: Should just build the specific output we need.
|
||||
.outputs = OutputsSpec::All { },
|
||||
}, bmRepair));
|
||||
worker.run(goals);
|
||||
worker.run([&](GoalFactory & gf) {
|
||||
return Goals{gf.makeGoal(
|
||||
DerivedPath::Built{
|
||||
.drvPath = makeConstantStorePathRef(*info->deriver),
|
||||
// FIXME: Should just build the specific output we need.
|
||||
.outputs = OutputsSpec::All{},
|
||||
},
|
||||
bmRepair
|
||||
)};
|
||||
});
|
||||
} else
|
||||
throw Error(worker.failingExitStatus(), "cannot repair path '%s'", printStorePath(path));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "goal.hh"
|
||||
#include "worker.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ enum struct JobCategory {
|
||||
Substitution,
|
||||
};
|
||||
|
||||
struct Goal : public std::enable_shared_from_this<Goal>
|
||||
struct Goal
|
||||
{
|
||||
typedef enum {ecSuccess, ecFailed, ecNoSubstituters, ecIncompleteClosure} ExitCode;
|
||||
|
||||
@@ -60,6 +60,13 @@ struct Goal : public std::enable_shared_from_this<Goal>
|
||||
*/
|
||||
Worker & worker;
|
||||
|
||||
/**
|
||||
* Whether this goal is only a dependency of other goals. Toplevel
|
||||
* goals that are also dependencies of other toplevel goals do not
|
||||
* set this, only goals that are exclusively dependencies do this.
|
||||
*/
|
||||
const bool isDependency;
|
||||
|
||||
/**
|
||||
* Goals that this goal is waiting for.
|
||||
*/
|
||||
@@ -112,9 +119,14 @@ public:
|
||||
struct [[nodiscard]] WaitForGoals {
|
||||
Goals goals;
|
||||
};
|
||||
struct [[nodiscard]] WaitForWorld {
|
||||
std::set<int> fds;
|
||||
bool inBuildSlot;
|
||||
};
|
||||
struct [[nodiscard]] Finished {
|
||||
ExitCode result;
|
||||
std::unique_ptr<Error> ex;
|
||||
ExitCode exitCode;
|
||||
BuildResult result;
|
||||
std::shared_ptr<Error> ex;
|
||||
bool permanentFailure = false;
|
||||
bool timedOut = false;
|
||||
bool hashMismatch = false;
|
||||
@@ -127,6 +139,7 @@ public:
|
||||
WaitForAWhile,
|
||||
ContinueImmediately,
|
||||
WaitForGoals,
|
||||
WaitForWorld,
|
||||
Finished>
|
||||
{
|
||||
WorkResult() = delete;
|
||||
@@ -136,10 +149,11 @@ public:
|
||||
/**
|
||||
* Exception containing an error message, if any.
|
||||
*/
|
||||
std::unique_ptr<Error> ex;
|
||||
std::shared_ptr<Error> ex;
|
||||
|
||||
Goal(Worker & worker, DerivedPath path)
|
||||
explicit Goal(Worker & worker, bool isDependency)
|
||||
: worker(worker)
|
||||
, isDependency(isDependency)
|
||||
{ }
|
||||
|
||||
virtual ~Goal() noexcept(false)
|
||||
@@ -147,7 +161,7 @@ public:
|
||||
trace("goal destroyed");
|
||||
}
|
||||
|
||||
virtual WorkResult work() = 0;
|
||||
virtual WorkResult work(bool inBuildSlot) = 0;
|
||||
|
||||
virtual void waiteeDone(GoalPtr waitee) { }
|
||||
|
||||
@@ -160,6 +174,11 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool respectsTimeouts()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void trace(std::string_view s);
|
||||
|
||||
std::string getName() const
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "file-system.hh"
|
||||
#include "globals.hh"
|
||||
#include "hook-instance.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -26,18 +27,21 @@ HookInstance::HookInstance()
|
||||
args.push_back(std::to_string(verbosity));
|
||||
|
||||
/* Create a pipe to get the output of the child. */
|
||||
fromHook.create();
|
||||
Pipe fromHook_;
|
||||
fromHook_.create();
|
||||
|
||||
/* Create the communication pipes. */
|
||||
toHook.create();
|
||||
Pipe toHook_;
|
||||
toHook_.create();
|
||||
|
||||
/* Create a pipe to get the output of the builder. */
|
||||
builderOut.create();
|
||||
Pipe builderOut_;
|
||||
builderOut_.create();
|
||||
|
||||
/* Fork the hook. */
|
||||
pid = startProcess([&]() {
|
||||
|
||||
if (dup2(fromHook.writeSide.get(), STDERR_FILENO) == -1)
|
||||
if (dup2(fromHook_.writeSide.get(), STDERR_FILENO) == -1)
|
||||
throw SysError("cannot pipe standard error into log file");
|
||||
|
||||
commonChildInit();
|
||||
@@ -45,16 +49,16 @@ HookInstance::HookInstance()
|
||||
if (chdir("/") == -1) throw SysError("changing into /");
|
||||
|
||||
/* Dup the communication pipes. */
|
||||
if (dup2(toHook.readSide.get(), STDIN_FILENO) == -1)
|
||||
if (dup2(toHook_.readSide.get(), STDIN_FILENO) == -1)
|
||||
throw SysError("dupping to-hook read side");
|
||||
|
||||
/* Use fd 4 for the builder's stdout/stderr. */
|
||||
if (dup2(builderOut.writeSide.get(), 4) == -1)
|
||||
if (dup2(builderOut_.writeSide.get(), 4) == -1)
|
||||
throw SysError("dupping builder's stdout/stderr");
|
||||
|
||||
/* Hack: pass the read side of that fd to allow build-remote
|
||||
to read SSH error messages. */
|
||||
if (dup2(builderOut.readSide.get(), 5) == -1)
|
||||
if (dup2(builderOut_.readSide.get(), 5) == -1)
|
||||
throw SysError("dupping builder's stdout/stderr");
|
||||
|
||||
execv(buildHook.c_str(), stringsToCharPtrs(args).data());
|
||||
@@ -63,10 +67,11 @@ HookInstance::HookInstance()
|
||||
});
|
||||
|
||||
pid.setSeparatePG(true);
|
||||
fromHook.writeSide.reset();
|
||||
toHook.readSide.reset();
|
||||
fromHook = std::move(fromHook_.readSide);
|
||||
toHook = std::move(toHook_.writeSide);
|
||||
builderOut = std::move(builderOut_.readSide);
|
||||
|
||||
sink = FdSink(toHook.writeSide.get());
|
||||
sink = FdSink(toHook.get());
|
||||
std::map<std::string, Config::SettingInfo> settings;
|
||||
globalConfig.getSettings(settings);
|
||||
for (auto & setting : settings)
|
||||
@@ -78,7 +83,7 @@ HookInstance::HookInstance()
|
||||
HookInstance::~HookInstance()
|
||||
{
|
||||
try {
|
||||
toHook.writeSide.reset();
|
||||
toHook.reset();
|
||||
if (pid) pid.kill();
|
||||
} catch (...) {
|
||||
ignoreException();
|
||||
|
||||
@@ -10,19 +10,19 @@ namespace nix {
|
||||
struct HookInstance
|
||||
{
|
||||
/**
|
||||
* Pipes for talking to the build hook.
|
||||
* Pipe for talking to the build hook.
|
||||
*/
|
||||
Pipe toHook;
|
||||
AutoCloseFD toHook;
|
||||
|
||||
/**
|
||||
* Pipe for the hook's standard output/error.
|
||||
*/
|
||||
Pipe fromHook;
|
||||
AutoCloseFD fromHook;
|
||||
|
||||
/**
|
||||
* Pipe for the builder's standard output/error.
|
||||
*/
|
||||
Pipe builderOut;
|
||||
AutoCloseFD builderOut;
|
||||
|
||||
/**
|
||||
* The process ID of the hook.
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
#include "local-derivation-goal.hh"
|
||||
#include "indirect-root-store.hh"
|
||||
#include "hook-instance.hh"
|
||||
#include "machines.hh"
|
||||
#include "store-api.hh"
|
||||
#include "worker.hh"
|
||||
#include "builtins.hh"
|
||||
#include "builtins/buildenv.hh"
|
||||
#include "path-references.hh"
|
||||
#include "finally.hh"
|
||||
#include "archive.hh"
|
||||
#include "compression.hh"
|
||||
#include "daemon.hh"
|
||||
#include "topo-sort.hh"
|
||||
#include "json-utils.hh"
|
||||
@@ -17,6 +15,8 @@
|
||||
#include "namespaces.hh"
|
||||
#include "child.hh"
|
||||
#include "unix-domain-socket.hh"
|
||||
#include "mount.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
#include <regex>
|
||||
#include <queue>
|
||||
@@ -149,17 +149,30 @@ void LocalDerivationGoal::killSandbox(bool getStats)
|
||||
}
|
||||
|
||||
|
||||
Goal::WorkResult LocalDerivationGoal::tryLocalBuild()
|
||||
Goal::WorkResult LocalDerivationGoal::tryLocalBuild(bool inBuildSlot)
|
||||
{
|
||||
#if __APPLE__
|
||||
additionalSandboxProfile = parsedDrv->getStringAttr("__sandboxProfile").value_or("");
|
||||
#endif
|
||||
|
||||
unsigned int curBuilds = worker.getNrLocalBuilds();
|
||||
if (curBuilds >= settings.maxBuildJobs) {
|
||||
if (!inBuildSlot) {
|
||||
state = &DerivationGoal::tryToBuild;
|
||||
outputLocks.unlock();
|
||||
return WaitForSlot{};
|
||||
if (0U != settings.maxBuildJobs) {
|
||||
return WaitForSlot{};
|
||||
}
|
||||
if (getMachines().empty()) {
|
||||
throw Error(
|
||||
"unable to start any build; either set '--max-jobs' to a non-zero value or enable "
|
||||
"remote builds.\n"
|
||||
"https://docs.lix.systems/manual/lix/stable/advanced-topics/distributed-builds.html"
|
||||
);
|
||||
} else {
|
||||
throw Error(
|
||||
"unable to start any build; remote machines may not have all required system features.\n"
|
||||
"https://docs.lix.systems/manual/lix/stable/advanced-topics/distributed-builds.html"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
assert(derivationType);
|
||||
@@ -230,7 +243,14 @@ Goal::WorkResult LocalDerivationGoal::tryLocalBuild()
|
||||
try {
|
||||
|
||||
/* Okay, we have to build. */
|
||||
startBuilder();
|
||||
auto fds = startBuilder();
|
||||
|
||||
/* This state will be reached when we get EOF on the child's
|
||||
log pipe. */
|
||||
state = &DerivationGoal::buildDone;
|
||||
|
||||
started();
|
||||
return WaitForWorld{std::move(fds), true};
|
||||
|
||||
} catch (BuildError & e) {
|
||||
outputLocks.unlock();
|
||||
@@ -239,12 +259,6 @@ Goal::WorkResult LocalDerivationGoal::tryLocalBuild()
|
||||
report.permanentFailure = true;
|
||||
return report;
|
||||
}
|
||||
|
||||
/* This state will be reached when we get EOF on the child's
|
||||
log pipe. */
|
||||
state = &DerivationGoal::buildDone;
|
||||
|
||||
return started();
|
||||
}
|
||||
|
||||
|
||||
@@ -279,8 +293,10 @@ void LocalDerivationGoal::closeReadPipes()
|
||||
{
|
||||
if (hook) {
|
||||
DerivationGoal::closeReadPipes();
|
||||
} else
|
||||
builderOut.close();
|
||||
} else {
|
||||
builderOutPTY.close();
|
||||
builderOutFD = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -372,40 +388,7 @@ void LocalDerivationGoal::cleanupPostOutputsRegisteredModeNonCheck()
|
||||
cleanupPostOutputsRegisteredModeCheck();
|
||||
}
|
||||
|
||||
#if __linux__
|
||||
static void doBind(const Path & source, const Path & target, bool optional = false) {
|
||||
debug("bind mounting '%1%' to '%2%'", source, target);
|
||||
|
||||
auto bindMount = [&]() {
|
||||
if (mount(source.c_str(), target.c_str(), "", MS_BIND | MS_REC, 0) == -1)
|
||||
throw SysError("bind mount from '%1%' to '%2%' failed", source, target);
|
||||
};
|
||||
|
||||
auto maybeSt = maybeLstat(source);
|
||||
if (!maybeSt) {
|
||||
if (optional)
|
||||
return;
|
||||
else
|
||||
throw SysError("getting attributes of path '%1%'", source);
|
||||
}
|
||||
auto st = *maybeSt;
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
createDirs(target);
|
||||
bindMount();
|
||||
} else if (S_ISLNK(st.st_mode)) {
|
||||
// Symlinks can (apparently) not be bind-mounted, so just copy it
|
||||
createDirs(dirOf(target));
|
||||
copyFile(source, target, {});
|
||||
} else {
|
||||
createDirs(dirOf(target));
|
||||
writeFile(target, "");
|
||||
bindMount();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
void LocalDerivationGoal::startBuilder()
|
||||
std::set<int> LocalDerivationGoal::startBuilder()
|
||||
{
|
||||
if ((buildUser && buildUser->getUIDCount() != 1)
|
||||
#if __linux__
|
||||
@@ -466,13 +449,23 @@ void LocalDerivationGoal::startBuilder()
|
||||
killSandbox(false);
|
||||
|
||||
/* Right platform? */
|
||||
if (!parsedDrv->canBuildLocally(worker.store))
|
||||
throw Error("a '%s' with features {%s} is required to build '%s', but I am a '%s' with features {%s}",
|
||||
drv->platform,
|
||||
concatStringsSep(", ", parsedDrv->getRequiredSystemFeatures()),
|
||||
worker.store.printStorePath(drvPath),
|
||||
settings.thisSystem,
|
||||
concatStringsSep<StringSet>(", ", worker.store.systemFeatures));
|
||||
if (!parsedDrv->canBuildLocally(worker.store)) {
|
||||
HintFmt addendum{""};
|
||||
if (settings.useSubstitutes && !parsedDrv->substitutesAllowed()) {
|
||||
addendum = HintFmt("\n\nHint: the failing derivation has %s set to %s, forcing it to be built rather than substituted.\n"
|
||||
"Passing %s to force substitution may resolve this failure if the path is available in a substituter.",
|
||||
"allowSubstitutes", "false", "--always-allow-substitutes");
|
||||
}
|
||||
throw Error({
|
||||
.msg = HintFmt("a '%s' with features {%s} is required to build '%s', but I am a '%s' with features {%s}%s",
|
||||
drv->platform,
|
||||
concatStringsSep(", ", parsedDrv->getRequiredSystemFeatures()),
|
||||
worker.store.printStorePath(drvPath),
|
||||
settings.thisSystem,
|
||||
concatStringsSep<StringSet>(", ", worker.store.systemFeatures),
|
||||
Uncolored(addendum))
|
||||
});
|
||||
}
|
||||
|
||||
/* Create a temporary directory where the build will take
|
||||
place. */
|
||||
@@ -704,12 +697,13 @@ void LocalDerivationGoal::startBuilder()
|
||||
Path logFile = openLogFile();
|
||||
|
||||
/* Create a pseudoterminal to get the output of the builder. */
|
||||
builderOut = AutoCloseFD{posix_openpt(O_RDWR | O_NOCTTY)};
|
||||
if (!builderOut)
|
||||
builderOutPTY = AutoCloseFD{posix_openpt(O_RDWR | O_NOCTTY)};
|
||||
if (!builderOutPTY)
|
||||
throw SysError("opening pseudoterminal master");
|
||||
builderOutFD = &builderOutPTY;
|
||||
|
||||
// FIXME: not thread-safe, use ptsname_r
|
||||
std::string slaveName = ptsname(builderOut.get());
|
||||
std::string slaveName = ptsname(builderOutPTY.get());
|
||||
|
||||
if (buildUser) {
|
||||
if (chmod(slaveName.c_str(), 0600))
|
||||
@@ -720,12 +714,12 @@ void LocalDerivationGoal::startBuilder()
|
||||
}
|
||||
#if __APPLE__
|
||||
else {
|
||||
if (grantpt(builderOut.get()))
|
||||
if (grantpt(builderOutPTY.get()))
|
||||
throw SysError("granting access to pseudoterminal slave");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (unlockpt(builderOut.get()))
|
||||
if (unlockpt(builderOutPTY.get()))
|
||||
throw SysError("unlocking pseudoterminal");
|
||||
|
||||
/* Open the slave side of the pseudoterminal and use it as stderr. */
|
||||
@@ -756,14 +750,13 @@ void LocalDerivationGoal::startBuilder()
|
||||
|
||||
/* parent */
|
||||
pid.setSeparatePG(true);
|
||||
worker.childStarted(shared_from_this(), {builderOut.get()}, true, true);
|
||||
|
||||
/* Check if setting up the build environment failed. */
|
||||
std::vector<std::string> msgs;
|
||||
while (true) {
|
||||
std::string msg = [&]() {
|
||||
try {
|
||||
return readLine(builderOut.get());
|
||||
return readLine(builderOutPTY.get());
|
||||
} catch (Error & e) {
|
||||
auto status = pid.wait();
|
||||
e.addTrace({}, "while waiting for the build environment for '%s' to initialize (%s, previous messages: %s)",
|
||||
@@ -775,7 +768,7 @@ void LocalDerivationGoal::startBuilder()
|
||||
}();
|
||||
if (msg.substr(0, 1) == "\2") break;
|
||||
if (msg.substr(0, 1) == "\1") {
|
||||
FdSource source(builderOut.get());
|
||||
FdSource source(builderOutPTY.get());
|
||||
auto ex = readError(source);
|
||||
ex.addTrace({}, "while setting up the build environment");
|
||||
throw ex;
|
||||
@@ -783,6 +776,8 @@ void LocalDerivationGoal::startBuilder()
|
||||
debug("sandbox setup: " + msg);
|
||||
msgs.push_back(std::move(msg));
|
||||
}
|
||||
|
||||
return {builderOutPTY.get()};
|
||||
}
|
||||
|
||||
|
||||
@@ -1307,7 +1302,7 @@ void LocalDerivationGoal::addDependency(const StorePath & path)
|
||||
Path target = chrootRootDir + worker.store.printStorePath(path);
|
||||
|
||||
if (pathExists(target)) {
|
||||
// There is a similar debug message in doBind, so only run it in this block to not have double messages.
|
||||
// There is a similar debug message in bindPath, so only run it in this block to not have double messages.
|
||||
debug("bind-mounting %s -> %s", target, source);
|
||||
throw Error("store path '%s' already exists in the sandbox", worker.store.printStorePath(path));
|
||||
}
|
||||
@@ -1324,7 +1319,7 @@ void LocalDerivationGoal::addDependency(const StorePath & path)
|
||||
if (setns(sandboxMountNamespace.get(), 0) == -1)
|
||||
throw SysError("entering sandbox mount namespace");
|
||||
|
||||
doBind(source, target);
|
||||
bindPath(source, target);
|
||||
|
||||
_exit(0);
|
||||
});
|
||||
@@ -1516,7 +1511,7 @@ void LocalDerivationGoal::runChild()
|
||||
chmodPath(dst, 0555);
|
||||
} else
|
||||
#endif
|
||||
doBind(i.second.source, chrootRootDir + i.first, i.second.optional);
|
||||
bindPath(i.second.source, chrootRootDir + i.first, i.second.optional);
|
||||
}
|
||||
|
||||
/* Bind a new instance of procfs on /proc. */
|
||||
@@ -1555,8 +1550,8 @@ void LocalDerivationGoal::runChild()
|
||||
} else {
|
||||
if (errno != EINVAL)
|
||||
throw SysError("mounting /dev/pts");
|
||||
doBind("/dev/pts", chrootRootDir + "/dev/pts");
|
||||
doBind("/dev/ptmx", chrootRootDir + "/dev/ptmx");
|
||||
bindPath("/dev/pts", chrootRootDir + "/dev/pts");
|
||||
bindPath("/dev/ptmx", chrootRootDir + "/dev/ptmx");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2596,13 +2591,6 @@ void LocalDerivationGoal::deleteTmpDir(bool force)
|
||||
}
|
||||
|
||||
|
||||
bool LocalDerivationGoal::isReadDesc(int fd)
|
||||
{
|
||||
return (hook && DerivationGoal::isReadDesc(fd)) ||
|
||||
(!hook && fd == builderOut.get());
|
||||
}
|
||||
|
||||
|
||||
StorePath LocalDerivationGoal::makeFallbackPath(OutputNameView outputName)
|
||||
{
|
||||
return worker.store.makeStorePath(
|
||||
|
||||
@@ -40,7 +40,7 @@ struct LocalDerivationGoal : public DerivationGoal
|
||||
* Master side of the pseudoterminal used for the builder's
|
||||
* standard output/error.
|
||||
*/
|
||||
AutoCloseFD builderOut;
|
||||
AutoCloseFD builderOutPTY;
|
||||
|
||||
/**
|
||||
* Pipe for synchronising updates to the builder namespaces.
|
||||
@@ -186,6 +186,7 @@ struct LocalDerivationGoal : public DerivationGoal
|
||||
const StorePath & drvPath,
|
||||
const OutputsSpec & wantedOutputs,
|
||||
Worker & worker,
|
||||
bool isDependency,
|
||||
BuildMode buildMode
|
||||
);
|
||||
|
||||
@@ -198,6 +199,7 @@ struct LocalDerivationGoal : public DerivationGoal
|
||||
const BasicDerivation & drv,
|
||||
const OutputsSpec & wantedOutputs,
|
||||
Worker & worker,
|
||||
bool isDependency,
|
||||
BuildMode buildMode
|
||||
);
|
||||
|
||||
@@ -211,12 +213,12 @@ struct LocalDerivationGoal : public DerivationGoal
|
||||
/**
|
||||
* The additional states.
|
||||
*/
|
||||
WorkResult tryLocalBuild() override;
|
||||
WorkResult tryLocalBuild(bool inBuildSlot) override;
|
||||
|
||||
/**
|
||||
* Start building a derivation.
|
||||
*/
|
||||
void startBuilder();
|
||||
std::set<int> startBuilder();
|
||||
|
||||
/**
|
||||
* Fill in the environment for the builder.
|
||||
@@ -285,8 +287,6 @@ struct LocalDerivationGoal : public DerivationGoal
|
||||
void cleanupPostOutputsRegisteredModeCheck() override;
|
||||
void cleanupPostOutputsRegisteredModeNonCheck() override;
|
||||
|
||||
bool isReadDesc(int fd) override;
|
||||
|
||||
/**
|
||||
* Delete the temporary directory, if we have one.
|
||||
*/
|
||||
@@ -359,6 +359,10 @@ protected:
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool respectsTimeouts() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -6,8 +6,14 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
PathSubstitutionGoal::PathSubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair, std::optional<ContentAddress> ca)
|
||||
: Goal(worker, DerivedPath::Opaque { storePath })
|
||||
PathSubstitutionGoal::PathSubstitutionGoal(
|
||||
const StorePath & storePath,
|
||||
Worker & worker,
|
||||
bool isDependency,
|
||||
RepairFlag repair,
|
||||
std::optional<ContentAddress> ca
|
||||
)
|
||||
: Goal(worker, isDependency)
|
||||
, storePath(storePath)
|
||||
, repair(repair)
|
||||
, ca(ca)
|
||||
@@ -15,7 +21,7 @@ PathSubstitutionGoal::PathSubstitutionGoal(const StorePath & storePath, Worker &
|
||||
state = &PathSubstitutionGoal::init;
|
||||
name = fmt("substitution of '%s'", worker.store.printStorePath(this->storePath));
|
||||
trace("created");
|
||||
maintainExpectedSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.expectedSubstitutions);
|
||||
maintainExpectedSubstitutions = worker.expectedSubstitutions.addTemporarily(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,17 +41,17 @@ Goal::Finished PathSubstitutionGoal::done(
|
||||
debug(*errorMsg);
|
||||
buildResult.errorMsg = *errorMsg;
|
||||
}
|
||||
return Finished{result};
|
||||
return Finished{result, std::move(buildResult)};
|
||||
}
|
||||
|
||||
|
||||
Goal::WorkResult PathSubstitutionGoal::work()
|
||||
Goal::WorkResult PathSubstitutionGoal::work(bool inBuildSlot)
|
||||
{
|
||||
return (this->*state)();
|
||||
return (this->*state)(inBuildSlot);
|
||||
}
|
||||
|
||||
|
||||
Goal::WorkResult PathSubstitutionGoal::init()
|
||||
Goal::WorkResult PathSubstitutionGoal::init(bool inBuildSlot)
|
||||
{
|
||||
trace("init");
|
||||
|
||||
@@ -61,11 +67,11 @@ Goal::WorkResult PathSubstitutionGoal::init()
|
||||
|
||||
subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
|
||||
|
||||
return tryNext();
|
||||
return tryNext(inBuildSlot);
|
||||
}
|
||||
|
||||
|
||||
Goal::WorkResult PathSubstitutionGoal::tryNext()
|
||||
Goal::WorkResult PathSubstitutionGoal::tryNext(bool inBuildSlot)
|
||||
{
|
||||
trace("trying next substituter");
|
||||
|
||||
@@ -97,23 +103,23 @@ Goal::WorkResult PathSubstitutionGoal::tryNext()
|
||||
if (sub->storeDir == worker.store.storeDir)
|
||||
assert(subPath == storePath);
|
||||
} else if (sub->storeDir != worker.store.storeDir) {
|
||||
return tryNext();
|
||||
return tryNext(inBuildSlot);
|
||||
}
|
||||
|
||||
try {
|
||||
// FIXME: make async
|
||||
info = sub->queryPathInfo(subPath ? *subPath : storePath);
|
||||
} catch (InvalidPath &) {
|
||||
return tryNext();
|
||||
return tryNext(inBuildSlot);
|
||||
} catch (SubstituterDisabled &) {
|
||||
if (settings.tryFallback) {
|
||||
return tryNext();
|
||||
return tryNext(inBuildSlot);
|
||||
}
|
||||
throw;
|
||||
} catch (Error & e) {
|
||||
if (settings.tryFallback) {
|
||||
logError(e.info());
|
||||
return tryNext();
|
||||
return tryNext(inBuildSlot);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
@@ -126,18 +132,18 @@ Goal::WorkResult PathSubstitutionGoal::tryNext()
|
||||
} else {
|
||||
printError("asked '%s' for '%s' but got '%s'",
|
||||
sub->getUri(), worker.store.printStorePath(storePath), sub->printStorePath(info->path));
|
||||
return tryNext();
|
||||
return tryNext(inBuildSlot);
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the total expected download size. */
|
||||
auto narInfo = std::dynamic_pointer_cast<const NarInfo>(info);
|
||||
|
||||
maintainExpectedNar = std::make_unique<MaintainCount<uint64_t>>(worker.expectedNarSize, info->narSize);
|
||||
maintainExpectedNar = worker.expectedNarSize.addTemporarily(info->narSize);
|
||||
|
||||
maintainExpectedDownload =
|
||||
narInfo && narInfo->fileSize
|
||||
? std::make_unique<MaintainCount<uint64_t>>(worker.expectedDownloadSize, narInfo->fileSize)
|
||||
? worker.expectedDownloadSize.addTemporarily(narInfo->fileSize)
|
||||
: nullptr;
|
||||
|
||||
/* Bail out early if this substituter lacks a valid
|
||||
@@ -147,7 +153,7 @@ Goal::WorkResult PathSubstitutionGoal::tryNext()
|
||||
{
|
||||
warn("ignoring substitute for '%s' from '%s', as it's not signed by any of the keys in 'trusted-public-keys'",
|
||||
worker.store.printStorePath(storePath), sub->getUri());
|
||||
return tryNext();
|
||||
return tryNext(inBuildSlot);
|
||||
}
|
||||
|
||||
/* To maintain the closure invariant, we first have to realise the
|
||||
@@ -155,10 +161,10 @@ Goal::WorkResult PathSubstitutionGoal::tryNext()
|
||||
WaitForGoals result;
|
||||
for (auto & i : info->references)
|
||||
if (i != storePath) /* ignore self-references */
|
||||
result.goals.insert(worker.makePathSubstitutionGoal(i));
|
||||
result.goals.insert(worker.goalFactory().makePathSubstitutionGoal(i));
|
||||
|
||||
if (result.goals.empty()) {/* to prevent hang (no wake-up event) */
|
||||
return referencesValid();
|
||||
return referencesValid(inBuildSlot);
|
||||
} else {
|
||||
state = &PathSubstitutionGoal::referencesValid;
|
||||
return result;
|
||||
@@ -166,7 +172,7 @@ Goal::WorkResult PathSubstitutionGoal::tryNext()
|
||||
}
|
||||
|
||||
|
||||
Goal::WorkResult PathSubstitutionGoal::referencesValid()
|
||||
Goal::WorkResult PathSubstitutionGoal::referencesValid(bool inBuildSlot)
|
||||
{
|
||||
trace("all references realised");
|
||||
|
||||
@@ -182,22 +188,19 @@ Goal::WorkResult PathSubstitutionGoal::referencesValid()
|
||||
assert(worker.store.isValidPath(i));
|
||||
|
||||
state = &PathSubstitutionGoal::tryToRun;
|
||||
return ContinueImmediately{};
|
||||
return tryToRun(inBuildSlot);
|
||||
}
|
||||
|
||||
|
||||
Goal::WorkResult PathSubstitutionGoal::tryToRun()
|
||||
Goal::WorkResult PathSubstitutionGoal::tryToRun(bool inBuildSlot)
|
||||
{
|
||||
trace("trying to run");
|
||||
|
||||
/* Make sure that we are allowed to start a substitution. Note that even
|
||||
if maxSubstitutionJobs == 0, we still allow a substituter to run. This
|
||||
prevents infinite waiting. */
|
||||
if (worker.getNrSubstitutions() >= std::max(1U, (unsigned int) settings.maxSubstitutionJobs)) {
|
||||
if (!inBuildSlot) {
|
||||
return WaitForSlot{};
|
||||
}
|
||||
|
||||
maintainRunningSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.runningSubstitutions);
|
||||
maintainRunningSubstitutions = worker.runningSubstitutions.addTemporarily(1);
|
||||
|
||||
outPipe.create();
|
||||
|
||||
@@ -224,14 +227,12 @@ Goal::WorkResult PathSubstitutionGoal::tryToRun()
|
||||
}
|
||||
});
|
||||
|
||||
worker.childStarted(shared_from_this(), {outPipe.readSide.get()}, true, false);
|
||||
|
||||
state = &PathSubstitutionGoal::finished;
|
||||
return StillAlive{};
|
||||
return WaitForWorld{{outPipe.readSide.get()}, true};
|
||||
}
|
||||
|
||||
|
||||
Goal::WorkResult PathSubstitutionGoal::finished()
|
||||
Goal::WorkResult PathSubstitutionGoal::finished(bool inBuildSlot)
|
||||
{
|
||||
trace("substitute finished");
|
||||
|
||||
@@ -255,7 +256,7 @@ Goal::WorkResult PathSubstitutionGoal::finished()
|
||||
|
||||
/* Try the next substitute. */
|
||||
state = &PathSubstitutionGoal::tryNext;
|
||||
return ContinueImmediately{};
|
||||
return tryNext(inBuildSlot);
|
||||
}
|
||||
|
||||
worker.markContentsGood(storePath);
|
||||
@@ -267,13 +268,10 @@ Goal::WorkResult PathSubstitutionGoal::finished()
|
||||
maintainExpectedSubstitutions.reset();
|
||||
worker.doneSubstitutions++;
|
||||
|
||||
if (maintainExpectedDownload) {
|
||||
auto fileSize = maintainExpectedDownload->delta;
|
||||
maintainExpectedDownload.reset();
|
||||
worker.doneDownloadSize += fileSize;
|
||||
}
|
||||
worker.doneDownloadSize += maintainExpectedDownload.delta();
|
||||
maintainExpectedDownload.reset();
|
||||
|
||||
worker.doneNarSize += maintainExpectedNar->delta;
|
||||
worker.doneNarSize += maintainExpectedNar.delta();
|
||||
maintainExpectedNar.reset();
|
||||
|
||||
return done(ecSuccess, BuildResult::Substituted);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
///@file
|
||||
|
||||
#include "lock.hh"
|
||||
#include "notifying-counter.hh"
|
||||
#include "store-api.hh"
|
||||
#include "goal.hh"
|
||||
|
||||
@@ -63,10 +64,10 @@ struct PathSubstitutionGoal : public Goal
|
||||
*/
|
||||
Path destPath;
|
||||
|
||||
std::unique_ptr<MaintainCount<uint64_t>> maintainExpectedSubstitutions,
|
||||
NotifyingCounter<uint64_t>::Bump maintainExpectedSubstitutions,
|
||||
maintainRunningSubstitutions, maintainExpectedNar, maintainExpectedDownload;
|
||||
|
||||
typedef WorkResult (PathSubstitutionGoal::*GoalState)();
|
||||
typedef WorkResult (PathSubstitutionGoal::*GoalState)(bool inBuildSlot);
|
||||
GoalState state;
|
||||
|
||||
/**
|
||||
@@ -80,7 +81,13 @@ struct PathSubstitutionGoal : public Goal
|
||||
std::optional<std::string> errorMsg = {});
|
||||
|
||||
public:
|
||||
PathSubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
||||
PathSubstitutionGoal(
|
||||
const StorePath & storePath,
|
||||
Worker & worker,
|
||||
bool isDependency,
|
||||
RepairFlag repair = NoRepair,
|
||||
std::optional<ContentAddress> ca = std::nullopt
|
||||
);
|
||||
~PathSubstitutionGoal();
|
||||
|
||||
Finished timedOut(Error && ex) override { abort(); };
|
||||
@@ -94,17 +101,16 @@ public:
|
||||
return "a$" + std::string(storePath.name()) + "$" + worker.store.printStorePath(storePath);
|
||||
}
|
||||
|
||||
WorkResult work() override;
|
||||
WorkResult work(bool inBuildSlot) override;
|
||||
|
||||
/**
|
||||
* The states.
|
||||
*/
|
||||
WorkResult init();
|
||||
WorkResult tryNext();
|
||||
WorkResult gotInfo();
|
||||
WorkResult referencesValid();
|
||||
WorkResult tryToRun();
|
||||
WorkResult finished();
|
||||
WorkResult init(bool inBuildSlot);
|
||||
WorkResult tryNext(bool inBuildSlot);
|
||||
WorkResult referencesValid(bool inBuildSlot);
|
||||
WorkResult tryToRun(bool inBuildSlot);
|
||||
WorkResult finished(bool inBuildSlot);
|
||||
|
||||
/**
|
||||
* Callback used by the worker to write to the log.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "charptr-cast.hh"
|
||||
#include "machines.hh"
|
||||
#include "worker.hh"
|
||||
#include "finally.hh"
|
||||
#include "substitution-goal.hh"
|
||||
#include "drv-output-substitution-goal.hh"
|
||||
#include "local-derivation-goal.hh"
|
||||
@@ -60,22 +60,38 @@ std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon(
|
||||
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(const StorePath & drvPath,
|
||||
const OutputsSpec & wantedOutputs, BuildMode buildMode)
|
||||
{
|
||||
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
|
||||
return !dynamic_cast<LocalStore *>(&store)
|
||||
? std::make_shared<DerivationGoal>(drvPath, wantedOutputs, *this, buildMode)
|
||||
: LocalDerivationGoal::makeLocalDerivationGoal(drvPath, wantedOutputs, *this, buildMode);
|
||||
});
|
||||
return makeDerivationGoalCommon(
|
||||
drvPath,
|
||||
wantedOutputs,
|
||||
[&]() -> std::shared_ptr<DerivationGoal> {
|
||||
return !dynamic_cast<LocalStore *>(&store)
|
||||
? std::make_shared<DerivationGoal>(
|
||||
drvPath, wantedOutputs, *this, running, buildMode
|
||||
)
|
||||
: LocalDerivationGoal::makeLocalDerivationGoal(
|
||||
drvPath, wantedOutputs, *this, running, buildMode
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<DerivationGoal> Worker::makeBasicDerivationGoal(const StorePath & drvPath,
|
||||
const BasicDerivation & drv, const OutputsSpec & wantedOutputs, BuildMode buildMode)
|
||||
{
|
||||
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
|
||||
return !dynamic_cast<LocalStore *>(&store)
|
||||
? std::make_shared<DerivationGoal>(drvPath, drv, wantedOutputs, *this, buildMode)
|
||||
: LocalDerivationGoal::makeLocalDerivationGoal(drvPath, drv, wantedOutputs, *this, buildMode);
|
||||
});
|
||||
return makeDerivationGoalCommon(
|
||||
drvPath,
|
||||
wantedOutputs,
|
||||
[&]() -> std::shared_ptr<DerivationGoal> {
|
||||
return !dynamic_cast<LocalStore *>(&store)
|
||||
? std::make_shared<DerivationGoal>(
|
||||
drvPath, drv, wantedOutputs, *this, running, buildMode
|
||||
)
|
||||
: LocalDerivationGoal::makeLocalDerivationGoal(
|
||||
drvPath, drv, wantedOutputs, *this, running, buildMode
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +100,7 @@ std::shared_ptr<PathSubstitutionGoal> Worker::makePathSubstitutionGoal(const Sto
|
||||
std::weak_ptr<PathSubstitutionGoal> & goal_weak = substitutionGoals[path];
|
||||
auto goal = goal_weak.lock(); // FIXME
|
||||
if (!goal) {
|
||||
goal = std::make_shared<PathSubstitutionGoal>(path, *this, repair, ca);
|
||||
goal = std::make_shared<PathSubstitutionGoal>(path, *this, running, repair, ca);
|
||||
goal_weak = goal;
|
||||
wakeUp(goal);
|
||||
}
|
||||
@@ -97,7 +113,7 @@ std::shared_ptr<DrvOutputSubstitutionGoal> Worker::makeDrvOutputSubstitutionGoal
|
||||
std::weak_ptr<DrvOutputSubstitutionGoal> & goal_weak = drvOutputSubstitutionGoals[id];
|
||||
auto goal = goal_weak.lock(); // FIXME
|
||||
if (!goal) {
|
||||
goal = std::make_shared<DrvOutputSubstitutionGoal>(id, *this, repair, ca);
|
||||
goal = std::make_shared<DrvOutputSubstitutionGoal>(id, *this, running, repair, ca);
|
||||
goal_weak = goal;
|
||||
wakeUp(goal);
|
||||
}
|
||||
@@ -140,20 +156,14 @@ void Worker::goalFinished(GoalPtr goal, Goal::Finished & f)
|
||||
{
|
||||
goal->trace("done");
|
||||
assert(!goal->exitCode.has_value());
|
||||
goal->exitCode = f.result;
|
||||
goal->exitCode = f.exitCode;
|
||||
goal->ex = f.ex;
|
||||
|
||||
permanentFailure |= f.permanentFailure;
|
||||
timedOut |= f.timedOut;
|
||||
hashMismatch |= f.hashMismatch;
|
||||
checkMismatch |= f.checkMismatch;
|
||||
|
||||
if (f.ex) {
|
||||
if (!goal->waiters.empty())
|
||||
logError(f.ex->info());
|
||||
else
|
||||
goal->ex = std::move(f.ex);
|
||||
}
|
||||
|
||||
for (auto & i : goal->waiters) {
|
||||
if (GoalPtr waiting = i.lock()) {
|
||||
assert(waiting->waitees.count(goal));
|
||||
@@ -161,11 +171,11 @@ void Worker::goalFinished(GoalPtr goal, Goal::Finished & f)
|
||||
|
||||
waiting->trace(fmt("waitee '%s' done; %d left", goal->name, waiting->waitees.size()));
|
||||
|
||||
if (f.result != Goal::ecSuccess) ++waiting->nrFailed;
|
||||
if (f.result == Goal::ecNoSubstituters) ++waiting->nrNoSubstituters;
|
||||
if (f.result == Goal::ecIncompleteClosure) ++waiting->nrIncompleteClosure;
|
||||
if (f.exitCode != Goal::ecSuccess) ++waiting->nrFailed;
|
||||
if (f.exitCode == Goal::ecNoSubstituters) ++waiting->nrNoSubstituters;
|
||||
if (f.exitCode == Goal::ecIncompleteClosure) ++waiting->nrIncompleteClosure;
|
||||
|
||||
if (waiting->waitees.empty() || (f.result == Goal::ecFailed && !settings.keepGoing)) {
|
||||
if (waiting->waitees.empty() || (f.exitCode == Goal::ecFailed && !settings.keepGoing)) {
|
||||
/* If we failed and keepGoing is not set, we remove all
|
||||
remaining waitees. */
|
||||
for (auto & i : waiting->waitees) {
|
||||
@@ -198,6 +208,7 @@ void Worker::handleWorkResult(GoalPtr goal, Goal::WorkResult how)
|
||||
dep->waiters.insert(goal);
|
||||
}
|
||||
},
|
||||
[&](Goal::WaitForWorld & w) { childStarted(goal, w.fds, w.inBuildSlot); },
|
||||
[&](Goal::Finished & f) { goalFinished(goal, f); },
|
||||
},
|
||||
how
|
||||
@@ -232,20 +243,8 @@ void Worker::wakeUp(GoalPtr goal)
|
||||
}
|
||||
|
||||
|
||||
unsigned Worker::getNrLocalBuilds()
|
||||
{
|
||||
return nrLocalBuilds;
|
||||
}
|
||||
|
||||
|
||||
unsigned Worker::getNrSubstitutions()
|
||||
{
|
||||
return nrSubstitutions;
|
||||
}
|
||||
|
||||
|
||||
void Worker::childStarted(GoalPtr goal, const std::set<int> & fds,
|
||||
bool inBuildSlot, bool respectTimeouts)
|
||||
bool inBuildSlot)
|
||||
{
|
||||
Child child;
|
||||
child.goal = goal;
|
||||
@@ -253,7 +252,6 @@ void Worker::childStarted(GoalPtr goal, const std::set<int> & fds,
|
||||
child.fds = fds;
|
||||
child.timeStarted = child.lastOutput = steady_time_point::clock::now();
|
||||
child.inBuildSlot = inBuildSlot;
|
||||
child.respectTimeouts = respectTimeouts;
|
||||
children.emplace_back(child);
|
||||
if (inBuildSlot) {
|
||||
switch (goal->jobCategory()) {
|
||||
@@ -307,8 +305,8 @@ void Worker::waitForBuildSlot(GoalPtr goal)
|
||||
{
|
||||
goal->trace("wait for build slot");
|
||||
bool isSubstitutionGoal = goal->jobCategory() == JobCategory::Substitution;
|
||||
if ((!isSubstitutionGoal && getNrLocalBuilds() < settings.maxBuildJobs) ||
|
||||
(isSubstitutionGoal && getNrSubstitutions() < settings.maxSubstitutionJobs))
|
||||
if ((!isSubstitutionGoal && nrLocalBuilds < settings.maxBuildJobs) ||
|
||||
(isSubstitutionGoal && nrSubstitutions < settings.maxSubstitutionJobs))
|
||||
wakeUp(goal); /* we can do it right away */
|
||||
else
|
||||
wantingToBuild.insert(goal);
|
||||
@@ -322,26 +320,38 @@ void Worker::waitForAWhile(GoalPtr goal)
|
||||
}
|
||||
|
||||
|
||||
void Worker::run(const Goals & _topGoals)
|
||||
void Worker::updateStatistics()
|
||||
{
|
||||
std::vector<nix::DerivedPath> topPaths;
|
||||
// only update progress info while running. this notably excludes updating
|
||||
// progress info while destroying, which causes the progress bar to assert
|
||||
if (running && statisticsOutdated) {
|
||||
actDerivations.progress(
|
||||
doneBuilds, expectedBuilds + doneBuilds, runningBuilds, failedBuilds
|
||||
);
|
||||
actSubstitutions.progress(
|
||||
doneSubstitutions,
|
||||
expectedSubstitutions + doneSubstitutions,
|
||||
runningSubstitutions,
|
||||
failedSubstitutions
|
||||
);
|
||||
act.setExpected(actFileTransfer, expectedDownloadSize + doneDownloadSize);
|
||||
act.setExpected(actCopyPath, expectedNarSize + doneNarSize);
|
||||
|
||||
for (auto & i : _topGoals) {
|
||||
topGoals.insert(i);
|
||||
if (auto goal = dynamic_cast<DerivationGoal *>(i.get())) {
|
||||
topPaths.push_back(DerivedPath::Built {
|
||||
.drvPath = makeConstantStorePathRef(goal->drvPath),
|
||||
.outputs = goal->wantedOutputs,
|
||||
});
|
||||
} else if (auto goal = dynamic_cast<PathSubstitutionGoal *>(i.get())) {
|
||||
topPaths.push_back(DerivedPath::Opaque{goal->storePath});
|
||||
}
|
||||
statisticsOutdated = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Call queryMissing() to efficiently query substitutes. */
|
||||
StorePathSet willBuild, willSubstitute, unknown;
|
||||
uint64_t downloadSize, narSize;
|
||||
store.queryMissing(topPaths, willBuild, willSubstitute, unknown, downloadSize, narSize);
|
||||
Goals Worker::run(std::function<Goals (GoalFactory &)> req)
|
||||
{
|
||||
auto _topGoals = req(goalFactory());
|
||||
|
||||
assert(!running);
|
||||
running = true;
|
||||
Finally const _stop([&] { running = false; });
|
||||
|
||||
updateStatistics();
|
||||
|
||||
topGoals = _topGoals;
|
||||
|
||||
debug("entered goal loop");
|
||||
|
||||
@@ -364,19 +374,13 @@ void Worker::run(const Goals & _topGoals)
|
||||
awake.clear();
|
||||
for (auto & goal : awake2) {
|
||||
checkInterrupt();
|
||||
handleWorkResult(goal, goal->work());
|
||||
|
||||
actDerivations.progress(
|
||||
doneBuilds, expectedBuilds + doneBuilds, runningBuilds, failedBuilds
|
||||
);
|
||||
actSubstitutions.progress(
|
||||
doneSubstitutions,
|
||||
expectedSubstitutions + doneSubstitutions,
|
||||
runningSubstitutions,
|
||||
failedSubstitutions
|
||||
);
|
||||
act.setExpected(actFileTransfer, expectedDownloadSize + doneDownloadSize);
|
||||
act.setExpected(actCopyPath, expectedNarSize + doneNarSize);
|
||||
/* Make sure that we are always allowed to run at least one substitution.
|
||||
This prevents infinite waiting. */
|
||||
const bool inSlot = goal->jobCategory() == JobCategory::Substitution
|
||||
? nrSubstitutions < std::max(1U, (unsigned int) settings.maxSubstitutionJobs)
|
||||
: nrLocalBuilds < settings.maxBuildJobs;
|
||||
handleWorkResult(goal, goal->work(inSlot));
|
||||
updateStatistics();
|
||||
|
||||
if (topGoals.empty()) break; // stuff may have been cancelled
|
||||
}
|
||||
@@ -388,18 +392,6 @@ void Worker::run(const Goals & _topGoals)
|
||||
if (!children.empty() || !waitingForAWhile.empty())
|
||||
waitForInput();
|
||||
else {
|
||||
if (awake.empty() && 0U == settings.maxBuildJobs)
|
||||
{
|
||||
if (getMachines().empty())
|
||||
throw Error("unable to start any build; either increase '--max-jobs' "
|
||||
"or enable remote builds."
|
||||
"\nhttps://docs.lix.systems/manual/lix/stable/advanced-topics/distributed-builds.html");
|
||||
else
|
||||
throw Error("unable to start any build; remote machines may not have "
|
||||
"all required system features."
|
||||
"\nhttps://docs.lix.systems/manual/lix/stable/advanced-topics/distributed-builds.html");
|
||||
|
||||
}
|
||||
assert(!awake.empty());
|
||||
}
|
||||
}
|
||||
@@ -410,6 +402,8 @@ void Worker::run(const Goals & _topGoals)
|
||||
assert(!settings.keepGoing || awake.empty());
|
||||
assert(!settings.keepGoing || wantingToBuild.empty());
|
||||
assert(!settings.keepGoing || children.empty());
|
||||
|
||||
return _topGoals;
|
||||
}
|
||||
|
||||
void Worker::waitForInput()
|
||||
@@ -434,11 +428,13 @@ void Worker::waitForInput()
|
||||
// Periodicallty wake up to see if we need to run the garbage collector.
|
||||
nearest = before + std::chrono::seconds(10);
|
||||
for (auto & i : children) {
|
||||
if (!i.respectTimeouts) continue;
|
||||
if (0 != settings.maxSilentTime)
|
||||
nearest = std::min(nearest, i.lastOutput + std::chrono::seconds(settings.maxSilentTime));
|
||||
if (0 != settings.buildTimeout)
|
||||
nearest = std::min(nearest, i.timeStarted + std::chrono::seconds(settings.buildTimeout));
|
||||
if (auto goal = i.goal.lock()) {
|
||||
if (!goal->respectsTimeouts()) continue;
|
||||
if (0 != settings.maxSilentTime)
|
||||
nearest = std::min(nearest, i.lastOutput + std::chrono::seconds(settings.maxSilentTime));
|
||||
if (0 != settings.buildTimeout)
|
||||
nearest = std::min(nearest, i.timeStarted + std::chrono::seconds(settings.buildTimeout));
|
||||
}
|
||||
}
|
||||
if (nearest != steady_time_point::max()) {
|
||||
timeout = std::max(1L, (long) std::chrono::duration_cast<std::chrono::seconds>(nearest - before).count());
|
||||
@@ -491,7 +487,7 @@ void Worker::waitForInput()
|
||||
|
||||
if (!goal->exitCode.has_value() &&
|
||||
0 != settings.maxSilentTime &&
|
||||
j->respectTimeouts &&
|
||||
goal->respectsTimeouts() &&
|
||||
after - j->lastOutput >= std::chrono::seconds(settings.maxSilentTime))
|
||||
{
|
||||
handleWorkResult(
|
||||
@@ -507,7 +503,7 @@ void Worker::waitForInput()
|
||||
|
||||
else if (!goal->exitCode.has_value() &&
|
||||
0 != settings.buildTimeout &&
|
||||
j->respectTimeouts &&
|
||||
goal->respectsTimeouts() &&
|
||||
after - j->timeStarted >= std::chrono::seconds(settings.buildTimeout))
|
||||
{
|
||||
handleWorkResult(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "notifying-counter.hh"
|
||||
#include "types.hh"
|
||||
#include "lock.hh"
|
||||
#include "store-api.hh"
|
||||
@@ -29,7 +30,6 @@ struct Child
|
||||
WeakGoalPtr goal;
|
||||
Goal * goal2; // ugly hackery
|
||||
std::set<int> fds;
|
||||
bool respectTimeouts;
|
||||
bool inBuildSlot;
|
||||
/**
|
||||
* Time we last got output on stdout/stderr
|
||||
@@ -41,13 +41,62 @@ struct Child
|
||||
/* Forward definition. */
|
||||
struct HookInstance;
|
||||
|
||||
class GoalFactory
|
||||
{
|
||||
public:
|
||||
virtual std::shared_ptr<DerivationGoal> makeDerivationGoal(
|
||||
const StorePath & drvPath, const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal
|
||||
) = 0;
|
||||
virtual std::shared_ptr<DerivationGoal> makeBasicDerivationGoal(
|
||||
const StorePath & drvPath,
|
||||
const BasicDerivation & drv,
|
||||
const OutputsSpec & wantedOutputs,
|
||||
BuildMode buildMode = bmNormal
|
||||
) = 0;
|
||||
|
||||
/**
|
||||
* @ref SubstitutionGoal "substitution goal"
|
||||
*/
|
||||
virtual std::shared_ptr<PathSubstitutionGoal> makePathSubstitutionGoal(
|
||||
const StorePath & storePath,
|
||||
RepairFlag repair = NoRepair,
|
||||
std::optional<ContentAddress> ca = std::nullopt
|
||||
) = 0;
|
||||
virtual std::shared_ptr<DrvOutputSubstitutionGoal> makeDrvOutputSubstitutionGoal(
|
||||
const DrvOutput & id,
|
||||
RepairFlag repair = NoRepair,
|
||||
std::optional<ContentAddress> ca = std::nullopt
|
||||
) = 0;
|
||||
|
||||
/**
|
||||
* Make a goal corresponding to the `DerivedPath`.
|
||||
*
|
||||
* It will be a `DerivationGoal` for a `DerivedPath::Built` or
|
||||
* a `SubstitutionGoal` for a `DerivedPath::Opaque`.
|
||||
*/
|
||||
virtual GoalPtr makeGoal(const DerivedPath & req, BuildMode buildMode = bmNormal) = 0;
|
||||
};
|
||||
|
||||
// elaborate hoax to let goals access factory methods while hiding them from the public
|
||||
class WorkerBase : protected GoalFactory
|
||||
{
|
||||
friend struct DerivationGoal;
|
||||
friend struct PathSubstitutionGoal;
|
||||
friend class DrvOutputSubstitutionGoal;
|
||||
|
||||
protected:
|
||||
GoalFactory & goalFactory() { return *this; }
|
||||
};
|
||||
|
||||
/**
|
||||
* The worker class.
|
||||
*/
|
||||
class Worker
|
||||
class Worker : public WorkerBase
|
||||
{
|
||||
private:
|
||||
|
||||
bool running = false;
|
||||
|
||||
/* Note: the worker should only have strong pointers to the
|
||||
top-level goals. */
|
||||
|
||||
@@ -153,6 +202,33 @@ private:
|
||||
*/
|
||||
void waitForInput();
|
||||
|
||||
/**
|
||||
* Remove a dead goal.
|
||||
*/
|
||||
void removeGoal(GoalPtr goal);
|
||||
|
||||
/**
|
||||
* Registers a running child process. `inBuildSlot` means that
|
||||
* the process counts towards the jobs limit.
|
||||
*/
|
||||
void childStarted(GoalPtr goal, const std::set<int> & fds,
|
||||
bool inBuildSlot);
|
||||
|
||||
/**
|
||||
* Pass current stats counters to the logger for progress bar updates.
|
||||
*/
|
||||
void updateStatistics();
|
||||
|
||||
bool statisticsOutdated = true;
|
||||
|
||||
/**
|
||||
* Mark statistics as outdated, such that `updateStatistics` will be called.
|
||||
*/
|
||||
void updateStatisticsLater()
|
||||
{
|
||||
statisticsOutdated = true;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
const Activity act;
|
||||
@@ -174,19 +250,19 @@ public:
|
||||
|
||||
HookState hook;
|
||||
|
||||
uint64_t expectedBuilds = 0;
|
||||
uint64_t doneBuilds = 0;
|
||||
uint64_t failedBuilds = 0;
|
||||
uint64_t runningBuilds = 0;
|
||||
NotifyingCounter<uint64_t> expectedBuilds{[this] { updateStatisticsLater(); }};
|
||||
NotifyingCounter<uint64_t> doneBuilds{[this] { updateStatisticsLater(); }};
|
||||
NotifyingCounter<uint64_t> failedBuilds{[this] { updateStatisticsLater(); }};
|
||||
NotifyingCounter<uint64_t> runningBuilds{[this] { updateStatisticsLater(); }};
|
||||
|
||||
uint64_t expectedSubstitutions = 0;
|
||||
uint64_t doneSubstitutions = 0;
|
||||
uint64_t failedSubstitutions = 0;
|
||||
uint64_t runningSubstitutions = 0;
|
||||
uint64_t expectedDownloadSize = 0;
|
||||
uint64_t doneDownloadSize = 0;
|
||||
uint64_t expectedNarSize = 0;
|
||||
uint64_t doneNarSize = 0;
|
||||
NotifyingCounter<uint64_t> expectedSubstitutions{[this] { updateStatisticsLater(); }};
|
||||
NotifyingCounter<uint64_t> doneSubstitutions{[this] { updateStatisticsLater(); }};
|
||||
NotifyingCounter<uint64_t> failedSubstitutions{[this] { updateStatisticsLater(); }};
|
||||
NotifyingCounter<uint64_t> runningSubstitutions{[this] { updateStatisticsLater(); }};
|
||||
NotifyingCounter<uint64_t> expectedDownloadSize{[this] { updateStatisticsLater(); }};
|
||||
NotifyingCounter<uint64_t> doneDownloadSize{[this] { updateStatisticsLater(); }};
|
||||
NotifyingCounter<uint64_t> expectedNarSize{[this] { updateStatisticsLater(); }};
|
||||
NotifyingCounter<uint64_t> doneNarSize{[this] { updateStatisticsLater(); }};
|
||||
|
||||
Worker(Store & store, Store & evalStore);
|
||||
~Worker();
|
||||
@@ -202,19 +278,18 @@ private:
|
||||
std::shared_ptr<DerivationGoal> makeDerivationGoalCommon(
|
||||
const StorePath & drvPath, const OutputsSpec & wantedOutputs,
|
||||
std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal);
|
||||
public:
|
||||
std::shared_ptr<DerivationGoal> makeDerivationGoal(
|
||||
const StorePath & drvPath,
|
||||
const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal);
|
||||
const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal) override;
|
||||
std::shared_ptr<DerivationGoal> makeBasicDerivationGoal(
|
||||
const StorePath & drvPath, const BasicDerivation & drv,
|
||||
const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal);
|
||||
const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal) override;
|
||||
|
||||
/**
|
||||
* @ref SubstitutionGoal "substitution goal"
|
||||
*/
|
||||
std::shared_ptr<PathSubstitutionGoal> makePathSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
||||
std::shared_ptr<DrvOutputSubstitutionGoal> makeDrvOutputSubstitutionGoal(const DrvOutput & id, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
||||
std::shared_ptr<PathSubstitutionGoal> makePathSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt) override;
|
||||
std::shared_ptr<DrvOutputSubstitutionGoal> makeDrvOutputSubstitutionGoal(const DrvOutput & id, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt) override;
|
||||
|
||||
/**
|
||||
* Make a goal corresponding to the `DerivedPath`.
|
||||
@@ -222,31 +297,9 @@ public:
|
||||
* It will be a `DerivationGoal` for a `DerivedPath::Built` or
|
||||
* a `SubstitutionGoal` for a `DerivedPath::Opaque`.
|
||||
*/
|
||||
GoalPtr makeGoal(const DerivedPath & req, BuildMode buildMode = bmNormal);
|
||||
|
||||
/**
|
||||
* Remove a dead goal.
|
||||
*/
|
||||
void removeGoal(GoalPtr goal);
|
||||
|
||||
/**
|
||||
* Return the number of local build processes currently running (but not
|
||||
* remote builds via the build hook).
|
||||
*/
|
||||
unsigned int getNrLocalBuilds();
|
||||
|
||||
/**
|
||||
* Return the number of substitution processes currently running.
|
||||
*/
|
||||
unsigned int getNrSubstitutions();
|
||||
|
||||
/**
|
||||
* Registers a running child process. `inBuildSlot` means that
|
||||
* the process counts towards the jobs limit.
|
||||
*/
|
||||
void childStarted(GoalPtr goal, const std::set<int> & fds,
|
||||
bool inBuildSlot, bool respectTimeouts);
|
||||
GoalPtr makeGoal(const DerivedPath & req, BuildMode buildMode = bmNormal) override;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Unregisters a running child process.
|
||||
*/
|
||||
@@ -255,7 +308,7 @@ public:
|
||||
/**
|
||||
* Loop until the specified top-level goals have finished.
|
||||
*/
|
||||
void run(const Goals & topGoals);
|
||||
Goals run(std::function<Goals (GoalFactory &)> req);
|
||||
|
||||
/***
|
||||
* The exit status in case of failure.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "buildenv.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "store-api.hh"
|
||||
#include "archive.hh"
|
||||
#include "compression.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace nix {
|
||||
{ \
|
||||
return LengthPrefixedProtoHelper<CommonProto, T >::read(store, conn); \
|
||||
} \
|
||||
/* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
|
||||
TEMPLATE [[nodiscard]] WireFormatGenerator CommonProto::Serialise< T >::write(const Store & store, CommonProto::WriteConn conn, const T & t) \
|
||||
{ \
|
||||
return LengthPrefixedProtoHelper<CommonProto, T >::write(store, conn, t); \
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "args.hh"
|
||||
#include "content-address.hh"
|
||||
#include "split.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include "monitor-fd.hh"
|
||||
#include "worker-protocol.hh"
|
||||
#include "worker-protocol-impl.hh"
|
||||
#include "build-result.hh"
|
||||
#include "build-result.hh" // IWYU pragma: keep
|
||||
#include "store-api.hh"
|
||||
#include "store-cast.hh"
|
||||
#include "gc-store.hh"
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "finally.hh"
|
||||
#include "archive.hh"
|
||||
#include "derivations.hh"
|
||||
#include "strings.hh"
|
||||
#include "args.hh"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
#include "store-api.hh"
|
||||
#include "globals.hh"
|
||||
#include "types.hh"
|
||||
#include "split.hh"
|
||||
#include "common-protocol.hh"
|
||||
#include "common-protocol-impl.hh"
|
||||
#include "fs-accessor.hh"
|
||||
#include "json-utils.hh"
|
||||
#include "strings.hh"
|
||||
#include "backed-string-view.hh"
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "path.hh"
|
||||
#include "outputs-spec.hh"
|
||||
#include "comparator.hh"
|
||||
#include "ref.hh"
|
||||
|
||||
#include <variant>
|
||||
|
||||
@@ -78,10 +79,12 @@ struct SingleDerivedPathBuilt {
|
||||
DECLARE_CMP(SingleDerivedPathBuilt);
|
||||
};
|
||||
|
||||
using _SingleDerivedPathRaw = std::variant<
|
||||
namespace derived_path::detail {
|
||||
using SingleDerivedPathRaw = std::variant<
|
||||
DerivedPathOpaque,
|
||||
SingleDerivedPathBuilt
|
||||
>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A "derived path" is a very simple sort of expression (not a Nix
|
||||
@@ -94,8 +97,8 @@ using _SingleDerivedPathRaw = std::variant<
|
||||
* - built, in which case it is a pair of a derivation path and an
|
||||
* output name.
|
||||
*/
|
||||
struct SingleDerivedPath : _SingleDerivedPathRaw {
|
||||
using Raw = _SingleDerivedPathRaw;
|
||||
struct SingleDerivedPath : derived_path::detail::SingleDerivedPathRaw {
|
||||
using Raw = derived_path::detail::SingleDerivedPathRaw;
|
||||
using Raw::Raw;
|
||||
|
||||
using Opaque = DerivedPathOpaque;
|
||||
@@ -201,10 +204,12 @@ struct DerivedPathBuilt {
|
||||
DECLARE_CMP(DerivedPathBuilt);
|
||||
};
|
||||
|
||||
using _DerivedPathRaw = std::variant<
|
||||
namespace derived_path::detail {
|
||||
using DerivedPathRaw = std::variant<
|
||||
DerivedPathOpaque,
|
||||
DerivedPathBuilt
|
||||
>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A "derived path" is a very simple sort of expression that evaluates
|
||||
@@ -216,8 +221,8 @@ using _DerivedPathRaw = std::variant<
|
||||
* - built, in which case it is a pair of a derivation path and some
|
||||
* output names.
|
||||
*/
|
||||
struct DerivedPath : _DerivedPathRaw {
|
||||
using Raw = _DerivedPathRaw;
|
||||
struct DerivedPath : derived_path::detail::DerivedPathRaw {
|
||||
using Raw = derived_path::detail::DerivedPathRaw;
|
||||
using Raw::Raw;
|
||||
|
||||
using Opaque = DerivedPathOpaque;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "dummy-store.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
namespace nix {
|
||||
@@ -73,6 +74,8 @@ struct DummyStore : public virtual DummyStoreConfig, public virtual Store
|
||||
{ unsupported("getFSAccessor"); }
|
||||
};
|
||||
|
||||
static RegisterStoreImplementation<DummyStore, DummyStoreConfig> regDummyStore;
|
||||
void registerDummyStore() {
|
||||
StoreImplementations::add<DummyStore, DummyStoreConfig>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
namespace nix {
|
||||
|
||||
void registerDummyStore();
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "s3.hh"
|
||||
#include "signals.hh"
|
||||
#include "compression.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
#if ENABLE_S3
|
||||
#include <aws/core/client/ClientConfiguration.h>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
///@file
|
||||
|
||||
#include "box_ptr.hh"
|
||||
#include "ref.hh"
|
||||
#include "logging.hh"
|
||||
#include "serialise.hh"
|
||||
#include "types.hh"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "signals.hh"
|
||||
#include "finally.hh"
|
||||
#include "unix-domain-socket.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
#include <queue>
|
||||
#include <regex>
|
||||
|
||||
+37
-13
@@ -33,6 +33,16 @@
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
// All built-in store implementations.
|
||||
#include "dummy-store.hh"
|
||||
#include "http-binary-cache-store.hh"
|
||||
#include "legacy-ssh-store.hh"
|
||||
#include "local-binary-cache-store.hh"
|
||||
#include "local-store.hh"
|
||||
#include "s3-binary-cache-store.hh"
|
||||
#include "ssh-store.hh"
|
||||
#include "uds-remote-store.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
@@ -116,29 +126,30 @@ Settings::Settings()
|
||||
|
||||
void loadConfFile()
|
||||
{
|
||||
auto applyConfigFile = [&](const Path & path) {
|
||||
auto applyConfigFile = [&](const ApplyConfigOptions & options) {
|
||||
try {
|
||||
std::string contents = readFile(path);
|
||||
globalConfig.applyConfig(contents, path);
|
||||
} catch (SysError &) { }
|
||||
std::string contents = readFile(*options.path);
|
||||
globalConfig.applyConfig(contents, options);
|
||||
} catch (SysError &) {
|
||||
}
|
||||
};
|
||||
|
||||
applyConfigFile(settings.nixConfDir + "/nix.conf");
|
||||
applyConfigFile(ApplyConfigOptions{.path = settings.nixConfDir + "/nix.conf"});
|
||||
|
||||
/* We only want to send overrides to the daemon, i.e. stuff from
|
||||
~/.nix/nix.conf or the command line. */
|
||||
globalConfig.resetOverridden();
|
||||
|
||||
auto files = settings.nixUserConfFiles;
|
||||
auto home = getHome();
|
||||
for (auto file = files.rbegin(); file != files.rend(); file++) {
|
||||
applyConfigFile(*file);
|
||||
applyConfigFile(ApplyConfigOptions{.path = *file, .home = home});
|
||||
}
|
||||
|
||||
auto nixConfEnv = getEnv("NIX_CONFIG");
|
||||
if (nixConfEnv.has_value()) {
|
||||
globalConfig.applyConfig(nixConfEnv.value(), "NIX_CONFIG");
|
||||
globalConfig.applyConfig(nixConfEnv.value(), ApplyConfigOptions{.fromEnvVar = true});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::vector<Path> getUserConfigFiles()
|
||||
@@ -264,7 +275,7 @@ NLOHMANN_JSON_SERIALIZE_ENUM(SandboxMode, {
|
||||
{SandboxMode::smDisabled, false},
|
||||
});
|
||||
|
||||
template<> SandboxMode BaseSetting<SandboxMode>::parse(const std::string & str) const
|
||||
template<> SandboxMode BaseSetting<SandboxMode>::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||
{
|
||||
if (str == "true") return smEnabled;
|
||||
else if (str == "relaxed") return smRelaxed;
|
||||
@@ -307,7 +318,7 @@ template<> void BaseSetting<SandboxMode>::convertToArg(Args & args, const std::s
|
||||
});
|
||||
}
|
||||
|
||||
unsigned int MaxBuildJobsSetting::parse(const std::string & str) const
|
||||
unsigned int MaxBuildJobsSetting::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||
{
|
||||
if (str == "auto") return std::max(1U, std::thread::hardware_concurrency());
|
||||
else {
|
||||
@@ -315,15 +326,15 @@ unsigned int MaxBuildJobsSetting::parse(const std::string & str) const
|
||||
return *n;
|
||||
else
|
||||
throw UsageError("configuration setting '%s' should be 'auto' or an integer", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Paths PluginFilesSetting::parse(const std::string & str) const
|
||||
Paths PluginFilesSetting::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||
{
|
||||
if (pluginsLoaded)
|
||||
throw UsageError("plugin-files set after plugins were loaded, you may need to move the flag before the subcommand");
|
||||
return BaseSetting<Paths>::parse(str);
|
||||
return BaseSetting<Paths>::parse(str, options);
|
||||
}
|
||||
|
||||
|
||||
@@ -396,6 +407,17 @@ static void preloadNSS()
|
||||
});
|
||||
}
|
||||
|
||||
static void registerStoreImplementations() {
|
||||
registerDummyStore();
|
||||
registerHttpBinaryCacheStore();
|
||||
registerLegacySSHStore();
|
||||
registerLocalBinaryCacheStore();
|
||||
registerLocalStore();
|
||||
registerS3BinaryCacheStore();
|
||||
registerSSHStore();
|
||||
registerUDSRemoteStore();
|
||||
}
|
||||
|
||||
static bool initLibStoreDone = false;
|
||||
|
||||
void assertLibStoreInitialized() {
|
||||
@@ -433,6 +455,8 @@ void initLibStore() {
|
||||
unsetenv("TMPDIR");
|
||||
#endif
|
||||
|
||||
registerStoreImplementations();
|
||||
|
||||
initLibStoreDone = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ struct MaxBuildJobsSetting : public BaseSetting<unsigned int>
|
||||
options->addSetting(this);
|
||||
}
|
||||
|
||||
unsigned int parse(const std::string & str) const override;
|
||||
unsigned int parse(const std::string & str, const ApplyConfigOptions & options) const override;
|
||||
};
|
||||
|
||||
struct PluginFilesSetting : public BaseSetting<Paths>
|
||||
@@ -43,7 +43,7 @@ struct PluginFilesSetting : public BaseSetting<Paths>
|
||||
options->addSetting(this);
|
||||
}
|
||||
|
||||
Paths parse(const std::string & str) const override;
|
||||
Paths parse(const std::string & str, const ApplyConfigOptions & options) const override;
|
||||
};
|
||||
|
||||
const uint32_t maxIdsPerBuild =
|
||||
@@ -634,7 +634,7 @@ public:
|
||||
line.
|
||||
)"};
|
||||
|
||||
OptionalPathSetting diffHook{
|
||||
PathsSetting<std::optional<Path>> diffHook{
|
||||
this, std::nullopt, "diff-hook",
|
||||
R"(
|
||||
Absolute path to an executable capable of diffing build
|
||||
@@ -1088,6 +1088,7 @@ void loadConfFile();
|
||||
|
||||
// Used by the Settings constructor
|
||||
std::vector<Path> getUserConfigFiles();
|
||||
std::vector<Path> getHomeConfigFile();
|
||||
|
||||
extern const std::string nixVersion;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "http-binary-cache-store.hh"
|
||||
#include "binary-cache-store.hh"
|
||||
#include "filetransfer.hh"
|
||||
#include "globals.hh"
|
||||
@@ -194,6 +195,8 @@ protected:
|
||||
}
|
||||
};
|
||||
|
||||
static RegisterStoreImplementation<HttpBinaryCacheStore, HttpBinaryCacheStoreConfig> regHttpBinaryCacheStore;
|
||||
void registerHttpBinaryCacheStore() {
|
||||
StoreImplementations::add<HttpBinaryCacheStore, HttpBinaryCacheStoreConfig>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
namespace nix {
|
||||
|
||||
void registerHttpBinaryCacheStore();
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "ssh-store-config.hh"
|
||||
#include "legacy-ssh-store.hh"
|
||||
#include "archive.hh"
|
||||
#include "pool.hh"
|
||||
#include "remote-store.hh"
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "store-api.hh"
|
||||
#include "path-with-outputs.hh"
|
||||
#include "ssh.hh"
|
||||
#include "ssh-store.hh"
|
||||
#include "strings.hh"
|
||||
#include "derivations.hh"
|
||||
|
||||
namespace nix {
|
||||
@@ -412,6 +414,8 @@ public:
|
||||
{ unsupported("queryRealisation"); }
|
||||
};
|
||||
|
||||
static RegisterStoreImplementation<LegacySSHStore, LegacySSHStoreConfig> regLegacySSHStore;
|
||||
void registerLegacySSHStore() {
|
||||
StoreImplementations::add<LegacySSHStore, LegacySSHStoreConfig>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
namespace nix {
|
||||
|
||||
void registerLegacySSHStore();
|
||||
|
||||
}
|
||||
@@ -61,9 +61,9 @@ template<class Inner, typename... Ts>
|
||||
LENGTH_PREFIXED_PROTO_HELPER(Inner, std::tuple<Ts...>);
|
||||
|
||||
template<class Inner, typename K, typename V>
|
||||
#define _X std::map<K, V>
|
||||
LENGTH_PREFIXED_PROTO_HELPER(Inner, _X);
|
||||
#undef _X
|
||||
#define DONT_SUBSTITUTE_KV_TYPE std::map<K, V>
|
||||
LENGTH_PREFIXED_PROTO_HELPER(Inner, DONT_SUBSTITUTE_KV_TYPE);
|
||||
#undef DONT_SUBSTITUTE_KV_TYPE
|
||||
|
||||
template<class Inner, typename T>
|
||||
std::vector<T>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "local-binary-cache-store.hh"
|
||||
#include "binary-cache-store.hh"
|
||||
#include "globals.hh"
|
||||
#include "nar-info-disk-cache.hh"
|
||||
@@ -124,6 +125,8 @@ std::set<std::string> LocalBinaryCacheStore::uriSchemes()
|
||||
return {"file"};
|
||||
}
|
||||
|
||||
static RegisterStoreImplementation<LocalBinaryCacheStore, LocalBinaryCacheStoreConfig> regLocalBinaryCacheStore;
|
||||
void registerLocalBinaryCacheStore() {
|
||||
StoreImplementations::add<LocalBinaryCacheStore, LocalBinaryCacheStoreConfig>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
namespace nix {
|
||||
|
||||
void registerLocalBinaryCacheStore();
|
||||
|
||||
}
|
||||
@@ -11,21 +11,21 @@ struct LocalFSStoreConfig : virtual StoreConfig
|
||||
{
|
||||
using StoreConfig::StoreConfig;
|
||||
|
||||
const OptionalPathSetting rootDir{this, std::nullopt,
|
||||
const PathsSetting<std::optional<Path>> rootDir{this, std::nullopt,
|
||||
"root",
|
||||
"Directory prefixed to all other paths."};
|
||||
|
||||
const PathSetting stateDir{this,
|
||||
const PathsSetting<Path> stateDir{this,
|
||||
rootDir.get() ? *rootDir.get() + "/nix/var/nix" : settings.nixStateDir,
|
||||
"state",
|
||||
"Directory where Lix will store state."};
|
||||
|
||||
const PathSetting logDir{this,
|
||||
const PathsSetting<Path> logDir{this,
|
||||
rootDir.get() ? *rootDir.get() + "/nix/var/log/nix" : settings.nixLogDir,
|
||||
"log",
|
||||
"directory where Lix will store log files."};
|
||||
|
||||
const PathSetting realStoreDir{this,
|
||||
const PathsSetting<Path> realStoreDir{this,
|
||||
rootDir.get() ? *rootDir.get() + "/nix/store" : storeDir, "real",
|
||||
"Physical path of the Nix store."};
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "signals.hh"
|
||||
#include "finally.hh"
|
||||
#include "compression.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
@@ -421,4 +421,7 @@ void canonicaliseTimestampAndPermissions(const Path & path);
|
||||
|
||||
MakeError(PathInUse, Error);
|
||||
|
||||
// Implemented by the relevant platform/ module being used.
|
||||
void registerLocalStore();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "types.hh"
|
||||
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <vector>
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "types.hh"
|
||||
#include "ref.hh"
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "make-content-addressed.hh"
|
||||
#include "references.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
@@ -118,12 +118,16 @@ libstore_headers = files(
|
||||
'derived-path-map.hh',
|
||||
'derived-path.hh',
|
||||
'downstream-placeholder.hh',
|
||||
'dummy-store.hh',
|
||||
'filetransfer.hh',
|
||||
'fs-accessor.hh',
|
||||
'gc-store.hh',
|
||||
'globals.hh',
|
||||
'http-binary-cache-store.hh',
|
||||
'indirect-root-store.hh',
|
||||
'legacy-ssh-store.hh',
|
||||
'length-prefixed-protocol-helper.hh',
|
||||
'local-binary-cache-store.hh',
|
||||
'local-fs-store.hh',
|
||||
'local-store.hh',
|
||||
'lock.hh',
|
||||
@@ -152,8 +156,8 @@ libstore_headers = files(
|
||||
'serve-protocol-impl.hh',
|
||||
'serve-protocol.hh',
|
||||
'sqlite.hh',
|
||||
'ssh-store-config.hh',
|
||||
'ssh.hh',
|
||||
'ssh-store.hh',
|
||||
'store-api.hh',
|
||||
'store-cast.hh',
|
||||
'uds-remote-store.hh',
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "derivations.hh"
|
||||
#include "parsed-derivations.hh"
|
||||
#include "globals.hh"
|
||||
#include "local-store.hh"
|
||||
#include "store-api.hh"
|
||||
#include "thread-pool.hh"
|
||||
#include "topo-sort.hh"
|
||||
#include "closure.hh"
|
||||
#include "filetransfer.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "fs-accessor.hh"
|
||||
#include "ref.hh"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
#include "fs-accessor.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "sqlite.hh"
|
||||
#include "globals.hh"
|
||||
#include "users.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "globals.hh"
|
||||
#include "strings.hh"
|
||||
#include "nar-info.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "local-store.hh"
|
||||
#include "globals.hh"
|
||||
#include "signals.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
#include <cstring>
|
||||
#include <sys/types.h>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "parsed-derivations.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <regex>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "path-info.hh"
|
||||
#include "store-api.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "path-with-outputs.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
#include <regex>
|
||||
#include "strings.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
@@ -112,3 +112,13 @@ PathSet Store::printStorePathSet(const StorePathSet & paths) const
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::size_t std::hash<nix::StorePath>::operator()(const nix::StorePath & path) const noexcept
|
||||
{
|
||||
// It's already a cryptographic hash of 160 bits (assuming that nobody gives us bogus ones...), so just parse it.
|
||||
auto h = nix::Hash::parseNonSRIUnprefixed(path.hashPart(), nix::HashType::SHA1);
|
||||
// This need not be stable across machines, so bit casting the start of it is fine.
|
||||
size_t r;
|
||||
memcpy(&r, h.hash, sizeof(r));
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
///@file
|
||||
|
||||
#include <string_view>
|
||||
#include <string>
|
||||
|
||||
#include "types.hh"
|
||||
#include "types.hh" // IWYU pragma: keep
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -89,10 +90,7 @@ const std::string drvExtension = ".drv";
|
||||
namespace std {
|
||||
|
||||
template<> struct hash<nix::StorePath> {
|
||||
std::size_t operator()(const nix::StorePath & path) const noexcept
|
||||
{
|
||||
return * (std::size_t *) path.to_string().data();
|
||||
}
|
||||
std::size_t operator()(const nix::StorePath & path) const noexcept;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -49,8 +49,12 @@ struct FdLock
|
||||
|
||||
~FdLock()
|
||||
{
|
||||
if (acquired)
|
||||
lockFile(fd, ltNone, false);
|
||||
try {
|
||||
if (acquired)
|
||||
lockFile(fd, ltNone, false);
|
||||
} catch (SysError &) {
|
||||
ignoreException();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user