forked from lix-project/lix
Fix tests on systems with a non-master git defaultBranch #1
|
@ -29,3 +29,7 @@ trim_trailing_whitespace = false
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
max_line_length = 0
|
max_line_length = 0
|
||||||
|
|
||||||
|
[meson.build]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
name: Missing or incorrect documentation
|
name: Missing or incorrect documentation
|
||||||
about: Help us improve the reference manual
|
about: Help us improve the reference manual
|
||||||
title: ''
|
title: ''
|
||||||
labels: documentation
|
labels: docs
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -19,10 +19,10 @@ assignees: ''
|
||||||
|
|
||||||
<!-- make sure this issue is not redundant or obsolete -->
|
<!-- make sure this issue is not redundant or obsolete -->
|
||||||
|
|
||||||
- [ ] checked [latest Lix manual] \([source]\)
|
- [ ] checked [latest Lix manual] or its [source code]
|
||||||
- [ ] checked [documentation issues] and [recent documentation changes] for possible duplicates
|
- [ ] checked [documentation issues] and [recent documentation changes] for possible duplicates
|
||||||
|
|
||||||
[latest Nix manual]: https://docs.lix.systems/manual/lix/nightly
|
[latest Lix manual]: https://docs.lix.systems/manual/lix/nightly
|
||||||
[source]: https://git.lix.systems/lix-project/lix/src/main/doc/manual/src
|
[source code]: https://git.lix.systems/lix-project/lix/src/main/doc/manual/src
|
||||||
[documentation issues]: https://git.lix.systems/lix-project/lix/issues?labels=151&state=all
|
[documentation issues]: https://git.lix.systems/lix-project/lix/issues?labels=151&state=all
|
||||||
[recent documentation changes]: https://gerrit.lix.systems/q/p:lix+path:%22%5Edoc/manual/.*%22
|
[recent documentation changes]: https://gerrit.lix.systems/q/p:lix+path:%22%5Edoc/manual/.*%22
|
||||||
|
|
28
Cargo.lock
generated
28
Cargo.lock
generated
|
@ -2,12 +2,6 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "autocfg"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "countme"
|
name = "countme"
|
||||||
version = "3.0.1"
|
version = "3.0.1"
|
||||||
|
@ -16,15 +10,15 @@ checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dissimilar"
|
name = "dissimilar"
|
||||||
version = "1.0.7"
|
version = "1.0.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "86e3bdc80eee6e16b2b6b0f87fbc98c04bee3455e35174c0de1a125d0688c632"
|
checksum = "59f8e79d1fbf76bdfbde321e902714bf6c49df88a7dda6fc682fc2979226962d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "expect-test"
|
name = "expect-test"
|
||||||
version = "1.4.1"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "30d9eafeadd538e68fb28016364c9732d78e420b9ff8853fa5e4058861e9f8d3"
|
checksum = "9e0be0a561335815e06dab7c62e50353134c796e7a6155402a64bcff66b6a5e0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dissimilar",
|
"dissimilar",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -45,15 +39,6 @@ dependencies = [
|
||||||
"rowan",
|
"rowan",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memoffset"
|
|
||||||
version = "0.9.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.19.0"
|
version = "1.19.0"
|
||||||
|
@ -71,13 +56,12 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rowan"
|
name = "rowan"
|
||||||
version = "0.15.15"
|
version = "0.15.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32a58fa8a7ccff2aec4f39cc45bf5f985cec7125ab271cf681c279fd00192b49"
|
checksum = "0a542b0253fa46e632d27a1dc5cf7b930de4df8659dc6e720b647fc72147ae3d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"countme",
|
"countme",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
"memoffset",
|
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"text-size",
|
"text-size",
|
||||||
]
|
]
|
||||||
|
|
|
@ -33,32 +33,7 @@ GENERATE_LATEX = NO
|
||||||
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
||||||
# Note: If this tag is empty the current directory is searched.
|
# Note: If this tag is empty the current directory is searched.
|
||||||
|
|
||||||
# FIXME Make this list more maintainable somehow. We could maybe generate this
|
INPUT = @INPUT_PATHS@
|
||||||
# in the Makefile, but we would need to change how `.in` files are preprocessed
|
|
||||||
# so they can expand variables despite configure variables.
|
|
||||||
|
|
||||||
INPUT = \
|
|
||||||
src/libcmd \
|
|
||||||
src/libexpr \
|
|
||||||
src/libexpr/flake \
|
|
||||||
tests/unit/libexpr \
|
|
||||||
tests/unit/libexpr/value \
|
|
||||||
tests/unit/libexpr/test \
|
|
||||||
tests/unit/libexpr/test/value \
|
|
||||||
src/libexpr/value \
|
|
||||||
src/libfetchers \
|
|
||||||
src/libmain \
|
|
||||||
src/libstore \
|
|
||||||
src/libstore/build \
|
|
||||||
src/libstore/builtins \
|
|
||||||
tests/unit/libstore \
|
|
||||||
tests/unit/libstore/test \
|
|
||||||
src/libutil \
|
|
||||||
tests/unit/libutil \
|
|
||||||
tests/unit/libutil/test \
|
|
||||||
src/nix \
|
|
||||||
src/nix-env \
|
|
||||||
src/nix-store
|
|
||||||
|
|
||||||
# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
|
# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
|
||||||
# in the source code. If set to NO, only conditional compilation will be
|
# in the source code. If set to NO, only conditional compilation will be
|
||||||
|
@ -97,3 +72,15 @@ EXPAND_AS_DEFINED = \
|
||||||
DECLARE_WORKER_SERIALISER \
|
DECLARE_WORKER_SERIALISER \
|
||||||
DECLARE_SERVE_SERIALISER \
|
DECLARE_SERVE_SERIALISER \
|
||||||
LENGTH_PREFIXED_PROTO_HELPER
|
LENGTH_PREFIXED_PROTO_HELPER
|
||||||
|
|
||||||
|
# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
|
||||||
|
# Stripping is only done if one of the specified strings matches the left-hand
|
||||||
|
# part of the path. The tag can be used to show relative paths in the file list.
|
||||||
|
# If left blank the directory from which doxygen is run is used as the path to
|
||||||
|
# strip.
|
||||||
|
#
|
||||||
|
# Note that you can specify absolute paths here, but also relative paths, which
|
||||||
|
# will be relative from the directory where doxygen is started.
|
||||||
|
# This tag requires that the tag FULL_PATH_NAMES is set to YES.
|
||||||
|
|
||||||
|
STRIP_FROM_PATH = "@PROJECT_SOURCE_ROOT@"
|
||||||
|
|
|
@ -1,3 +1,35 @@
|
||||||
|
internal_api_sources = [
|
||||||
|
'src/libcmd',
|
||||||
|
'src/libexpr',
|
||||||
|
'src/libexpr/flake',
|
||||||
|
'tests/unit/libexpr',
|
||||||
|
'tests/unit/libexpr/value',
|
||||||
|
'tests/unit/libexpr/test',
|
||||||
|
'tests/unit/libexpr/test/value',
|
||||||
|
'src/libexpr/value',
|
||||||
|
'src/libfetchers',
|
||||||
|
'src/libmain',
|
||||||
|
'src/libstore',
|
||||||
|
'src/libstore/build',
|
||||||
|
'src/libstore/builtins',
|
||||||
|
'tests/unit/libstore',
|
||||||
|
'tests/unit/libstore/test',
|
||||||
|
'src/libutil',
|
||||||
|
'tests/unit/libutil',
|
||||||
|
'tests/unit/libutil/test',
|
||||||
|
'src/nix',
|
||||||
|
'src/nix-env',
|
||||||
|
'src/nix-store',
|
||||||
|
]
|
||||||
|
|
||||||
|
# We feed Doxygen absolute paths so it can be invoked from any working directory.
|
||||||
|
internal_api_sources_absolute = []
|
||||||
|
foreach src : internal_api_sources
|
||||||
|
internal_api_sources_absolute += '"' + (meson.project_source_root() / src) + '"'
|
||||||
|
endforeach
|
||||||
|
|
||||||
|
internal_api_sources_oneline = ' \\\n '.join(internal_api_sources_absolute)
|
||||||
|
|
||||||
doxygen_cfg = configure_file(
|
doxygen_cfg = configure_file(
|
||||||
input : 'doxygen.cfg.in',
|
input : 'doxygen.cfg.in',
|
||||||
output : 'doxygen.cfg',
|
output : 'doxygen.cfg',
|
||||||
|
@ -5,22 +37,16 @@ doxygen_cfg = configure_file(
|
||||||
'PACKAGE_VERSION': meson.project_version(),
|
'PACKAGE_VERSION': meson.project_version(),
|
||||||
'RAPIDCHECK_HEADERS': rapidcheck_meson.get_variable('includedir'),
|
'RAPIDCHECK_HEADERS': rapidcheck_meson.get_variable('includedir'),
|
||||||
'docdir' : meson.current_build_dir(),
|
'docdir' : meson.current_build_dir(),
|
||||||
|
'INPUT_PATHS' : internal_api_sources_oneline,
|
||||||
|
'PROJECT_SOURCE_ROOT' : meson.project_source_root(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
internal_api_docs = custom_target(
|
internal_api_docs = custom_target(
|
||||||
'internal-api-docs',
|
'internal-api-docs',
|
||||||
command : [
|
command : [
|
||||||
bash,
|
|
||||||
# Meson can you please just give us a `workdir` argument to custom targets...
|
|
||||||
'-c',
|
|
||||||
# We have to prefix the doxygen_cfg path with the project build root
|
|
||||||
# because of the cd in front.
|
|
||||||
'cd @0@ && @1@ @2@/@INPUT0@'.format(
|
|
||||||
meson.project_source_root(),
|
|
||||||
doxygen.full_path(),
|
doxygen.full_path(),
|
||||||
meson.project_build_root(),
|
'@INPUT0@',
|
||||||
),
|
|
||||||
],
|
],
|
||||||
input : [
|
input : [
|
||||||
doxygen_cfg,
|
doxygen_cfg,
|
||||||
|
|
|
@ -147,3 +147,6 @@ winter:
|
||||||
|
|
||||||
yshui:
|
yshui:
|
||||||
github: yshui
|
github: yshui
|
||||||
|
|
||||||
|
zimbatm:
|
||||||
|
github: zimbatm
|
||||||
|
|
|
@ -126,20 +126,19 @@ manual = custom_target(
|
||||||
'manual',
|
'manual',
|
||||||
'markdown',
|
'markdown',
|
||||||
],
|
],
|
||||||
|
install : true,
|
||||||
|
install_dir : [
|
||||||
|
datadir / 'doc/nix',
|
||||||
|
false,
|
||||||
|
],
|
||||||
depfile : 'manual.d',
|
depfile : 'manual.d',
|
||||||
env : {
|
env : {
|
||||||
'RUST_LOG': 'info',
|
'RUST_LOG': 'info',
|
||||||
'MDBOOK_SUBSTITUTE_SEARCH': meson.current_build_dir() / 'src',
|
'MDBOOK_SUBSTITUTE_SEARCH': meson.current_build_dir() / 'src',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
manual_html = manual[0]
|
|
||||||
manual_md = manual[1]
|
manual_md = manual[1]
|
||||||
|
|
||||||
install_subdir(
|
|
||||||
manual_html.full_path(),
|
|
||||||
install_dir : datadir / 'doc/nix',
|
|
||||||
)
|
|
||||||
|
|
||||||
nix_nested_manpages = [
|
nix_nested_manpages = [
|
||||||
[ 'nix-env',
|
[ 'nix-env',
|
||||||
[
|
[
|
||||||
|
|
10
doc/manual/rl-next/alt-left-and-alt-right-in-repl.md
Normal file
10
doc/manual/rl-next/alt-left-and-alt-right-in-repl.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
synopsis: "`Alt+Left` and `Alt+Right` go back/forwards by words in `nix repl`"
|
||||||
|
issues: [fj#501]
|
||||||
|
cls: [1883]
|
||||||
|
category: Fixes
|
||||||
|
credits: 9999years
|
||||||
|
---
|
||||||
|
|
||||||
|
`nix repl` now recognizes `Alt+Left` and `Alt+Right` for navigating by words
|
||||||
|
when entering input in `nix repl` on more terminals/platforms.
|
13
doc/manual/rl-next/ctrl-c-improved.md
Normal file
13
doc/manual/rl-next/ctrl-c-improved.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
synopsis: Ctrl-C stops Nix commands much more reliably and responsively
|
||||||
|
issues: [7245, fj#393]
|
||||||
|
cls: [2016]
|
||||||
|
prs: [11618]
|
||||||
|
category: Fixes
|
||||||
|
credits: [roberth, 9999years]
|
||||||
|
---
|
||||||
|
|
||||||
|
CTRL-C will now stop Nix commands much more reliably and responsively. While
|
||||||
|
there are still some cases where a Nix command can be slow or unresponsive
|
||||||
|
following a `SIGINT` (please report these as issues!), the vast majority of
|
||||||
|
signals will now cause the Nix command to quit quickly and consistently.
|
23
doc/manual/rl-next/fetchGit-regression.md
Normal file
23
doc/manual/rl-next/fetchGit-regression.md
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
synopsis: restore backwards-compatibility of `builtins.fetchGit` with Nix 2.3
|
||||||
|
issues: [5291, 5128]
|
||||||
|
credits: [ma27]
|
||||||
|
category: Fixes
|
||||||
|
---
|
||||||
|
|
||||||
|
Compatibility with `builtins.fetchGit` from Nix 2.3 has been restored as follows:
|
||||||
|
|
||||||
|
* Until now, each `ref` was prefixed with `refs/heads` unless it starts with `refs/` itself.
|
||||||
|
|
||||||
|
Now, this is not done if the `ref` looks like a commit hash.
|
||||||
|
|
||||||
|
* Specifying `builtins.fetchGit { ref = "a-tag"; /* … */ }` was broken because `refs/heads` was appended.
|
||||||
|
|
||||||
|
Now, the fetcher doesn't turn a ref into `refs/heads/ref`, but into `refs/*/ref`. That way,
|
||||||
|
the value in `ref` can be either a tag or a branch.
|
||||||
|
|
||||||
|
* The ref resolution happens the same way as in git:
|
||||||
|
|
||||||
|
* If `refs/ref` exists, it's used.
|
||||||
|
* If a tag `refs/tags/ref` exists, it's used.
|
||||||
|
* If a branch `refs/heads/ref` exists, it's used.
|
38
doc/manual/rl-next/nix-fmt-default-argument.md
Normal file
38
doc/manual/rl-next/nix-fmt-default-argument.md
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
---
|
||||||
|
synopsis: Removing the `.` default argument passed to the `nix fmt` formatter
|
||||||
|
issues: []
|
||||||
|
prs: [11438]
|
||||||
|
cls: [1902]
|
||||||
|
category: Breaking Changes
|
||||||
|
credits: zimbatm
|
||||||
|
---
|
||||||
|
|
||||||
|
The underlying formatter no longer receives the ". " default argument when `nix fmt` is called with no arguments.
|
||||||
|
|
||||||
|
This change was necessary as the formatter wasn't able to distinguish between
|
||||||
|
a user wanting to format the current folder with `nix fmt .` or the generic
|
||||||
|
`nix fmt`.
|
||||||
|
|
||||||
|
The default behaviour is now the responsibility of the formatter itself, and
|
||||||
|
allows tools such as treefmt to format the whole tree instead of only the
|
||||||
|
current directory and below.
|
||||||
|
|
||||||
|
This may cause issues with some formatters: nixfmt, nixpkgs-fmt and alejandra currently format stdin when no arguments are passed.
|
||||||
|
|
||||||
|
Here is a small wrapper example that will restore the previous behaviour for such a formatter:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
outputs = { self, nixpkgs, systems }:
|
||||||
|
let
|
||||||
|
eachSystem = nixpkgs.lib.genAttrs (import systems) (system: nixpkgs.legacyPackages.${system});
|
||||||
|
in
|
||||||
|
{
|
||||||
|
formatter = eachSystem (pkgs:
|
||||||
|
pkgs.writeShellScriptBin "formatter" ''
|
||||||
|
if [[ $# = 0 ]]; set -- .; fi
|
||||||
|
exec "${pkgs.nixfmt-rfc-style}/bin/nixfmt "$@"
|
||||||
|
'');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
17
doc/manual/rl-next/readline-support-removed.md
Normal file
17
doc/manual/rl-next/readline-support-removed.md
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
synopsis: readline support removed
|
||||||
|
cls: [1885]
|
||||||
|
category: Packaging
|
||||||
|
credits: [9999years]
|
||||||
|
---
|
||||||
|
|
||||||
|
Support for building Lix with [`readline`][readline] instead of
|
||||||
|
[`editline`][editline] has been removed. `readline` support hasn't worked for a
|
||||||
|
long time (attempting to use it would lead to build errors) and would make Lix
|
||||||
|
subject to the GPL if it did work. In the future, we're hoping to replace
|
||||||
|
`editline` with [`rustyline`][rustyline] for improved ergonomics in the `nix
|
||||||
|
repl`.
|
||||||
|
|
||||||
|
[readline]: https://en.wikipedia.org/wiki/GNU_Readline
|
||||||
|
[editline]: https://github.com/troglobit/editline
|
||||||
|
[rustyline]: https://github.com/kkawakam/rustyline
|
30
doc/manual/rl-next/relative-and-tilde-paths-in-config.md
Normal file
30
doc/manual/rl-next/relative-and-tilde-paths-in-config.md
Normal file
|
@ -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.
|
26
doc/manual/rl-next/stack-traces.md
Normal file
26
doc/manual/rl-next/stack-traces.md
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
synopsis: "Some Lix crashes now produce reporting instructions and a stack trace, then abort"
|
||||||
|
cls: [1854]
|
||||||
|
category: Improvements
|
||||||
|
credits: jade
|
||||||
|
---
|
||||||
|
|
||||||
|
Lix, being a C++ program, can crash in a few kinds of ways.
|
||||||
|
It can obviously do a memory access violation, which will generate a core dump and thus be relatively debuggable.
|
||||||
|
But, worse, it could throw an unhandled exception, and, in the past, we would just show the message but not where it comes from, in spite of this always being a bug, since we expect all such errors to be translated to a Lix specific error.
|
||||||
|
Now the latter kind of bug should print reporting instructions, a rudimentary stack trace and (depending on system configuration) generate a core dump.
|
||||||
|
|
||||||
|
Sample output:
|
||||||
|
|
||||||
|
```
|
||||||
|
Lix crashed. This is a bug. We would appreciate if you report it along with what caused it at https://git.lix.systems/lix-project/lix/issues with the following information included:
|
||||||
|
|
||||||
|
Exception: std::runtime_error: test exception
|
||||||
|
Stack trace:
|
||||||
|
0# nix::printStackTrace() in /home/jade/lix/lix3/build/src/nix/../libutil/liblixutil.so
|
||||||
|
1# 0x000073C9862331F2 in /home/jade/lix/lix3/build/src/nix/../libmain/liblixmain.so
|
||||||
|
2# 0x000073C985F2E21A in /nix/store/p44qan69linp3ii0xrviypsw2j4qdcp2-gcc-13.2.0-lib/lib/libstdc++.so.6
|
||||||
|
3# 0x000073C985F2E285 in /nix/store/p44qan69linp3ii0xrviypsw2j4qdcp2-gcc-13.2.0-lib/lib/libstdc++.so.6
|
||||||
|
4# nix::handleExceptions(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::function<void ()>) in /home/jade/lix/lix3/build/src/nix/../libmain/liblixmain.so
|
||||||
|
...
|
||||||
|
```
|
10
doc/manual/rl-next/verify-tls.md
Normal file
10
doc/manual/rl-next/verify-tls.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
synopsis: "`<nix/fetchurl.nix>` now uses TLS verification"
|
||||||
|
category: Fixes
|
||||||
|
prs: [11585]
|
||||||
|
credits: edolstra
|
||||||
|
---
|
||||||
|
|
||||||
|
Previously `<nix/fetchurl.nix>` did not do TLS verification. This was because the Nix sandbox in the past did not have access to TLS certificates, and Nix checks the hash of the fetched file anyway. However, this can expose authentication data from `netrc` and URLs to man-in-the-middle attackers. In addition, Nix now in some cases (such as when using impure derivations) does *not* check the hash. Therefore we have now enabled TLS verification. This means that downloads by `<nix/fetchurl.nix>` will now fail if you're fetching from a HTTPS server that does not have a valid certificate.
|
||||||
|
|
||||||
|
`<nix/fetchurl.nix>` is also known as the builtin derivation builder `builtin:fetchurl`. It's not to be confused with the evaluation-time function `builtins.fetchurl`, which was not affected by this issue.
|
|
@ -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:
|
- 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`.
|
- 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.
|
- 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.
|
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.
|
||||||
|
|
13
flake.nix
13
flake.nix
|
@ -99,9 +99,10 @@
|
||||||
];
|
];
|
||||||
|
|
||||||
stdenvs = [
|
stdenvs = [
|
||||||
"gccStdenv"
|
# see assertion in package.nix why these two are disabled
|
||||||
|
# "stdenv"
|
||||||
|
# "gccStdenv"
|
||||||
"clangStdenv"
|
"clangStdenv"
|
||||||
"stdenv"
|
|
||||||
"libcxxStdenv"
|
"libcxxStdenv"
|
||||||
"ccacheStdenv"
|
"ccacheStdenv"
|
||||||
];
|
];
|
||||||
|
@ -121,7 +122,11 @@
|
||||||
name = "${stdenvName}Packages";
|
name = "${stdenvName}Packages";
|
||||||
value = f stdenvName;
|
value = f stdenvName;
|
||||||
}) stdenvs
|
}) stdenvs
|
||||||
);
|
)
|
||||||
|
// {
|
||||||
|
# TODO delete this and reënable gcc stdenvs once gcc compiles kj coros correctly
|
||||||
|
stdenvPackages = f "clangStdenv";
|
||||||
|
};
|
||||||
|
|
||||||
# Memoize nixpkgs for different platforms for efficiency.
|
# Memoize nixpkgs for different platforms for efficiency.
|
||||||
nixpkgsFor = forAllSystems (
|
nixpkgsFor = forAllSystems (
|
||||||
|
@ -212,7 +217,7 @@
|
||||||
|
|
||||||
# A Nixpkgs overlay that overrides the 'nix' and
|
# A Nixpkgs overlay that overrides the 'nix' and
|
||||||
# 'nix.perl-bindings' packages.
|
# 'nix.perl-bindings' packages.
|
||||||
overlays.default = overlayFor (p: p.stdenv);
|
overlays.default = overlayFor (p: p.clangStdenv);
|
||||||
|
|
||||||
hydraJobs = {
|
hydraJobs = {
|
||||||
# Binary package for various platforms.
|
# Binary package for various platforms.
|
||||||
|
|
32
meson.build
32
meson.build
|
@ -47,12 +47,12 @@
|
||||||
# in the build directory.
|
# in the build directory.
|
||||||
|
|
||||||
project('lix', 'cpp', 'rust',
|
project('lix', 'cpp', 'rust',
|
||||||
|
meson_version : '>=1.4.0',
|
||||||
version : run_command('bash', '-c', 'echo -n $(jq -r .version < ./version.json)$VERSION_SUFFIX', check : true).stdout().strip(),
|
version : run_command('bash', '-c', 'echo -n $(jq -r .version < ./version.json)$VERSION_SUFFIX', check : true).stdout().strip(),
|
||||||
default_options : [
|
default_options : [
|
||||||
'cpp_std=c++2a',
|
'cpp_std=c++23',
|
||||||
'rust_std=2021',
|
'rust_std=2021',
|
||||||
# TODO(Qyriad): increase the warning level
|
'warning_level=2',
|
||||||
'warning_level=1',
|
|
||||||
'debug=true',
|
'debug=true',
|
||||||
'optimization=2',
|
'optimization=2',
|
||||||
'errorlogs=true', # Please print logs for tests that fail
|
'errorlogs=true', # Please print logs for tests that fail
|
||||||
|
@ -167,10 +167,18 @@ endif
|
||||||
# frees one would expect when the objects are unique_ptrs. these problems
|
# frees one would expect when the objects are unique_ptrs. these problems
|
||||||
# often show up as memory corruption when nesting generators (since we do
|
# often show up as memory corruption when nesting generators (since we do
|
||||||
# treat generators like owned memory) and will cause inexplicable crashs.
|
# treat generators like owned memory) and will cause inexplicable crashs.
|
||||||
|
#
|
||||||
|
# gcc 13 does not compile capnp coroutine code correctly. a newer version
|
||||||
|
# may fix this. (cf. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102051)
|
||||||
|
# we allow gcc 13 here anyway because CI uses it for clang-tidy, and when
|
||||||
|
# the compiler crashes outright if won't produce any bad binaries either.
|
||||||
assert(
|
assert(
|
||||||
cxx.get_id() != 'gcc' or cxx.version().version_compare('>=13'),
|
cxx.get_id() != 'gcc' or cxx.version().version_compare('>=13'),
|
||||||
'GCC 12 and earlier are known to miscompile lix coroutines, use GCC 13 or clang.'
|
'GCC is known to miscompile coroutines, use clang.'
|
||||||
)
|
)
|
||||||
|
if cxx.get_id() == 'gcc'
|
||||||
|
warning('GCC is known to crash while building coroutines, use clang.')
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
# Translate some historical and Mesony CPU names to Lixy CPU names.
|
# Translate some historical and Mesony CPU names to Lixy CPU names.
|
||||||
|
@ -229,6 +237,7 @@ configdata += {
|
||||||
}
|
}
|
||||||
|
|
||||||
boost = dependency('boost', required : true, modules : ['container'], include_type : 'system')
|
boost = dependency('boost', required : true, modules : ['container'], include_type : 'system')
|
||||||
|
kj = dependency('kj-async', required : true, include_type : 'system')
|
||||||
|
|
||||||
# cpuid only makes sense on x86_64
|
# cpuid only makes sense on x86_64
|
||||||
cpuid_required = is_x64 ? get_option('cpuid') : false
|
cpuid_required = is_x64 ? get_option('cpuid') : false
|
||||||
|
@ -475,6 +484,7 @@ add_project_arguments(
|
||||||
# TODO(Qyriad): Yes this is how the autoconf+Make system did it.
|
# TODO(Qyriad): Yes this is how the autoconf+Make system did it.
|
||||||
# It would be nice for our headers to be idempotent instead.
|
# It would be nice for our headers to be idempotent instead.
|
||||||
'-include', 'config.h',
|
'-include', 'config.h',
|
||||||
|
'-Wno-unused-parameter',
|
||||||
'-Wno-deprecated-declarations',
|
'-Wno-deprecated-declarations',
|
||||||
'-Wimplicit-fallthrough',
|
'-Wimplicit-fallthrough',
|
||||||
'-Werror=switch',
|
'-Werror=switch',
|
||||||
|
@ -483,12 +493,6 @@ add_project_arguments(
|
||||||
'-Wdeprecated-copy',
|
'-Wdeprecated-copy',
|
||||||
'-Wignored-qualifiers',
|
'-Wignored-qualifiers',
|
||||||
'-Werror=suggest-override',
|
'-Werror=suggest-override',
|
||||||
# Enable assertions in libstdc++ by default. Harmless on libc++. Benchmarked
|
|
||||||
# at ~1% overhead in `nix search`.
|
|
||||||
#
|
|
||||||
# FIXME: remove when we get meson 1.4.0 which will default this to on for us:
|
|
||||||
# https://mesonbuild.com/Release-notes-for-1-4-0.html#ndebug-setting-now-controls-c-stdlib-assertions
|
|
||||||
'-D_GLIBCXX_ASSERTIONS=1',
|
|
||||||
language : 'cpp',
|
language : 'cpp',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -584,10 +588,10 @@ run_command(
|
||||||
)
|
)
|
||||||
|
|
||||||
if is_darwin
|
if is_darwin
|
||||||
configure_file(
|
fs.copyfile(
|
||||||
input : 'misc/launchd/org.nixos.nix-daemon.plist.in',
|
'misc/launchd/org.nixos.nix-daemon.plist.in',
|
||||||
output : 'org.nixos.nix-daemon.plist',
|
'org.nixos.nix-daemon.plist',
|
||||||
copy : true,
|
install : true,
|
||||||
install_dir : prefix / 'Library/LaunchDaemons',
|
install_dir : prefix / 'Library/LaunchDaemons',
|
||||||
)
|
)
|
||||||
endif
|
endif
|
||||||
|
|
89
meson/clang-tidy/clang-tidy-runner.py
Executable file
89
meson/clang-tidy/clang-tidy-runner.py
Executable file
|
@ -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 = []
|
out = []
|
||||||
eat_next = False
|
eat_next = False
|
||||||
for i, arg in enumerate(args):
|
for i, arg in enumerate(args):
|
||||||
if arg == '-fpch-preprocess':
|
if arg in ['-fpch-preprocess', '-fpch-instantiate-templates']:
|
||||||
# as used with gcc
|
# -fpch-preprocess as used with gcc, -fpch-instantiate-templates as used by clang
|
||||||
continue
|
continue
|
||||||
elif arg == '-include-pch' or (arg == '-include' and args[i + 1] == 'precompiled-headers.hh'):
|
elif arg == '-include-pch' or (arg == '-include' and args[i + 1] == 'precompiled-headers.hh'):
|
||||||
# -include-pch some-pch (clang), or -include some-pch (gcc)
|
# -include-pch some-pch (clang), or -include some-pch (gcc)
|
||||||
|
|
|
@ -57,34 +57,18 @@ build_all_generated_headers = custom_target(
|
||||||
)
|
)
|
||||||
|
|
||||||
if lix_clang_tidy_so_found
|
if lix_clang_tidy_so_found
|
||||||
default_concurrency = run_command(python, '-c', '''
|
|
||||||
import multiprocessing
|
|
||||||
import os
|
|
||||||
print(min(multiprocessing.cpu_count(), int(os.environ.get("NIX_BUILD_CORES", "16"))))
|
|
||||||
''', check : true).stdout()
|
|
||||||
|
|
||||||
run_clang_tidy_args = [
|
run_clang_tidy_args = [
|
||||||
'-load',
|
meson.current_source_dir() / 'clang-tidy-runner.py',
|
||||||
lix_clang_tidy_so,
|
'--run-clang-tidy-path', run_clang_tidy,
|
||||||
'-p',
|
'--compdb-path', meson.current_build_dir(),
|
||||||
# We have to workaround a run-clang-tidy bug too, so we must give the
|
'--plugin-path', lix_clang_tidy_so,
|
||||||
# directory name rather than the actual compdb file.
|
|
||||||
# https://github.com/llvm/llvm-project/issues/101440
|
|
||||||
meson.current_build_dir(),
|
|
||||||
'-quiet',
|
|
||||||
'-j', default_concurrency,
|
|
||||||
]
|
]
|
||||||
run_target(
|
run_target(
|
||||||
'clang-tidy',
|
'clang-tidy',
|
||||||
command : [
|
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,
|
python,
|
||||||
run_clang_tidy,
|
|
||||||
run_clang_tidy_args,
|
run_clang_tidy_args,
|
||||||
'-warnings-as-errors',
|
'--werror',
|
||||||
'*',
|
|
||||||
],
|
],
|
||||||
depends : [
|
depends : [
|
||||||
build_all_generated_headers,
|
build_all_generated_headers,
|
||||||
|
@ -94,9 +78,8 @@ print(min(multiprocessing.cpu_count(), int(os.environ.get("NIX_BUILD_CORES", "16
|
||||||
'clang-tidy-fix',
|
'clang-tidy-fix',
|
||||||
command : [
|
command : [
|
||||||
python,
|
python,
|
||||||
run_clang_tidy,
|
|
||||||
run_clang_tidy_args,
|
run_clang_tidy_args,
|
||||||
'-fix',
|
'--fix',
|
||||||
],
|
],
|
||||||
depends : [
|
depends : [
|
||||||
build_all_generated_headers,
|
build_all_generated_headers,
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
configure_file(
|
fs.copyfile(
|
||||||
input : 'completion.sh',
|
'completion.sh',
|
||||||
output : 'nix',
|
'nix',
|
||||||
install : true,
|
install : true,
|
||||||
install_dir : datadir / 'bash-completion/completions',
|
install_dir : datadir / 'bash-completion/completions',
|
||||||
install_mode : 'rw-r--r--',
|
install_mode : 'rw-r--r--',
|
||||||
copy : true,
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,7 +14,7 @@ function _nix_complete
|
||||||
# But the variable also misses the current token so it cancels out.
|
# But the variable also misses the current token so it cancels out.
|
||||||
set -l nix_arg_to_complete (count $nix_args)
|
set -l nix_arg_to_complete (count $nix_args)
|
||||||
|
|
||||||
env NIX_GET_COMPLETIONS=$nix_arg_to_complete $nix_args $current_token
|
env NIX_GET_COMPLETIONS=$nix_arg_to_complete $nix_args $current_token 2>/dev/null
|
||||||
end
|
end
|
||||||
|
|
||||||
function _nix_accepts_files
|
function _nix_accepts_files
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
configure_file(
|
fs.copyfile(
|
||||||
input : 'completion.fish',
|
'completion.fish',
|
||||||
output : 'nix.fish',
|
'nix.fish',
|
||||||
install : true,
|
install : true,
|
||||||
install_dir : datadir / 'fish/vendor_completions.d',
|
install_dir : datadir / 'fish/vendor_completions.d',
|
||||||
install_mode : 'rw-r--r--',
|
install_mode : 'rw-r--r--',
|
||||||
copy : true,
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,8 +5,4 @@ subdir('zsh')
|
||||||
subdir('systemd')
|
subdir('systemd')
|
||||||
subdir('flake-registry')
|
subdir('flake-registry')
|
||||||
|
|
||||||
runinpty = configure_file(
|
runinpty = fs.copyfile('runinpty.py')
|
||||||
copy : true,
|
|
||||||
input : meson.current_source_dir() / 'runinpty.py',
|
|
||||||
output : 'runinpty.py',
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
foreach script : [ [ 'completion.zsh', '_nix' ], [ 'run-help-nix' ] ]
|
foreach script : [ [ 'completion.zsh', '_nix' ], [ 'run-help-nix' ] ]
|
||||||
configure_file(
|
fs.copyfile(
|
||||||
input : script[0],
|
script[0],
|
||||||
output : script.get(1, script[0]),
|
script.get(1, script[0]),
|
||||||
install : true,
|
install : true,
|
||||||
install_dir : datadir / 'zsh/site-functions',
|
install_dir : datadir / 'zsh/site-functions',
|
||||||
install_mode : 'rw-r--r--',
|
install_mode : 'rw-r--r--',
|
||||||
copy : true,
|
|
||||||
)
|
)
|
||||||
endforeach
|
endforeach
|
||||||
|
|
106
nix-support/editline.patch
Normal file
106
nix-support/editline.patch
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
From d0f2a5bc2300b96b2434c7838184c1dfd6a639f5 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Rebecca Turner <rbt@sent.as>
|
||||||
|
Date: Sun, 8 Sep 2024 15:42:42 -0700
|
||||||
|
Subject: [PATCH 1/2] Recognize Meta+Left and Meta+Right
|
||||||
|
|
||||||
|
Recognize `Alt-Left` and `Alt-Right` for navigating by words in more
|
||||||
|
terminals/shells/platforms.
|
||||||
|
|
||||||
|
I'm not sure exactly where to find canonical documentation for these
|
||||||
|
codes, but this seems to match what my terminal produces (macOS + iTerm2
|
||||||
|
+ Fish + Tmux).
|
||||||
|
|
||||||
|
It might also be nice to have some more support for editing the bindings
|
||||||
|
for these characters; sequences of more than one character are not
|
||||||
|
supported by `el_bind_key` and similar.
|
||||||
|
|
||||||
|
Originally from: https://github.com/troglobit/editline/pull/70
|
||||||
|
This patch is applied upstream: https://gerrit.lix.systems/c/lix/+/1883
|
||||||
|
|
||||||
|
---
|
||||||
|
src/editline.c | 29 +++++++++++++++++++++++++++--
|
||||||
|
1 file changed, 27 insertions(+), 2 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/editline.c b/src/editline.c
|
||||||
|
index 5ec9afb..d1cfbbc 100644
|
||||||
|
--- a/src/editline.c
|
||||||
|
+++ b/src/editline.c
|
||||||
|
@@ -1034,6 +1034,30 @@ static el_status_t meta(void)
|
||||||
|
return CSeof;
|
||||||
|
|
||||||
|
#ifdef CONFIG_ANSI_ARROWS
|
||||||
|
+ /* See: https://en.wikipedia.org/wiki/ANSI_escape_code */
|
||||||
|
+ /* Recognize ANSI escapes for `Meta+Left` and `Meta+Right`. */
|
||||||
|
+ if (c == '\e') {
|
||||||
|
+ switch (tty_get()) {
|
||||||
|
+ case '[':
|
||||||
|
+ {
|
||||||
|
+ switch (tty_get()) {
|
||||||
|
+ /* \e\e[C = Meta+Left */
|
||||||
|
+ case 'C': return fd_word();
|
||||||
|
+ /* \e\e[D = Meta+Right */
|
||||||
|
+ case 'D': return bk_word();
|
||||||
|
+ default:
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return el_ring_bell();
|
||||||
|
+ }
|
||||||
|
+ default:
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return el_ring_bell();
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
/* Also include VT-100 arrows. */
|
||||||
|
if (c == '[' || c == 'O') {
|
||||||
|
switch (tty_get()) {
|
||||||
|
@@ -1043,6 +1067,7 @@ static el_status_t meta(void)
|
||||||
|
char seq[4] = { 0 };
|
||||||
|
seq[0] = tty_get();
|
||||||
|
|
||||||
|
+ /* \e[1~ */
|
||||||
|
if (seq[0] == '~')
|
||||||
|
return beg_line(); /* Home */
|
||||||
|
|
||||||
|
@@ -1050,9 +1075,9 @@ static el_status_t meta(void)
|
||||||
|
seq[c] = tty_get();
|
||||||
|
|
||||||
|
if (!strncmp(seq, ";5C", 3))
|
||||||
|
- return fd_word(); /* Ctrl+Right */
|
||||||
|
+ return fd_word(); /* \e[1;5C = Ctrl+Right */
|
||||||
|
if (!strncmp(seq, ";5D", 3))
|
||||||
|
- return bk_word(); /* Ctrl+Left */
|
||||||
|
+ return bk_word(); /* \e[1;5D = Ctrl+Left */
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
From 4c4455353a0a88bee09d5f27c28f81f747682fed Mon Sep 17 00:00:00 2001
|
||||||
|
From: Rebecca Turner <rbt@sent.as>
|
||||||
|
Date: Mon, 9 Sep 2024 09:44:44 -0700
|
||||||
|
Subject: [PATCH 2/2] Add support for \e[1;3C and \e[1;3D
|
||||||
|
|
||||||
|
---
|
||||||
|
src/editline.c | 6 ++++--
|
||||||
|
1 file changed, 4 insertions(+), 2 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/editline.c b/src/editline.c
|
||||||
|
index d1cfbbc..350b5cb 100644
|
||||||
|
--- a/src/editline.c
|
||||||
|
+++ b/src/editline.c
|
||||||
|
@@ -1074,9 +1074,11 @@ static el_status_t meta(void)
|
||||||
|
for (c = 1; c < 3; c++)
|
||||||
|
seq[c] = tty_get();
|
||||||
|
|
||||||
|
- if (!strncmp(seq, ";5C", 3))
|
||||||
|
+ if (!strncmp(seq, ";5C", 3)
|
||||||
|
+ || !strncmp(seq, ";3C", 3))
|
||||||
|
return fd_word(); /* \e[1;5C = Ctrl+Right */
|
||||||
|
- if (!strncmp(seq, ";5D", 3))
|
||||||
|
+ if (!strncmp(seq, ";5D", 3)
|
||||||
|
+ || !strncmp(seq, ";3D", 3))
|
||||||
|
return bk_word(); /* \e[1;5D = Ctrl+Left */
|
||||||
|
|
||||||
|
break;
|
31
package.nix
31
package.nix
|
@ -15,6 +15,8 @@
|
||||||
brotli,
|
brotli,
|
||||||
bzip2,
|
bzip2,
|
||||||
callPackage,
|
callPackage,
|
||||||
|
capnproto-lix ? __forDefaults.capnproto-lix,
|
||||||
|
capnproto,
|
||||||
cmake,
|
cmake,
|
||||||
curl,
|
curl,
|
||||||
doxygen,
|
doxygen,
|
||||||
|
@ -36,6 +38,7 @@
|
||||||
mercurial,
|
mercurial,
|
||||||
meson,
|
meson,
|
||||||
ninja,
|
ninja,
|
||||||
|
ncurses,
|
||||||
openssl,
|
openssl,
|
||||||
pegtl,
|
pegtl,
|
||||||
pkg-config,
|
pkg-config,
|
||||||
|
@ -79,12 +82,36 @@
|
||||||
boehmgc-nix = boehmgc.override { enableLargeConfig = true; };
|
boehmgc-nix = boehmgc.override { enableLargeConfig = true; };
|
||||||
|
|
||||||
editline-lix = editline.overrideAttrs (prev: {
|
editline-lix = editline.overrideAttrs (prev: {
|
||||||
configureFlags = prev.configureFlags or [ ] ++ [ (lib.enableFeature true "sigstop") ];
|
patches = (prev.patches or [ ]) ++ [
|
||||||
|
# Recognize `Alt-Left` and `Alt-Right` for navigating by words in more
|
||||||
|
# terminals/shells/platforms.
|
||||||
|
#
|
||||||
|
# See: https://github.com/troglobit/editline/pull/70
|
||||||
|
./nix-support/editline.patch
|
||||||
|
];
|
||||||
|
|
||||||
|
configureFlags = (prev.configureFlags or [ ]) ++ [
|
||||||
|
# Enable SIGSTOP (Ctrl-Z) behavior.
|
||||||
|
(lib.enableFeature true "sigstop")
|
||||||
|
# Enable ANSI arrow keys.
|
||||||
|
(lib.enableFeature true "arrow-keys")
|
||||||
|
# Use termcap library to query terminal size.
|
||||||
|
(lib.enableFeature (ncurses != null) "termcap")
|
||||||
|
];
|
||||||
|
|
||||||
|
buildInputs = (prev.buildInputs or [ ]) ++ [ ncurses ];
|
||||||
});
|
});
|
||||||
|
|
||||||
build-release-notes = callPackage ./maintainers/build-release-notes.nix { };
|
build-release-notes = callPackage ./maintainers/build-release-notes.nix { };
|
||||||
|
|
||||||
|
# needs explicit c++20 to enable coroutine support
|
||||||
|
capnproto-lix = capnproto.overrideAttrs { CXXFLAGS = "-std=c++20"; };
|
||||||
},
|
},
|
||||||
}:
|
}:
|
||||||
|
|
||||||
|
# gcc miscompiles coroutines at least until 13.2, possibly longer
|
||||||
|
assert stdenv.cc.isClang || lintInsteadOfBuild || internalApiDocs;
|
||||||
|
|
||||||
let
|
let
|
||||||
inherit (__forDefaults) canRunInstalled;
|
inherit (__forDefaults) canRunInstalled;
|
||||||
inherit (lib) fileset;
|
inherit (lib) fileset;
|
||||||
|
@ -220,6 +247,7 @@ stdenv.mkDerivation (finalAttrs: {
|
||||||
ninja
|
ninja
|
||||||
cmake
|
cmake
|
||||||
rustc
|
rustc
|
||||||
|
capnproto-lix
|
||||||
]
|
]
|
||||||
++ [
|
++ [
|
||||||
(lib.getBin lowdown)
|
(lib.getBin lowdown)
|
||||||
|
@ -260,6 +288,7 @@ stdenv.mkDerivation (finalAttrs: {
|
||||||
libsodium
|
libsodium
|
||||||
toml11
|
toml11
|
||||||
pegtl
|
pegtl
|
||||||
|
capnproto-lix
|
||||||
]
|
]
|
||||||
++ lib.optionals hostPlatform.isLinux [
|
++ lib.optionals hostPlatform.isLinux [
|
||||||
libseccomp
|
libseccomp
|
||||||
|
|
|
@ -8,12 +8,7 @@ configure_file(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# https://github.com/mesonbuild/meson/issues/860
|
fs.copyfile('nix-profile.sh.in')
|
||||||
configure_file(
|
|
||||||
input : 'nix-profile.sh.in',
|
|
||||||
output : 'nix-profile.sh.in',
|
|
||||||
copy : true,
|
|
||||||
)
|
|
||||||
|
|
||||||
foreach rc : [ '.sh', '.fish', '-daemon.sh', '-daemon.fish' ]
|
foreach rc : [ '.sh', '.fish', '-daemon.sh', '-daemon.fish' ]
|
||||||
configure_file(
|
configure_file(
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string_view>
|
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <iomanip>
|
|
||||||
#if __APPLE__
|
#if __APPLE__
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#endif
|
#endif
|
||||||
|
@ -18,6 +14,7 @@
|
||||||
#include "build-result.hh"
|
#include "build-result.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "derivations.hh"
|
#include "derivations.hh"
|
||||||
|
#include "strings.hh"
|
||||||
#include "local-store.hh"
|
#include "local-store.hh"
|
||||||
#include "legacy.hh"
|
#include "legacy.hh"
|
||||||
#include "experimental-features.hh"
|
#include "experimental-features.hh"
|
||||||
|
|
|
@ -20,13 +20,15 @@ struct SingleBuiltPathBuilt {
|
||||||
DECLARE_CMP(SingleBuiltPathBuilt);
|
DECLARE_CMP(SingleBuiltPathBuilt);
|
||||||
};
|
};
|
||||||
|
|
||||||
using _SingleBuiltPathRaw = std::variant<
|
namespace built_path::detail {
|
||||||
|
using SingleBuiltPathRaw = std::variant<
|
||||||
DerivedPathOpaque,
|
DerivedPathOpaque,
|
||||||
SingleBuiltPathBuilt
|
SingleBuiltPathBuilt
|
||||||
>;
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
struct SingleBuiltPath : _SingleBuiltPathRaw {
|
struct SingleBuiltPath : built_path::detail::SingleBuiltPathRaw {
|
||||||
using Raw = _SingleBuiltPathRaw;
|
using Raw = built_path::detail::SingleBuiltPathRaw;
|
||||||
using Raw::Raw;
|
using Raw::Raw;
|
||||||
|
|
||||||
using Opaque = DerivedPathOpaque;
|
using Opaque = DerivedPathOpaque;
|
||||||
|
@ -65,17 +67,19 @@ struct BuiltPathBuilt {
|
||||||
DECLARE_CMP(BuiltPathBuilt);
|
DECLARE_CMP(BuiltPathBuilt);
|
||||||
};
|
};
|
||||||
|
|
||||||
using _BuiltPathRaw = std::variant<
|
namespace built_path::detail {
|
||||||
|
using BuiltPathRaw = std::variant<
|
||||||
DerivedPath::Opaque,
|
DerivedPath::Opaque,
|
||||||
BuiltPathBuilt
|
BuiltPathBuilt
|
||||||
>;
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A built path. Similar to a DerivedPath, but enriched with the corresponding
|
* A built path. Similar to a DerivedPath, but enriched with the corresponding
|
||||||
* output path(s).
|
* output path(s).
|
||||||
*/
|
*/
|
||||||
struct BuiltPath : _BuiltPathRaw {
|
struct BuiltPath : built_path::detail::BuiltPathRaw {
|
||||||
using Raw = _BuiltPathRaw;
|
using Raw = built_path::detail::BuiltPathRaw;
|
||||||
using Raw::Raw;
|
using Raw::Raw;
|
||||||
|
|
||||||
using Opaque = DerivedPathOpaque;
|
using Opaque = DerivedPathOpaque;
|
||||||
|
|
|
@ -9,8 +9,24 @@
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "command.hh"
|
#include "command.hh"
|
||||||
|
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
static std::regex const identifierRegex("^[A-Za-z_][A-Za-z0-9_'-]*$");
|
||||||
|
static void warnInvalidNixIdentifier(const std::string & name)
|
||||||
|
{
|
||||||
|
std::smatch match;
|
||||||
|
if (!std::regex_match(name, match, identifierRegex)) {
|
||||||
|
warn("This Nix invocation specifies a value for argument '%s' which isn't a valid \
|
||||||
|
Nix identifier. The project is considering to drop support for this \
|
||||||
|
or to require quotes around args that aren't valid Nix identifiers. \
|
||||||
|
If you depend on this behvior, please reach out in \
|
||||||
|
https://git.lix.systems/lix-project/lix/issues/496 so we can discuss \
|
||||||
|
your use-case.", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MixEvalArgs::MixEvalArgs()
|
MixEvalArgs::MixEvalArgs()
|
||||||
{
|
{
|
||||||
addFlag({
|
addFlag({
|
||||||
|
@ -18,7 +34,10 @@ MixEvalArgs::MixEvalArgs()
|
||||||
.description = "Pass the value *expr* as the argument *name* to Nix functions.",
|
.description = "Pass the value *expr* as the argument *name* to Nix functions.",
|
||||||
.category = category,
|
.category = category,
|
||||||
.labels = {"name", "expr"},
|
.labels = {"name", "expr"},
|
||||||
.handler = {[&](std::string name, std::string expr) { autoArgs[name] = 'E' + expr; }}
|
.handler = {[&](std::string name, std::string expr) {
|
||||||
|
warnInvalidNixIdentifier(name);
|
||||||
|
autoArgs[name] = 'E' + expr;
|
||||||
|
}}
|
||||||
});
|
});
|
||||||
|
|
||||||
addFlag({
|
addFlag({
|
||||||
|
@ -26,7 +45,10 @@ MixEvalArgs::MixEvalArgs()
|
||||||
.description = "Pass the string *string* as the argument *name* to Nix functions.",
|
.description = "Pass the string *string* as the argument *name* to Nix functions.",
|
||||||
.category = category,
|
.category = category,
|
||||||
.labels = {"name", "string"},
|
.labels = {"name", "string"},
|
||||||
.handler = {[&](std::string name, std::string s) { autoArgs[name] = 'S' + s; }},
|
.handler = {[&](std::string name, std::string s) {
|
||||||
|
warnInvalidNixIdentifier(name);
|
||||||
|
autoArgs[name] = 'S' + s;
|
||||||
|
}},
|
||||||
});
|
});
|
||||||
|
|
||||||
addFlag({
|
addFlag({
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "editor-for.hh"
|
#include "editor-for.hh"
|
||||||
#include "environment-variables.hh"
|
#include "environment-variables.hh"
|
||||||
#include "source-path.hh"
|
#include "source-path.hh"
|
||||||
|
#include "strings.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,6 @@
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
|
|
||||||
#ifdef READLINE
|
|
||||||
#include <readline/history.h>
|
|
||||||
#include <readline/readline.h>
|
|
||||||
#else
|
|
||||||
// editline < 1.15.2 don't wrap their API for C++ usage
|
// editline < 1.15.2 don't wrap their API for C++ usage
|
||||||
// (added in https://github.com/troglobit/editline/commit/91398ceb3427b730995357e9d120539fb9bb7461).
|
// (added in https://github.com/troglobit/editline/commit/91398ceb3427b730995357e9d120539fb9bb7461).
|
||||||
// This results in linker errors due to to name-mangling of editline C symbols.
|
// This results in linker errors due to to name-mangling of editline C symbols.
|
||||||
|
@ -20,7 +16,6 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <editline.h>
|
#include <editline.h>
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "finally.hh"
|
#include "finally.hh"
|
||||||
#include "repl-interacter.hh"
|
#include "repl-interacter.hh"
|
||||||
|
@ -115,17 +110,13 @@ ReadlineLikeInteracter::Guard ReadlineLikeInteracter::init(detail::ReplCompleter
|
||||||
} catch (SysError & e) {
|
} catch (SysError & e) {
|
||||||
logWarning(e.info());
|
logWarning(e.info());
|
||||||
}
|
}
|
||||||
#ifndef READLINE
|
|
||||||
el_hist_size = 1000;
|
el_hist_size = 1000;
|
||||||
#endif
|
|
||||||
read_history(historyFile.c_str());
|
read_history(historyFile.c_str());
|
||||||
auto oldRepl = curRepl;
|
auto oldRepl = curRepl;
|
||||||
curRepl = repl;
|
curRepl = repl;
|
||||||
Guard restoreRepl([oldRepl] { curRepl = oldRepl; });
|
Guard restoreRepl([oldRepl] { curRepl = oldRepl; });
|
||||||
#ifndef READLINE
|
|
||||||
rl_set_complete_func(completionCallback);
|
rl_set_complete_func(completionCallback);
|
||||||
rl_set_list_possib_func(listPossibleCallback);
|
rl_set_list_possib_func(listPossibleCallback);
|
||||||
#endif
|
|
||||||
return restoreRepl;
|
return restoreRepl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,7 @@ struct AttrDb
|
||||||
state->txn->commit();
|
state->txn->commit();
|
||||||
state->txn.reset();
|
state->txn.reset();
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
ignoreException();
|
ignoreExceptionInDestructor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ struct AttrDb
|
||||||
try {
|
try {
|
||||||
return fun();
|
return fun();
|
||||||
} catch (SQLiteError &) {
|
} catch (SQLiteError &) {
|
||||||
ignoreException();
|
ignoreExceptionExceptInterrupt();
|
||||||
failed = true;
|
failed = true;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -329,7 +329,7 @@ static std::shared_ptr<AttrDb> makeAttrDb(
|
||||||
try {
|
try {
|
||||||
return std::make_shared<AttrDb>(cfg, fingerprint, symbols);
|
return std::make_shared<AttrDb>(cfg, fingerprint, symbols);
|
||||||
} catch (SQLiteError &) {
|
} catch (SQLiteError &) {
|
||||||
ignoreException();
|
ignoreExceptionExceptInterrupt();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include "error.hh"
|
#include "error.hh"
|
||||||
|
#include "types.hh"
|
||||||
#include "pos-idx.hh"
|
#include "pos-idx.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
|
@ -31,7 +31,7 @@ Value * EvalState::allocValue()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
nrValues++;
|
nrValues++;
|
||||||
return (Value *) p;
|
return static_cast<Value *>(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,10 +54,10 @@ Env & EvalState::allocEnv(size_t size)
|
||||||
void * p = *env1AllocCache;
|
void * p = *env1AllocCache;
|
||||||
*env1AllocCache = GC_NEXT(p);
|
*env1AllocCache = GC_NEXT(p);
|
||||||
GC_NEXT(p) = nullptr;
|
GC_NEXT(p) = nullptr;
|
||||||
env = (Env *) p;
|
env = static_cast<Env *>(p);
|
||||||
} else
|
} else
|
||||||
#endif
|
#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. */
|
/* We assume that env->values has been cleared by the allocator; maybeThunk() and lookupVar fromWith expect this. */
|
||||||
|
|
||||||
|
|
|
@ -185,6 +185,54 @@ struct EvalSettings : Config
|
||||||
else
|
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
|
||||||
|
```
|
||||||
)"};
|
)"};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include "gc-small-vector.hh"
|
#include "gc-small-vector.hh"
|
||||||
#include "fetch-to-store.hh"
|
#include "fetch-to-store.hh"
|
||||||
#include "flake/flakeref.hh"
|
#include "flake/flakeref.hh"
|
||||||
|
#include "exit.hh"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "experimental-features.hh"
|
#include "experimental-features.hh"
|
||||||
#include "search-path.hh"
|
#include "search-path.hh"
|
||||||
#include "repl-exit-status.hh"
|
#include "repl-exit-status.hh"
|
||||||
|
#include "backed-string-view.hh"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
@ -791,4 +792,4 @@ static constexpr std::string_view corepkgsPrefix{"/__corepkgs__/"};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "eval-inline.hh"
|
#include "eval-inline.hh" // IWYU pragma: keep
|
||||||
|
|
|
@ -120,6 +120,7 @@ inline T * gcAllocType(size_t howMany = 1)
|
||||||
// However, people can and do request zero sized allocations, so we need
|
// However, people can and do request zero sized allocations, so we need
|
||||||
// to check that neither of our multiplicands were zero before complaining
|
// to check that neither of our multiplicands were zero before complaining
|
||||||
// about it.
|
// 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);
|
auto checkedSz = checked::Checked<size_t>(howMany) * sizeof(T);
|
||||||
size_t sz = checkedSz.valueWrapping();
|
size_t sz = checkedSz.valueWrapping();
|
||||||
if (checkedSz.overflowed()) {
|
if (checkedSz.overflowed()) {
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
ExprBlackHole eBlackHole;
|
ExprBlackHole eBlackHole;
|
||||||
|
Expr *eBlackHoleAddr = &eBlackHole;
|
||||||
|
|
||||||
// FIXME: remove, because *symbols* are abstract and do not have a single
|
// FIXME: remove, because *symbols* are abstract and do not have a single
|
||||||
// textual representation; see printIdentifier()
|
// textual representation; see printIdentifier()
|
||||||
|
@ -20,6 +21,14 @@ std::ostream & operator <<(std::ostream & str, const SymbolStr & symbol)
|
||||||
return printIdentifier(str, s);
|
return printIdentifier(str, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AttrName::AttrName(Symbol s) : symbol(s)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
AttrName::AttrName(std::unique_ptr<Expr> e) : expr(std::move(e))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void Expr::show(const SymbolTable & symbols, std::ostream & str) const
|
void Expr::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
abort();
|
abort();
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "eval-error.hh"
|
#include "eval-error.hh"
|
||||||
#include "pos-idx.hh"
|
#include "pos-idx.hh"
|
||||||
#include "pos-table.hh"
|
#include "pos-table.hh"
|
||||||
|
#include "strings.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -29,8 +30,8 @@ struct AttrName
|
||||||
{
|
{
|
||||||
Symbol symbol;
|
Symbol symbol;
|
||||||
std::unique_ptr<Expr> expr;
|
std::unique_ptr<Expr> expr;
|
||||||
AttrName(Symbol s) : symbol(s) {};
|
AttrName(Symbol s);
|
||||||
AttrName(std::unique_ptr<Expr> e) : expr(std::move(e)) {};
|
AttrName(std::unique_ptr<Expr> e);
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::vector<AttrName> AttrPath;
|
typedef std::vector<AttrName> AttrPath;
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace nix::parser {
|
||||||
struct StringToken
|
struct StringToken
|
||||||
{
|
{
|
||||||
std::string_view s;
|
std::string_view s;
|
||||||
bool hasIndentation;
|
bool hasIndentation = false;
|
||||||
operator std::string_view() const { return s; }
|
operator std::string_view() const { return s; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -394,7 +394,8 @@ static RegisterPrimOp primop_fetchGit({
|
||||||
[Git reference]: https://git-scm.com/book/en/v2/Git-Internals-Git-References
|
[Git reference]: https://git-scm.com/book/en/v2/Git-Internals-Git-References
|
||||||
|
|
||||||
By default, the `ref` value is prefixed with `refs/heads/`.
|
By default, the `ref` value is prefixed with `refs/heads/`.
|
||||||
As of 2.3.0, Nix will not prefix `refs/heads/` if `ref` starts with `refs/`.
|
As of 2.3.0, Nix will not prefix `refs/heads/` if `ref` starts with `refs/` or
|
||||||
|
if `ref` looks like a commit hash for backwards compatibility with CppNix 2.3.
|
||||||
|
|
||||||
- `submodules` (default: `false`)
|
- `submodules` (default: `false`)
|
||||||
|
|
||||||
|
|
|
@ -136,7 +136,9 @@ class ExternalValueBase
|
||||||
|
|
||||||
std::ostream & operator << (std::ostream & str, const ExternalValueBase & v);
|
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
|
struct NewValueAs
|
||||||
{
|
{
|
||||||
|
@ -196,6 +198,7 @@ private:
|
||||||
public:
|
public:
|
||||||
|
|
||||||
// Discount `using NewValueAs::*;`
|
// Discount `using NewValueAs::*;`
|
||||||
|
// NOLINTNEXTLINE(bugprone-macro-parentheses)
|
||||||
#define USING_VALUETYPE(name) using name = NewValueAs::name
|
#define USING_VALUETYPE(name) using name = NewValueAs::name
|
||||||
USING_VALUETYPE(integer_t);
|
USING_VALUETYPE(integer_t);
|
||||||
USING_VALUETYPE(floating_t);
|
USING_VALUETYPE(floating_t);
|
||||||
|
@ -473,7 +476,7 @@ public:
|
||||||
/// Constructs an evil thunk, whose evaluation represents infinite recursion.
|
/// Constructs an evil thunk, whose evaluation represents infinite recursion.
|
||||||
explicit Value(blackhole_t)
|
explicit Value(blackhole_t)
|
||||||
: internalType(tThunk)
|
: internalType(tThunk)
|
||||||
, thunk({ .env = nullptr, .expr = reinterpret_cast<Expr *>(&eBlackHole) })
|
, thunk({ .env = nullptr, .expr = eBlackHoleAddr })
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
Value(Value const & rhs) = default;
|
Value(Value const & rhs) = default;
|
||||||
|
@ -513,7 +516,10 @@ public:
|
||||||
// type() == nThunk
|
// type() == nThunk
|
||||||
inline bool isThunk() const { return internalType == tThunk; };
|
inline bool isThunk() const { return internalType == tThunk; };
|
||||||
inline bool isApp() const { return internalType == tApp; };
|
inline bool isApp() const { return internalType == tApp; };
|
||||||
inline bool isBlackhole() const;
|
inline bool isBlackhole() const
|
||||||
|
{
|
||||||
|
return internalType == tThunk && thunk.expr == eBlackHoleAddr;
|
||||||
|
}
|
||||||
|
|
||||||
// type() == nFunction
|
// type() == nFunction
|
||||||
inline bool isLambda() const { return internalType == tLambda; };
|
inline bool isLambda() const { return internalType == tLambda; };
|
||||||
|
@ -669,11 +675,6 @@ public:
|
||||||
|
|
||||||
void mkStringMove(const char * s, const NixStringContext & context);
|
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);
|
void mkPath(const SourcePath & path);
|
||||||
|
|
||||||
inline void mkPath(const char * path)
|
inline void mkPath(const char * path)
|
||||||
|
@ -732,7 +733,11 @@ public:
|
||||||
lambda.fun = f;
|
lambda.fun = f;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void mkBlackhole();
|
inline void mkBlackhole()
|
||||||
|
{
|
||||||
|
internalType = tThunk;
|
||||||
|
thunk.expr = eBlackHoleAddr;
|
||||||
|
}
|
||||||
|
|
||||||
void mkPrimOp(PrimOp * p);
|
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 ValueVector = GcVector<Value *>;
|
||||||
using ValueMap = GcMap<Symbol, Value *>;
|
using ValueMap = GcMap<Symbol, Value *>;
|
||||||
using ValueVectorMap = std::map<Symbol, ValueVector>;
|
using ValueVectorMap = std::map<Symbol, ValueVector>;
|
||||||
|
|
|
@ -7,7 +7,33 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
template<> AcceptFlakeConfig BaseSetting<AcceptFlakeConfig>::parse(const std::string & str) const
|
void to_json(nlohmann::json & j, const AcceptFlakeConfig & e)
|
||||||
|
{
|
||||||
|
if (e == AcceptFlakeConfig::False) {
|
||||||
|
j = false;
|
||||||
|
} else if (e == AcceptFlakeConfig::Ask) {
|
||||||
|
j = "ask";
|
||||||
|
} else if (e == AcceptFlakeConfig::True) {
|
||||||
|
j = true;
|
||||||
|
} else {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void from_json(const nlohmann::json & j, AcceptFlakeConfig & e)
|
||||||
|
{
|
||||||
|
if (j == false) {
|
||||||
|
e = AcceptFlakeConfig::False;
|
||||||
|
} else if (j == "ask") {
|
||||||
|
e = AcceptFlakeConfig::Ask;
|
||||||
|
} else if (j == true) {
|
||||||
|
e = AcceptFlakeConfig::True;
|
||||||
|
} else {
|
||||||
|
throw Error("Invalid accept-flake-config value '%s'", std::string(j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> AcceptFlakeConfig BaseSetting<AcceptFlakeConfig>::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||||
{
|
{
|
||||||
if (str == "true") return AcceptFlakeConfig::True;
|
if (str == "true") return AcceptFlakeConfig::True;
|
||||||
else if (str == "ask") return AcceptFlakeConfig::Ask;
|
else if (str == "ask") return AcceptFlakeConfig::Ask;
|
||||||
|
|
|
@ -13,6 +13,9 @@ namespace nix {
|
||||||
|
|
||||||
enum class AcceptFlakeConfig { False, Ask, True };
|
enum class AcceptFlakeConfig { False, Ask, True };
|
||||||
|
|
||||||
|
void to_json(nlohmann::json & j, const AcceptFlakeConfig & e);
|
||||||
|
void from_json(const nlohmann::json & j, AcceptFlakeConfig & e);
|
||||||
|
|
||||||
struct FetchSettings : public Config
|
struct FetchSettings : public Config
|
||||||
{
|
{
|
||||||
FetchSettings();
|
FetchSettings();
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
#include "path.hh"
|
#include "path.hh"
|
||||||
#include "attrs.hh"
|
#include "attrs.hh"
|
||||||
#include "url.hh"
|
#include "url.hh"
|
||||||
|
#include "ref.hh"
|
||||||
|
#include "strings.hh"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#include "error.hh"
|
||||||
#include "fetchers.hh"
|
#include "fetchers.hh"
|
||||||
#include "cache.hh"
|
#include "cache.hh"
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
|
@ -257,6 +258,28 @@ std::pair<StorePath, Input> fetchFromWorkdir(ref<Store> store, Input & input, co
|
||||||
}
|
}
|
||||||
} // end namespace
|
} // end namespace
|
||||||
|
|
||||||
|
static std::optional<Path> resolveRefToCachePath(
|
||||||
|
Input & input,
|
||||||
|
const Path & cacheDir,
|
||||||
|
std::vector<Path> & gitRefFileCandidates,
|
||||||
|
std::function<bool(const Path&)> condition)
|
||||||
|
{
|
||||||
|
if (input.getRef()->starts_with("refs/")) {
|
||||||
|
Path fullpath = cacheDir + "/" + *input.getRef();
|
||||||
|
if (condition(fullpath)) {
|
||||||
|
return fullpath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto & candidate : gitRefFileCandidates) {
|
||||||
|
if (condition(candidate)) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
struct GitInputScheme : InputScheme
|
struct GitInputScheme : InputScheme
|
||||||
{
|
{
|
||||||
std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
|
std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
|
||||||
|
@ -539,10 +562,13 @@ struct GitInputScheme : InputScheme
|
||||||
runProgram("git", true, { "-c", "init.defaultBranch=" + gitInitialBranch, "init", "--bare", repoDir });
|
runProgram("git", true, { "-c", "init.defaultBranch=" + gitInitialBranch, "init", "--bare", repoDir });
|
||||||
}
|
}
|
||||||
|
|
||||||
Path localRefFile =
|
std::vector<Path> gitRefFileCandidates;
|
||||||
input.getRef()->compare(0, 5, "refs/") == 0
|
for (auto & infix : {"", "tags/", "heads/"}) {
|
||||||
? cacheDir + "/" + *input.getRef()
|
Path p = cacheDir + "/refs/" + infix + *input.getRef();
|
||||||
: cacheDir + "/refs/heads/" + *input.getRef();
|
gitRefFileCandidates.push_back(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
Path localRefFile;
|
||||||
|
|
||||||
bool doFetch;
|
bool doFetch;
|
||||||
time_t now = time(0);
|
time_t now = time(0);
|
||||||
|
@ -564,29 +590,70 @@ struct GitInputScheme : InputScheme
|
||||||
if (allRefs) {
|
if (allRefs) {
|
||||||
doFetch = true;
|
doFetch = true;
|
||||||
} else {
|
} else {
|
||||||
|
std::function<bool(const Path&)> condition;
|
||||||
|
condition = [&now](const Path & path) {
|
||||||
/* If the local ref is older than ‘tarball-ttl’ seconds, do a
|
/* If the local ref is older than ‘tarball-ttl’ seconds, do a
|
||||||
git fetch to update the local ref to the remote ref. */
|
git fetch to update the local ref to the remote ref. */
|
||||||
struct stat st;
|
struct stat st;
|
||||||
doFetch = stat(localRefFile.c_str(), &st) != 0 ||
|
return stat(path.c_str(), &st) == 0 &&
|
||||||
!isCacheFileWithinTtl(now, st);
|
isCacheFileWithinTtl(now, st);
|
||||||
|
};
|
||||||
|
if (auto result = resolveRefToCachePath(
|
||||||
|
input,
|
||||||
|
cacheDir,
|
||||||
|
gitRefFileCandidates,
|
||||||
|
condition
|
||||||
|
)) {
|
||||||
|
localRefFile = *result;
|
||||||
|
doFetch = false;
|
||||||
|
} else {
|
||||||
|
doFetch = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When having to fetch, we don't know `localRefFile` yet.
|
||||||
|
// Because git needs to figure out what we're fetching
|
||||||
|
// (i.e. is it a rev? a branch? a tag?)
|
||||||
if (doFetch) {
|
if (doFetch) {
|
||||||
Activity act(*logger, lvlTalkative, actUnknown, fmt("fetching Git repository '%s'", actualUrl));
|
Activity act(*logger, lvlTalkative, actUnknown, fmt("fetching Git repository '%s'", actualUrl));
|
||||||
|
|
||||||
|
auto ref = input.getRef();
|
||||||
|
std::string fetchRef;
|
||||||
|
if (allRefs) {
|
||||||
|
fetchRef = "refs/*";
|
||||||
|
} else if (
|
||||||
|
ref->starts_with("refs/")
|
||||||
|
|| *ref == "HEAD"
|
||||||
|
|| std::regex_match(*ref, revRegex))
|
||||||
|
{
|
||||||
|
fetchRef = *ref;
|
||||||
|
} else {
|
||||||
|
fetchRef = "refs/*/" + *ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Finally finally([&]() {
|
||||||
|
if (auto p = resolveRefToCachePath(
|
||||||
|
input,
|
||||||
|
cacheDir,
|
||||||
|
gitRefFileCandidates,
|
||||||
|
pathExists
|
||||||
|
)) {
|
||||||
|
localRefFile = *p;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// FIXME: git stderr messes up our progress indicator, so
|
// FIXME: git stderr messes up our progress indicator, so
|
||||||
// we're using --quiet for now. Should process its stderr.
|
// we're using --quiet for now. Should process its stderr.
|
||||||
try {
|
runProgram("git", true, {
|
||||||
auto ref = input.getRef();
|
"-C", repoDir,
|
||||||
auto fetchRef = allRefs
|
"--git-dir", gitDir,
|
||||||
? "refs/*"
|
"fetch",
|
||||||
: ref->compare(0, 5, "refs/") == 0
|
"--quiet",
|
||||||
? *ref
|
"--force",
|
||||||
: ref == "HEAD"
|
"--", actualUrl, fmt("%s:%s", fetchRef, fetchRef)
|
||||||
? *ref
|
}, true);
|
||||||
: "refs/heads/" + *ref;
|
|
||||||
runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "fetch", "--quiet", "--force", "--", actualUrl, fmt("%s:%s", fetchRef, fetchRef) }, true);
|
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
if (!pathExists(localRefFile)) throw;
|
if (!pathExists(localRefFile)) throw;
|
||||||
warn("could not update local clone of Git repository '%s'; continuing with the most recent version", actualUrl);
|
warn("could not update local clone of Git repository '%s'; continuing with the most recent version", actualUrl);
|
||||||
|
|
41
src/libmain/crash-handler.cc
Normal file
41
src/libmain/crash-handler.cc
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
#include "crash-handler.hh"
|
||||||
|
#include "fmt.hh"
|
||||||
|
|
||||||
|
#include <boost/core/demangle.hpp>
|
||||||
|
#include <exception>
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void onTerminate()
|
||||||
|
{
|
||||||
|
std::cerr << "Lix crashed. This is a bug. We would appreciate if you report it along with what caused it at https://git.lix.systems/lix-project/lix/issues with the following information included:\n\n";
|
||||||
|
try {
|
||||||
|
std::exception_ptr eptr = std::current_exception();
|
||||||
|
if (eptr) {
|
||||||
|
std::rethrow_exception(eptr);
|
||||||
|
} else {
|
||||||
|
std::cerr << "std::terminate() called without exception\n";
|
||||||
|
}
|
||||||
|
} catch (const std::exception & ex) {
|
||||||
|
std::cerr << "Exception: " << boost::core::demangle(typeid(ex).name()) << ": " << ex.what() << "\n";
|
||||||
|
} catch (...) {
|
||||||
|
std::cerr << "Unknown exception! Spooky.\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "Stack trace:\n";
|
||||||
|
nix::printStackTrace();
|
||||||
|
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void registerCrashHandler()
|
||||||
|
{
|
||||||
|
// DO NOT use this for signals. Boost stacktrace is very much not
|
||||||
|
// async-signal-safe, and in a world with ASLR, addr2line is pointless.
|
||||||
|
//
|
||||||
|
// If you want signals, set up a minidump system and do it out-of-process.
|
||||||
|
std::set_terminate(onTerminate);
|
||||||
|
}
|
||||||
|
}
|
21
src/libmain/crash-handler.hh
Normal file
21
src/libmain/crash-handler.hh
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
/// @file Crash handler for Lix that prints back traces (hopefully in instances where it is not just going to crash the process itself).
|
||||||
|
/*
|
||||||
|
* Author's note: This will probably be partially/fully supplanted by a
|
||||||
|
* minidump writer like the following once we get our act together on crashes a
|
||||||
|
* little bit more:
|
||||||
|
* https://github.com/rust-minidump/minidump-writer
|
||||||
|
* https://github.com/EmbarkStudios/crash-handling
|
||||||
|
* (out of process implementation *should* be able to be done on-demand)
|
||||||
|
*
|
||||||
|
* Such an out-of-process implementation could then both make minidumps and
|
||||||
|
* print stack traces for arbitrarily messed-up process states such that we can
|
||||||
|
* safely give out backtraces for SIGSEGV and other deadly signals.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
/** Registers the Lix crash handler for std::terminate (currently; will support more crashes later). See also detectStackOverflow(). */
|
||||||
|
void registerCrashHandler();
|
||||||
|
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ namespace nix {
|
||||||
LogFormat defaultLogFormat = LogFormat::raw;
|
LogFormat defaultLogFormat = LogFormat::raw;
|
||||||
|
|
||||||
LogFormat parseLogFormat(const std::string & logFormatStr) {
|
LogFormat parseLogFormat(const std::string & logFormatStr) {
|
||||||
if (logFormatStr == "raw" || getEnv("NIX_GET_COMPLETIONS"))
|
if (logFormatStr == "raw")
|
||||||
return LogFormat::raw;
|
return LogFormat::raw;
|
||||||
else if (logFormatStr == "raw-with-logs")
|
else if (logFormatStr == "raw-with-logs")
|
||||||
return LogFormat::rawWithLogs;
|
return LogFormat::rawWithLogs;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
libmain_sources = files(
|
libmain_sources = files(
|
||||||
'common-args.cc',
|
'common-args.cc',
|
||||||
|
'crash-handler.cc',
|
||||||
'loggers.cc',
|
'loggers.cc',
|
||||||
'progress-bar.cc',
|
'progress-bar.cc',
|
||||||
'shared.cc',
|
'shared.cc',
|
||||||
|
@ -8,6 +9,7 @@ libmain_sources = files(
|
||||||
|
|
||||||
libmain_headers = files(
|
libmain_headers = files(
|
||||||
'common-args.hh',
|
'common-args.hh',
|
||||||
|
'crash-handler.hh',
|
||||||
'loggers.hh',
|
'loggers.hh',
|
||||||
'progress-bar.hh',
|
'progress-bar.hh',
|
||||||
'shared.hh',
|
'shared.hh',
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
#include "progress-bar.hh"
|
#include "progress-bar.hh"
|
||||||
|
#include "file-system.hh"
|
||||||
#include "sync.hh"
|
#include "sync.hh"
|
||||||
#include "store-api.hh"
|
|
||||||
#include "names.hh"
|
#include "names.hh"
|
||||||
#include "terminal.hh"
|
#include "terminal.hh"
|
||||||
|
#include "strings.hh"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
@ -91,7 +92,7 @@ void ProgressBar::resume()
|
||||||
nextWakeup = draw(*state, {});
|
nextWakeup = draw(*state, {});
|
||||||
state.wait_for(quitCV, std::chrono::milliseconds(50));
|
state.wait_for(quitCV, std::chrono::milliseconds(50));
|
||||||
}
|
}
|
||||||
writeLogsToStderr("\r\e[K");
|
eraseProgressDisplay(*state);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -557,7 +558,8 @@ std::optional<char> ProgressBar::ask(std::string_view msg)
|
||||||
{
|
{
|
||||||
auto state(state_.lock());
|
auto state(state_.lock());
|
||||||
if (state->paused > 0 || !isatty(STDIN_FILENO)) return {};
|
if (state->paused > 0 || !isatty(STDIN_FILENO)) return {};
|
||||||
std::cerr << fmt("\r\e[K%s ", msg);
|
eraseProgressDisplay(*state);
|
||||||
|
std::cerr << msg;
|
||||||
auto s = trim(readLine(STDIN_FILENO));
|
auto s = trim(readLine(STDIN_FILENO));
|
||||||
if (s.size() != 1) return {};
|
if (s.size() != 1) return {};
|
||||||
draw(*state, {});
|
draw(*state, {});
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#include "crash-handler.hh"
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
#include "shared.hh"
|
#include "shared.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
@ -6,6 +7,8 @@
|
||||||
#include "loggers.hh"
|
#include "loggers.hh"
|
||||||
#include "current-process.hh"
|
#include "current-process.hh"
|
||||||
#include "terminal.hh"
|
#include "terminal.hh"
|
||||||
|
#include "strings.hh"
|
||||||
|
#include "exit.hh"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
@ -116,6 +119,8 @@ static void sigHandler(int signo) { }
|
||||||
|
|
||||||
void initNix()
|
void initNix()
|
||||||
{
|
{
|
||||||
|
registerCrashHandler();
|
||||||
|
|
||||||
/* Turn on buffering for cerr. */
|
/* Turn on buffering for cerr. */
|
||||||
static char buf[1024];
|
static char buf[1024];
|
||||||
std::cerr.rdbuf()->pubsetbuf(buf, sizeof(buf));
|
std::cerr.rdbuf()->pubsetbuf(buf, sizeof(buf));
|
||||||
|
@ -333,12 +338,15 @@ int handleExceptions(const std::string & programName, std::function<void()> fun)
|
||||||
} catch (BaseError & e) {
|
} catch (BaseError & e) {
|
||||||
logError(e.info());
|
logError(e.info());
|
||||||
return e.info().status;
|
return e.info().status;
|
||||||
} catch (std::bad_alloc & e) {
|
} catch (const std::bad_alloc & e) {
|
||||||
printError(error + "out of memory");
|
printError(error + "out of memory");
|
||||||
return 1;
|
return 1;
|
||||||
} catch (std::exception & e) {
|
} catch (const std::exception & e) {
|
||||||
printError(error + e.what());
|
// Random exceptions bubbling into main are cause for bug reports, crash
|
||||||
return 1;
|
std::terminate();
|
||||||
|
} catch (...) {
|
||||||
|
// Explicitly do not tolerate non-std exceptions escaping.
|
||||||
|
std::terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -387,7 +395,7 @@ RunPager::~RunPager()
|
||||||
pid.wait();
|
pid.wait();
|
||||||
}
|
}
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
ignoreException();
|
ignoreExceptionInDestructor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,10 @@
|
||||||
#include "path.hh"
|
#include "path.hh"
|
||||||
#include "derived-path.hh"
|
#include "derived-path.hh"
|
||||||
#include "processes.hh"
|
#include "processes.hh"
|
||||||
#include "exit.hh"
|
#include "strings.hh"
|
||||||
|
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
|
||||||
#include <locale>
|
|
||||||
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -113,7 +111,7 @@ struct PrintFreed
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Install a SIGSEGV handler to detect stack overflows.
|
* Install a SIGSEGV handler to detect stack overflows. See also registerCrashHandler().
|
||||||
*/
|
*/
|
||||||
void detectStackOverflow();
|
void detectStackOverflow();
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "nar-accessor.hh"
|
#include "nar-accessor.hh"
|
||||||
#include "thread-pool.hh"
|
#include "thread-pool.hh"
|
||||||
#include "signals.hh"
|
#include "signals.hh"
|
||||||
|
#include "strings.hh"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
|
|
@ -47,7 +47,7 @@ struct BuildResult
|
||||||
* @todo This should be an entire ErrorInfo object, not just a
|
* @todo This should be an entire ErrorInfo object, not just a
|
||||||
* string, for richer information.
|
* string, for richer information.
|
||||||
*/
|
*/
|
||||||
std::string errorMsg;
|
std::string errorMsg = {};
|
||||||
|
|
||||||
std::string toString() const {
|
std::string toString() const {
|
||||||
auto strStatus = [&]() {
|
auto strStatus = [&]() {
|
||||||
|
@ -90,7 +90,7 @@ struct BuildResult
|
||||||
* For derivations, a mapping from the names of the wanted outputs
|
* For derivations, a mapping from the names of the wanted outputs
|
||||||
* to actual paths.
|
* to actual paths.
|
||||||
*/
|
*/
|
||||||
SingleDrvOutputs builtOutputs;
|
SingleDrvOutputs builtOutputs = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The start/stop times of the build (or one of the rounds, if it
|
* The start/stop times of the build (or one of the rounds, if it
|
||||||
|
|
|
@ -9,8 +9,15 @@
|
||||||
#include "logging-json.hh"
|
#include "logging-json.hh"
|
||||||
#include "substitution-goal.hh"
|
#include "substitution-goal.hh"
|
||||||
#include "drv-output-substitution-goal.hh"
|
#include "drv-output-substitution-goal.hh"
|
||||||
|
#include "strings.hh"
|
||||||
|
|
||||||
|
#include <boost/outcome/try.hpp>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <kj/array.h>
|
||||||
|
#include <kj/async-unix.h>
|
||||||
|
#include <kj/async.h>
|
||||||
|
#include <kj/debug.h>
|
||||||
|
#include <kj/vector.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
|
@ -57,26 +64,25 @@
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
DerivationGoal::DerivationGoal(const StorePath & drvPath,
|
DerivationGoal::DerivationGoal(const StorePath & drvPath,
|
||||||
const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
|
const OutputsSpec & wantedOutputs, Worker & worker, bool isDependency, BuildMode buildMode)
|
||||||
: Goal(worker)
|
: Goal(worker, isDependency)
|
||||||
, useDerivation(true)
|
, useDerivation(true)
|
||||||
, drvPath(drvPath)
|
, drvPath(drvPath)
|
||||||
, wantedOutputs(wantedOutputs)
|
, wantedOutputs(wantedOutputs)
|
||||||
, buildMode(buildMode)
|
, buildMode(buildMode)
|
||||||
{
|
{
|
||||||
state = &DerivationGoal::getDerivation;
|
|
||||||
name = fmt(
|
name = fmt(
|
||||||
"building of '%s' from .drv file",
|
"building of '%s' from .drv file",
|
||||||
DerivedPath::Built { makeConstantStorePathRef(drvPath), wantedOutputs }.to_string(worker.store));
|
DerivedPath::Built { makeConstantStorePathRef(drvPath), wantedOutputs }.to_string(worker.store));
|
||||||
trace("created");
|
trace("created");
|
||||||
|
|
||||||
mcExpectedBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.expectedBuilds);
|
mcExpectedBuilds = worker.expectedBuilds.addTemporarily(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
|
DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
|
||||||
const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
|
const OutputsSpec & wantedOutputs, Worker & worker, bool isDependency, BuildMode buildMode)
|
||||||
: Goal(worker)
|
: Goal(worker, isDependency)
|
||||||
, useDerivation(false)
|
, useDerivation(false)
|
||||||
, drvPath(drvPath)
|
, drvPath(drvPath)
|
||||||
, wantedOutputs(wantedOutputs)
|
, wantedOutputs(wantedOutputs)
|
||||||
|
@ -84,13 +90,12 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation
|
||||||
{
|
{
|
||||||
this->drv = std::make_unique<Derivation>(drv);
|
this->drv = std::make_unique<Derivation>(drv);
|
||||||
|
|
||||||
state = &DerivationGoal::haveDerivation;
|
|
||||||
name = fmt(
|
name = fmt(
|
||||||
"building of '%s' from in-memory derivation",
|
"building of '%s' from in-memory derivation",
|
||||||
DerivedPath::Built { makeConstantStorePathRef(drvPath), drv.outputNames() }.to_string(worker.store));
|
DerivedPath::Built { makeConstantStorePathRef(drvPath), drv.outputNames() }.to_string(worker.store));
|
||||||
trace("created");
|
trace("created");
|
||||||
|
|
||||||
mcExpectedBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.expectedBuilds);
|
mcExpectedBuilds = worker.expectedBuilds.addTemporarily(1);
|
||||||
|
|
||||||
/* Prevent the .chroot directory from being
|
/* Prevent the .chroot directory from being
|
||||||
garbage-collected. (See isActiveTempFile() in gc.cc.) */
|
garbage-collected. (See isActiveTempFile() in gc.cc.) */
|
||||||
|
@ -102,17 +107,7 @@ DerivationGoal::~DerivationGoal() noexcept(false)
|
||||||
{
|
{
|
||||||
/* Careful: we should never ever throw an exception from a
|
/* Careful: we should never ever throw an exception from a
|
||||||
destructor. */
|
destructor. */
|
||||||
try { closeLogFile(); } catch (...) { ignoreException(); }
|
try { closeLogFile(); } catch (...) { ignoreExceptionInDestructor(); }
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
std::string DerivationGoal::key()
|
|
||||||
{
|
|
||||||
/* Ensure that derivations get built in order of their name,
|
|
||||||
i.e. a derivation named "aardvark" always comes before
|
|
||||||
"baboon". And substitution goals always happen before
|
|
||||||
derivation goals (due to "b$"). */
|
|
||||||
return "b$" + std::string(drvPath.name()) + "$" + worker.store.printStorePath(drvPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,20 +118,24 @@ void DerivationGoal::killChild()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Goal::Finished DerivationGoal::timedOut(Error && ex)
|
Goal::WorkResult DerivationGoal::timedOut(Error && ex)
|
||||||
{
|
{
|
||||||
killChild();
|
killChild();
|
||||||
return done(BuildResult::TimedOut, {}, std::move(ex));
|
return done(BuildResult::TimedOut, {}, std::move(ex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Goal::WorkResult DerivationGoal::work(bool inBuildSlot)
|
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::workImpl() noexcept
|
||||||
{
|
{
|
||||||
return (this->*state)(inBuildSlot);
|
return useDerivation ? getDerivation() : haveDerivation();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DerivationGoal::addWantedOutputs(const OutputsSpec & outputs)
|
bool DerivationGoal::addWantedOutputs(const OutputsSpec & outputs)
|
||||||
{
|
{
|
||||||
|
if (isDone) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
auto newWanted = wantedOutputs.union_(outputs);
|
auto newWanted = wantedOutputs.union_(outputs);
|
||||||
switch (needRestart) {
|
switch (needRestart) {
|
||||||
case NeedRestartForMoreOutputs::OutputsUnmodifedDontNeed:
|
case NeedRestartForMoreOutputs::OutputsUnmodifedDontNeed:
|
||||||
|
@ -153,32 +152,38 @@ void DerivationGoal::addWantedOutputs(const OutputsSpec & outputs)
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
wantedOutputs = newWanted;
|
wantedOutputs = newWanted;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Goal::WorkResult DerivationGoal::getDerivation(bool inBuildSlot)
|
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::getDerivation() noexcept
|
||||||
{
|
try {
|
||||||
trace("init");
|
trace("init");
|
||||||
|
|
||||||
/* The first thing to do is to make sure that the derivation
|
/* The first thing to do is to make sure that the derivation
|
||||||
exists. If it doesn't, it may be created through a
|
exists. If it doesn't, it may be created through a
|
||||||
substitute. */
|
substitute. */
|
||||||
if (buildMode == bmNormal && worker.evalStore.isValidPath(drvPath)) {
|
if (buildMode == bmNormal && worker.evalStore.isValidPath(drvPath)) {
|
||||||
return loadDerivation(inBuildSlot);
|
co_return co_await loadDerivation();
|
||||||
|
}
|
||||||
|
|
||||||
|
(co_await waitForGoals(worker.goalFactory().makePathSubstitutionGoal(drvPath))).value();
|
||||||
|
co_return co_await loadDerivation();
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::failure(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
state = &DerivationGoal::loadDerivation;
|
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::loadDerivation() noexcept
|
||||||
return WaitForGoals{{worker.makePathSubstitutionGoal(drvPath)}};
|
try {
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Goal::WorkResult DerivationGoal::loadDerivation(bool inBuildSlot)
|
|
||||||
{
|
|
||||||
trace("loading derivation");
|
trace("loading derivation");
|
||||||
|
|
||||||
if (nrFailed != 0) {
|
if (nrFailed != 0) {
|
||||||
return done(BuildResult::MiscFailure, {}, Error("cannot build missing derivation '%s'", worker.store.printStorePath(drvPath)));
|
return {done(
|
||||||
|
BuildResult::MiscFailure,
|
||||||
|
{},
|
||||||
|
Error("cannot build missing derivation '%s'", worker.store.printStorePath(drvPath))
|
||||||
|
)};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* `drvPath' should already be a root, but let's be on the safe
|
/* `drvPath' should already be a root, but let's be on the safe
|
||||||
|
@ -200,12 +205,14 @@ Goal::WorkResult DerivationGoal::loadDerivation(bool inBuildSlot)
|
||||||
}
|
}
|
||||||
assert(drv);
|
assert(drv);
|
||||||
|
|
||||||
return haveDerivation(inBuildSlot);
|
return haveDerivation();
|
||||||
|
} catch (...) {
|
||||||
|
return {std::current_exception()};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Goal::WorkResult DerivationGoal::haveDerivation(bool inBuildSlot)
|
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::haveDerivation() noexcept
|
||||||
{
|
try {
|
||||||
trace("have derivation");
|
trace("have derivation");
|
||||||
|
|
||||||
parsedDrv = std::make_unique<ParsedDerivation>(drvPath, *drv);
|
parsedDrv = std::make_unique<ParsedDerivation>(drvPath, *drv);
|
||||||
|
@ -232,7 +239,7 @@ Goal::WorkResult DerivationGoal::haveDerivation(bool inBuildSlot)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return gaveUpOnSubstitution(inBuildSlot);
|
co_return co_await gaveUpOnSubstitution();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto & i : drv->outputsAndOptPaths(worker.store))
|
for (auto & i : drv->outputsAndOptPaths(worker.store))
|
||||||
|
@ -254,27 +261,27 @@ Goal::WorkResult DerivationGoal::haveDerivation(bool inBuildSlot)
|
||||||
|
|
||||||
/* If they are all valid, then we're done. */
|
/* If they are all valid, then we're done. */
|
||||||
if (allValid && buildMode == bmNormal) {
|
if (allValid && buildMode == bmNormal) {
|
||||||
return done(BuildResult::AlreadyValid, std::move(validOutputs));
|
co_return done(BuildResult::AlreadyValid, std::move(validOutputs));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We are first going to try to create the invalid output paths
|
/* We are first going to try to create the invalid output paths
|
||||||
through substitutes. If that doesn't work, we'll build
|
through substitutes. If that doesn't work, we'll build
|
||||||
them. */
|
them. */
|
||||||
WaitForGoals result;
|
kj::Vector<std::pair<GoalPtr, kj::Promise<Result<WorkResult>>>> dependencies;
|
||||||
if (settings.useSubstitutes) {
|
if (settings.useSubstitutes) {
|
||||||
if (parsedDrv->substitutesAllowed()) {
|
if (parsedDrv->substitutesAllowed()) {
|
||||||
for (auto & [outputName, status] : initialOutputs) {
|
for (auto & [outputName, status] : initialOutputs) {
|
||||||
if (!status.wanted) continue;
|
if (!status.wanted) continue;
|
||||||
if (!status.known)
|
if (!status.known)
|
||||||
result.goals.insert(
|
dependencies.add(
|
||||||
worker.makeDrvOutputSubstitutionGoal(
|
worker.goalFactory().makeDrvOutputSubstitutionGoal(
|
||||||
DrvOutput{status.outputHash, outputName},
|
DrvOutput{status.outputHash, outputName},
|
||||||
buildMode == bmRepair ? Repair : NoRepair
|
buildMode == bmRepair ? Repair : NoRepair
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
else {
|
else {
|
||||||
auto * cap = getDerivationCA(*drv);
|
auto * cap = getDerivationCA(*drv);
|
||||||
result.goals.insert(worker.makePathSubstitutionGoal(
|
dependencies.add(worker.goalFactory().makePathSubstitutionGoal(
|
||||||
status.known->path,
|
status.known->path,
|
||||||
buildMode == bmRepair ? Repair : NoRepair,
|
buildMode == bmRepair ? Repair : NoRepair,
|
||||||
cap ? std::optional { *cap } : std::nullopt));
|
cap ? std::optional { *cap } : std::nullopt));
|
||||||
|
@ -285,24 +292,31 @@ Goal::WorkResult DerivationGoal::haveDerivation(bool inBuildSlot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.goals.empty()) { /* to prevent hang (no wake-up event) */
|
if (!dependencies.empty()) { /* to prevent hang (no wake-up event) */
|
||||||
return outputsSubstitutionTried(inBuildSlot);
|
(co_await waitForGoals(dependencies.releaseAsArray())).value();
|
||||||
} else {
|
|
||||||
state = &DerivationGoal::outputsSubstitutionTried;
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
co_return co_await outputsSubstitutionTried();
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::failure(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
Goal::WorkResult DerivationGoal::outputsSubstitutionTried(bool inBuildSlot)
|
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::outputsSubstitutionTried() noexcept
|
||||||
{
|
try {
|
||||||
trace("all outputs substituted (maybe)");
|
trace("all outputs substituted (maybe)");
|
||||||
|
|
||||||
assert(drv->type().isPure());
|
assert(drv->type().isPure());
|
||||||
|
|
||||||
if (nrFailed > 0 && nrFailed > nrNoSubstituters + nrIncompleteClosure && !settings.tryFallback) {
|
if (nrFailed > 0 && nrFailed > nrNoSubstituters + nrIncompleteClosure && !settings.tryFallback)
|
||||||
return done(BuildResult::TransientFailure, {},
|
{
|
||||||
Error("some substitutes for the outputs of derivation '%s' failed (usually happens due to networking issues); try '--fallback' to build derivation from source ",
|
return {done(
|
||||||
worker.store.printStorePath(drvPath)));
|
BuildResult::TransientFailure,
|
||||||
|
{},
|
||||||
|
Error(
|
||||||
|
"some substitutes for the outputs of derivation '%s' failed (usually happens due "
|
||||||
|
"to networking issues); try '--fallback' to build derivation from source ",
|
||||||
|
worker.store.printStorePath(drvPath)
|
||||||
|
)
|
||||||
|
)};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If the substitutes form an incomplete closure, then we should
|
/* If the substitutes form an incomplete closure, then we should
|
||||||
|
@ -336,13 +350,13 @@ Goal::WorkResult DerivationGoal::outputsSubstitutionTried(bool inBuildSlot)
|
||||||
|
|
||||||
if (needRestart == NeedRestartForMoreOutputs::OutputsAddedDoNeed) {
|
if (needRestart == NeedRestartForMoreOutputs::OutputsAddedDoNeed) {
|
||||||
needRestart = NeedRestartForMoreOutputs::OutputsUnmodifedDontNeed;
|
needRestart = NeedRestartForMoreOutputs::OutputsUnmodifedDontNeed;
|
||||||
return haveDerivation(inBuildSlot);
|
return haveDerivation();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto [allValid, validOutputs] = checkPathValidity();
|
auto [allValid, validOutputs] = checkPathValidity();
|
||||||
|
|
||||||
if (buildMode == bmNormal && allValid) {
|
if (buildMode == bmNormal && allValid) {
|
||||||
return done(BuildResult::Substituted, std::move(validOutputs));
|
return {done(BuildResult::Substituted, std::move(validOutputs))};
|
||||||
}
|
}
|
||||||
if (buildMode == bmRepair && allValid) {
|
if (buildMode == bmRepair && allValid) {
|
||||||
return repairClosure();
|
return repairClosure();
|
||||||
|
@ -352,15 +366,17 @@ Goal::WorkResult DerivationGoal::outputsSubstitutionTried(bool inBuildSlot)
|
||||||
worker.store.printStorePath(drvPath));
|
worker.store.printStorePath(drvPath));
|
||||||
|
|
||||||
/* Nothing to wait for; tail call */
|
/* Nothing to wait for; tail call */
|
||||||
return gaveUpOnSubstitution(inBuildSlot);
|
return gaveUpOnSubstitution();
|
||||||
|
} catch (...) {
|
||||||
|
return {std::current_exception()};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* At least one of the output paths could not be
|
/* At least one of the output paths could not be
|
||||||
produced using a substitute. So we have to build instead. */
|
produced using a substitute. So we have to build instead. */
|
||||||
Goal::WorkResult DerivationGoal::gaveUpOnSubstitution(bool inBuildSlot)
|
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::gaveUpOnSubstitution() noexcept
|
||||||
{
|
try {
|
||||||
WaitForGoals result;
|
kj::Vector<std::pair<GoalPtr, kj::Promise<Result<WorkResult>>>> dependencies;
|
||||||
|
|
||||||
/* At this point we are building all outputs, so if more are wanted there
|
/* At this point we are building all outputs, so if more are wanted there
|
||||||
is no need to restart. */
|
is no need to restart. */
|
||||||
|
@ -373,7 +389,7 @@ Goal::WorkResult DerivationGoal::gaveUpOnSubstitution(bool inBuildSlot)
|
||||||
|
|
||||||
addWaiteeDerivedPath = [&](ref<SingleDerivedPath> inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
|
addWaiteeDerivedPath = [&](ref<SingleDerivedPath> inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
|
||||||
if (!inputNode.value.empty())
|
if (!inputNode.value.empty())
|
||||||
result.goals.insert(worker.makeGoal(
|
dependencies.add(worker.goalFactory().makeGoal(
|
||||||
DerivedPath::Built {
|
DerivedPath::Built {
|
||||||
.drvPath = inputDrv,
|
.drvPath = inputDrv,
|
||||||
.outputs = inputNode.value,
|
.outputs = inputNode.value,
|
||||||
|
@ -418,20 +434,20 @@ Goal::WorkResult DerivationGoal::gaveUpOnSubstitution(bool inBuildSlot)
|
||||||
if (!settings.useSubstitutes)
|
if (!settings.useSubstitutes)
|
||||||
throw Error("dependency '%s' of '%s' does not exist, and substitution is disabled",
|
throw Error("dependency '%s' of '%s' does not exist, and substitution is disabled",
|
||||||
worker.store.printStorePath(i), worker.store.printStorePath(drvPath));
|
worker.store.printStorePath(i), worker.store.printStorePath(drvPath));
|
||||||
result.goals.insert(worker.makePathSubstitutionGoal(i));
|
dependencies.add(worker.goalFactory().makePathSubstitutionGoal(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.goals.empty()) {/* to prevent hang (no wake-up event) */
|
if (!dependencies.empty()) {/* to prevent hang (no wake-up event) */
|
||||||
return inputsRealised(inBuildSlot);
|
(co_await waitForGoals(dependencies.releaseAsArray())).value();
|
||||||
} else {
|
|
||||||
state = &DerivationGoal::inputsRealised;
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
co_return co_await inputsRealised();
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::failure(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Goal::WorkResult DerivationGoal::repairClosure()
|
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::repairClosure() noexcept
|
||||||
{
|
try {
|
||||||
assert(drv->type().isPure());
|
assert(drv->type().isPure());
|
||||||
|
|
||||||
/* If we're repairing, we now know that our own outputs are valid.
|
/* If we're repairing, we now know that our own outputs are valid.
|
||||||
|
@ -466,7 +482,7 @@ Goal::WorkResult DerivationGoal::repairClosure()
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check each path (slow!). */
|
/* Check each path (slow!). */
|
||||||
WaitForGoals result;
|
kj::Vector<std::pair<GoalPtr, kj::Promise<Result<WorkResult>>>> dependencies;
|
||||||
for (auto & i : outputClosure) {
|
for (auto & i : outputClosure) {
|
||||||
if (worker.pathContentsGood(i)) continue;
|
if (worker.pathContentsGood(i)) continue;
|
||||||
printError(
|
printError(
|
||||||
|
@ -474,9 +490,9 @@ Goal::WorkResult DerivationGoal::repairClosure()
|
||||||
worker.store.printStorePath(i), worker.store.printStorePath(drvPath));
|
worker.store.printStorePath(i), worker.store.printStorePath(drvPath));
|
||||||
auto drvPath2 = outputsToDrv.find(i);
|
auto drvPath2 = outputsToDrv.find(i);
|
||||||
if (drvPath2 == outputsToDrv.end())
|
if (drvPath2 == outputsToDrv.end())
|
||||||
result.goals.insert(worker.makePathSubstitutionGoal(i, Repair));
|
dependencies.add(worker.goalFactory().makePathSubstitutionGoal(i, Repair));
|
||||||
else
|
else
|
||||||
result.goals.insert(worker.makeGoal(
|
dependencies.add(worker.goalFactory().makeGoal(
|
||||||
DerivedPath::Built {
|
DerivedPath::Built {
|
||||||
.drvPath = makeConstantStorePathRef(drvPath2->second),
|
.drvPath = makeConstantStorePathRef(drvPath2->second),
|
||||||
.outputs = OutputsSpec::All { },
|
.outputs = OutputsSpec::All { },
|
||||||
|
@ -484,40 +500,50 @@ Goal::WorkResult DerivationGoal::repairClosure()
|
||||||
bmRepair));
|
bmRepair));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.goals.empty()) {
|
if (dependencies.empty()) {
|
||||||
return done(BuildResult::AlreadyValid, assertPathValidity());
|
co_return done(BuildResult::AlreadyValid, assertPathValidity());
|
||||||
}
|
}
|
||||||
|
|
||||||
state = &DerivationGoal::closureRepaired;
|
(co_await waitForGoals(dependencies.releaseAsArray())).value();
|
||||||
return result;
|
co_return co_await closureRepaired();
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::failure(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Goal::WorkResult DerivationGoal::closureRepaired(bool inBuildSlot)
|
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::closureRepaired() noexcept
|
||||||
{
|
try {
|
||||||
trace("closure repaired");
|
trace("closure repaired");
|
||||||
if (nrFailed > 0)
|
if (nrFailed > 0)
|
||||||
throw Error("some paths in the output closure of derivation '%s' could not be repaired",
|
throw Error("some paths in the output closure of derivation '%s' could not be repaired",
|
||||||
worker.store.printStorePath(drvPath));
|
worker.store.printStorePath(drvPath));
|
||||||
return done(BuildResult::AlreadyValid, assertPathValidity());
|
return {done(BuildResult::AlreadyValid, assertPathValidity())};
|
||||||
|
} catch (...) {
|
||||||
|
return {std::current_exception()};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Goal::WorkResult DerivationGoal::inputsRealised(bool inBuildSlot)
|
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::inputsRealised() noexcept
|
||||||
{
|
try {
|
||||||
trace("all inputs realised");
|
trace("all inputs realised");
|
||||||
|
|
||||||
if (nrFailed != 0) {
|
if (nrFailed != 0) {
|
||||||
if (!useDerivation)
|
if (!useDerivation)
|
||||||
throw Error("some dependencies of '%s' are missing", worker.store.printStorePath(drvPath));
|
throw Error("some dependencies of '%s' are missing", worker.store.printStorePath(drvPath));
|
||||||
return done(BuildResult::DependencyFailed, {}, Error(
|
co_return done(
|
||||||
|
BuildResult::DependencyFailed,
|
||||||
|
{},
|
||||||
|
Error(
|
||||||
"%s dependencies of derivation '%s' failed to build",
|
"%s dependencies of derivation '%s' failed to build",
|
||||||
nrFailed, worker.store.printStorePath(drvPath)));
|
nrFailed,
|
||||||
|
worker.store.printStorePath(drvPath)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (retrySubstitution == RetrySubstitution::YesNeed) {
|
if (retrySubstitution == RetrySubstitution::YesNeed) {
|
||||||
retrySubstitution = RetrySubstitution::AlreadyRetried;
|
retrySubstitution = RetrySubstitution::AlreadyRetried;
|
||||||
return haveDerivation(inBuildSlot);
|
co_return co_await haveDerivation();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Gather information necessary for computing the closure and/or
|
/* Gather information necessary for computing the closure and/or
|
||||||
|
@ -579,11 +605,12 @@ Goal::WorkResult DerivationGoal::inputsRealised(bool inBuildSlot)
|
||||||
worker.store.printStorePath(pathResolved),
|
worker.store.printStorePath(pathResolved),
|
||||||
});
|
});
|
||||||
|
|
||||||
resolvedDrvGoal = worker.makeDerivationGoal(
|
auto dependency = worker.goalFactory().makeDerivationGoal(
|
||||||
pathResolved, wantedOutputs, buildMode);
|
pathResolved, wantedOutputs, buildMode);
|
||||||
|
resolvedDrvGoal = dependency.first;
|
||||||
|
|
||||||
state = &DerivationGoal::resolvedFinished;
|
(co_await waitForGoals(std::move(dependency))).value();
|
||||||
return WaitForGoals{{resolvedDrvGoal}};
|
co_return co_await resolvedFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accumInputPaths;
|
std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accumInputPaths;
|
||||||
|
@ -647,8 +674,9 @@ Goal::WorkResult DerivationGoal::inputsRealised(bool inBuildSlot)
|
||||||
/* Okay, try to build. Note that here we don't wait for a build
|
/* Okay, try to build. Note that here we don't wait for a build
|
||||||
slot to become available, since we don't need one if there is a
|
slot to become available, since we don't need one if there is a
|
||||||
build hook. */
|
build hook. */
|
||||||
state = &DerivationGoal::tryToBuild;
|
co_return co_await tryToBuild();
|
||||||
return ContinueImmediately{};
|
} catch (...) {
|
||||||
|
co_return result::failure(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
void DerivationGoal::started()
|
void DerivationGoal::started()
|
||||||
|
@ -661,11 +689,12 @@ void DerivationGoal::started()
|
||||||
if (hook) msg += fmt(" on '%s'", machineName);
|
if (hook) msg += fmt(" on '%s'", machineName);
|
||||||
act = std::make_unique<Activity>(*logger, lvlInfo, actBuild, msg,
|
act = std::make_unique<Activity>(*logger, lvlInfo, actBuild, msg,
|
||||||
Logger::Fields{worker.store.printStorePath(drvPath), hook ? machineName : "", 1, 1});
|
Logger::Fields{worker.store.printStorePath(drvPath), hook ? machineName : "", 1, 1});
|
||||||
mcRunningBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.runningBuilds);
|
mcRunningBuilds = worker.runningBuilds.addTemporarily(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Goal::WorkResult DerivationGoal::tryToBuild(bool inBuildSlot)
|
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::tryToBuild() noexcept
|
||||||
{
|
try {
|
||||||
|
retry:
|
||||||
trace("trying to build");
|
trace("trying to build");
|
||||||
|
|
||||||
/* Obtain locks on all output paths, if the paths are known a priori.
|
/* Obtain locks on all output paths, if the paths are known a priori.
|
||||||
|
@ -699,7 +728,9 @@ Goal::WorkResult DerivationGoal::tryToBuild(bool inBuildSlot)
|
||||||
if (!actLock)
|
if (!actLock)
|
||||||
actLock = std::make_unique<Activity>(*logger, lvlWarn, actBuildWaiting,
|
actLock = std::make_unique<Activity>(*logger, lvlWarn, actBuildWaiting,
|
||||||
fmt("waiting for lock on %s", Magenta(showPaths(lockFiles))));
|
fmt("waiting for lock on %s", Magenta(showPaths(lockFiles))));
|
||||||
return WaitForAWhile{};
|
co_await waitForAWhile();
|
||||||
|
// we can loop very often, and `co_return co_await` always allocates a new frame
|
||||||
|
goto retry;
|
||||||
}
|
}
|
||||||
|
|
||||||
actLock.reset();
|
actLock.reset();
|
||||||
|
@ -716,7 +747,7 @@ Goal::WorkResult DerivationGoal::tryToBuild(bool inBuildSlot)
|
||||||
if (buildMode != bmCheck && allValid) {
|
if (buildMode != bmCheck && allValid) {
|
||||||
debug("skipping build of derivation '%s', someone beat us to it", worker.store.printStorePath(drvPath));
|
debug("skipping build of derivation '%s', someone beat us to it", worker.store.printStorePath(drvPath));
|
||||||
outputLocks.setDeletion(true);
|
outputLocks.setDeletion(true);
|
||||||
return done(BuildResult::AlreadyValid, std::move(validOutputs));
|
co_return done(BuildResult::AlreadyValid, std::move(validOutputs));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If any of the outputs already exist but are not valid, delete
|
/* If any of the outputs already exist but are not valid, delete
|
||||||
|
@ -736,49 +767,63 @@ Goal::WorkResult DerivationGoal::tryToBuild(bool inBuildSlot)
|
||||||
&& settings.maxBuildJobs.get() != 0;
|
&& settings.maxBuildJobs.get() != 0;
|
||||||
|
|
||||||
if (!buildLocally) {
|
if (!buildLocally) {
|
||||||
auto hookReply = tryBuildHook(inBuildSlot);
|
auto hookReply = tryBuildHook();
|
||||||
auto result = std::visit(
|
switch (hookReply.index()) {
|
||||||
overloaded{
|
case 0: {
|
||||||
[&](HookReply::Accept & a) -> std::optional<WorkResult> {
|
HookReply::Accept & a = std::get<0>(hookReply);
|
||||||
/* Yes, it has started doing so. Wait until we get
|
/* Yes, it has started doing so. Wait until we get
|
||||||
EOF from the hook. */
|
EOF from the hook. */
|
||||||
actLock.reset();
|
actLock.reset();
|
||||||
buildResult.startTime = time(0); // inexact
|
buildResult.startTime = time(0); // inexact
|
||||||
state = &DerivationGoal::buildDone;
|
|
||||||
started();
|
started();
|
||||||
return WaitForWorld{std::move(a.fds), false};
|
auto r = co_await a.promise;
|
||||||
},
|
if (r.has_value()) {
|
||||||
[&](HookReply::Postpone) -> std::optional<WorkResult> {
|
co_return co_await buildDone();
|
||||||
|
} else if (r.has_error()) {
|
||||||
|
co_return r.assume_error();
|
||||||
|
} else {
|
||||||
|
co_return r.assume_exception();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case 1: {
|
||||||
|
HookReply::Decline _ [[gnu::unused]] = std::get<1>(hookReply);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2: {
|
||||||
|
HookReply::Postpone _ [[gnu::unused]] = std::get<2>(hookReply);
|
||||||
/* Not now; wait until at least one child finishes or
|
/* Not now; wait until at least one child finishes or
|
||||||
the wake-up timeout expires. */
|
the wake-up timeout expires. */
|
||||||
if (!actLock)
|
if (!actLock)
|
||||||
actLock = std::make_unique<Activity>(*logger, lvlTalkative, actBuildWaiting,
|
actLock = std::make_unique<Activity>(*logger, lvlTalkative, actBuildWaiting,
|
||||||
fmt("waiting for a machine to build '%s'", Magenta(worker.store.printStorePath(drvPath))));
|
fmt("waiting for a machine to build '%s'", Magenta(worker.store.printStorePath(drvPath))));
|
||||||
outputLocks.unlock();
|
outputLocks.unlock();
|
||||||
return WaitForAWhile{};
|
co_await waitForAWhile();
|
||||||
},
|
goto retry;
|
||||||
[&](HookReply::Decline) -> std::optional<WorkResult> {
|
}
|
||||||
/* We should do it ourselves. */
|
|
||||||
return std::nullopt;
|
default:
|
||||||
},
|
// can't static_assert this because HookReply *subclasses* variant and std::variant_size breaks
|
||||||
},
|
assert(false && "unexpected hook reply");
|
||||||
hookReply);
|
|
||||||
if (result) {
|
|
||||||
return std::move(*result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
actLock.reset();
|
actLock.reset();
|
||||||
|
|
||||||
state = &DerivationGoal::tryLocalBuild;
|
co_return co_await tryLocalBuild();
|
||||||
return ContinueImmediately{};
|
} catch (...) {
|
||||||
|
co_return result::failure(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
Goal::WorkResult DerivationGoal::tryLocalBuild(bool inBuildSlot) {
|
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::tryLocalBuild() noexcept
|
||||||
|
try {
|
||||||
throw Error(
|
throw Error(
|
||||||
"unable to build with a primary store that isn't a local store; "
|
"unable to build with a primary store that isn't a local store; "
|
||||||
"either pass a different '--store' or enable remote builds."
|
"either pass a different '--store' or enable remote builds."
|
||||||
"\nhttps://docs.lix.systems/manual/lix/stable/advanced-topics/distributed-builds.html");
|
"\nhttps://docs.lix.systems/manual/lix/stable/advanced-topics/distributed-builds.html");
|
||||||
|
} catch (...) {
|
||||||
|
return {std::current_exception()};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -818,7 +863,7 @@ void replaceValidPath(const Path & storePath, const Path & tmpPath)
|
||||||
// attempt to recover
|
// attempt to recover
|
||||||
movePath(oldPath, storePath);
|
movePath(oldPath, storePath);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
ignoreException();
|
ignoreExceptionExceptInterrupt();
|
||||||
}
|
}
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
@ -934,10 +979,11 @@ void runPostBuildHook(
|
||||||
proc.getStdout()->drainInto(sink);
|
proc.getStdout()->drainInto(sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
Goal::WorkResult DerivationGoal::buildDone(bool inBuildSlot)
|
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::buildDone() noexcept
|
||||||
{
|
try {
|
||||||
trace("build done");
|
trace("build done");
|
||||||
|
|
||||||
|
slotToken = {};
|
||||||
Finally releaseBuildUser([&](){ this->cleanupHookFinally(); });
|
Finally releaseBuildUser([&](){ this->cleanupHookFinally(); });
|
||||||
|
|
||||||
cleanupPreChildKill();
|
cleanupPreChildKill();
|
||||||
|
@ -953,9 +999,6 @@ Goal::WorkResult DerivationGoal::buildDone(bool inBuildSlot)
|
||||||
buildResult.timesBuilt++;
|
buildResult.timesBuilt++;
|
||||||
buildResult.stopTime = time(0);
|
buildResult.stopTime = time(0);
|
||||||
|
|
||||||
/* So the child is gone now. */
|
|
||||||
worker.childTerminated(this);
|
|
||||||
|
|
||||||
/* Close the read side of the logger pipe. */
|
/* Close the read side of the logger pipe. */
|
||||||
closeReadPipes();
|
closeReadPipes();
|
||||||
|
|
||||||
|
@ -1029,7 +1072,7 @@ Goal::WorkResult DerivationGoal::buildDone(bool inBuildSlot)
|
||||||
outputLocks.setDeletion(true);
|
outputLocks.setDeletion(true);
|
||||||
outputLocks.unlock();
|
outputLocks.unlock();
|
||||||
|
|
||||||
return done(BuildResult::Built, std::move(builtOutputs));
|
return {done(BuildResult::Built, std::move(builtOutputs))};
|
||||||
} catch (BuildError & e) {
|
} catch (BuildError & e) {
|
||||||
outputLocks.unlock();
|
outputLocks.unlock();
|
||||||
|
|
||||||
|
@ -1050,12 +1093,14 @@ Goal::WorkResult DerivationGoal::buildDone(bool inBuildSlot)
|
||||||
BuildResult::PermanentFailure;
|
BuildResult::PermanentFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
return done(st, {}, std::move(e));
|
return {done(st, {}, std::move(e))};
|
||||||
}
|
}
|
||||||
|
} catch (...) {
|
||||||
|
return {std::current_exception()};
|
||||||
}
|
}
|
||||||
|
|
||||||
Goal::WorkResult DerivationGoal::resolvedFinished(bool inBuildSlot)
|
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::resolvedFinished() noexcept
|
||||||
{
|
try {
|
||||||
trace("resolved derivation finished");
|
trace("resolved derivation finished");
|
||||||
|
|
||||||
assert(resolvedDrvGoal);
|
assert(resolvedDrvGoal);
|
||||||
|
@ -1122,10 +1167,12 @@ Goal::WorkResult DerivationGoal::resolvedFinished(bool inBuildSlot)
|
||||||
if (status == BuildResult::AlreadyValid)
|
if (status == BuildResult::AlreadyValid)
|
||||||
status = BuildResult::ResolvesToAlreadyValid;
|
status = BuildResult::ResolvesToAlreadyValid;
|
||||||
|
|
||||||
return done(status, std::move(builtOutputs));
|
return {done(status, std::move(builtOutputs))};
|
||||||
|
} catch (...) {
|
||||||
|
return {std::current_exception()};
|
||||||
}
|
}
|
||||||
|
|
||||||
HookReply DerivationGoal::tryBuildHook(bool inBuildSlot)
|
HookReply DerivationGoal::tryBuildHook()
|
||||||
{
|
{
|
||||||
if (!worker.hook.available || !useDerivation) return HookReply::Decline{};
|
if (!worker.hook.available || !useDerivation) return HookReply::Decline{};
|
||||||
|
|
||||||
|
@ -1137,7 +1184,7 @@ HookReply DerivationGoal::tryBuildHook(bool inBuildSlot)
|
||||||
/* Send the request to the hook. */
|
/* Send the request to the hook. */
|
||||||
worker.hook.instance->sink
|
worker.hook.instance->sink
|
||||||
<< "try"
|
<< "try"
|
||||||
<< (inBuildSlot ? 1 : 0)
|
<< (slotToken.valid() ? 1 : 0)
|
||||||
<< drv->platform
|
<< drv->platform
|
||||||
<< worker.store.printStorePath(drvPath)
|
<< worker.store.printStorePath(drvPath)
|
||||||
<< parsedDrv->getRequiredSystemFeatures();
|
<< parsedDrv->getRequiredSystemFeatures();
|
||||||
|
@ -1223,12 +1270,8 @@ HookReply DerivationGoal::tryBuildHook(bool inBuildSlot)
|
||||||
/* Create the log file and pipe. */
|
/* Create the log file and pipe. */
|
||||||
Path logFile = openLogFile();
|
Path logFile = openLogFile();
|
||||||
|
|
||||||
std::set<int> fds;
|
|
||||||
fds.insert(hook->fromHook.get());
|
|
||||||
fds.insert(hook->builderOut.get());
|
|
||||||
builderOutFD = &hook->builderOut;
|
builderOutFD = &hook->builderOut;
|
||||||
|
return HookReply::Accept{handleChildOutput()};
|
||||||
return HookReply::Accept{std::move(fds)};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1288,23 +1331,69 @@ void DerivationGoal::closeLogFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Goal::WorkResult DerivationGoal::handleChildOutput(int fd, std::string_view data)
|
Goal::WorkResult DerivationGoal::tooMuchLogs()
|
||||||
{
|
{
|
||||||
assert(builderOutFD);
|
|
||||||
|
|
||||||
auto tooMuchLogs = [&] {
|
|
||||||
killChild();
|
killChild();
|
||||||
return done(
|
return done(
|
||||||
BuildResult::LogLimitExceeded, {},
|
BuildResult::LogLimitExceeded, {},
|
||||||
Error("%s killed after writing more than %d bytes of log output",
|
Error("%s killed after writing more than %d bytes of log output",
|
||||||
getName(), settings.maxLogSize));
|
getName(), settings.maxLogSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DerivationGoal::InputStream final : private kj::AsyncObject
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
kj::UnixEventPort::FdObserver observer;
|
||||||
|
|
||||||
|
InputStream(kj::UnixEventPort & ep, int fd)
|
||||||
|
: fd(fd)
|
||||||
|
, observer(ep, fd, kj::UnixEventPort::FdObserver::OBSERVE_READ)
|
||||||
|
{
|
||||||
|
int flags = fcntl(fd, F_GETFL);
|
||||||
|
if (flags < 0) {
|
||||||
|
throw SysError("fcntl(F_GETFL) failed on fd %i", fd);
|
||||||
|
}
|
||||||
|
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
|
||||||
|
throw SysError("fcntl(F_SETFL) failed on fd %i", fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<std::string_view> read(kj::ArrayPtr<char> buffer)
|
||||||
|
{
|
||||||
|
const auto res = ::read(fd, buffer.begin(), buffer.size());
|
||||||
|
// closing a pty endpoint causes EIO on the other endpoint. stock kj streams
|
||||||
|
// do not handle this and throw exceptions we can't ask for errno instead :(
|
||||||
|
// (we can't use `errno` either because kj may well have mangled it by now.)
|
||||||
|
if (res == 0 || (res == -1 && errno == EIO)) {
|
||||||
|
return std::string_view{};
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_NONBLOCKING_SYSCALL(res) {}
|
||||||
|
|
||||||
|
if (res > 0) {
|
||||||
|
return std::string_view{buffer.begin(), static_cast<size_t>(res)};
|
||||||
|
}
|
||||||
|
|
||||||
|
return observer.whenBecomesReadable().then([this, buffer] {
|
||||||
|
return read(buffer);
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// local & `ssh://`-builds are dealt with here.
|
kj::Promise<Outcome<void, Goal::WorkResult>> DerivationGoal::handleBuilderOutput(InputStream & in) noexcept
|
||||||
if (fd == builderOutFD->get()) {
|
try {
|
||||||
|
auto buf = kj::heapArray<char>(4096);
|
||||||
|
while (true) {
|
||||||
|
auto data = co_await in.read(buf);
|
||||||
|
lastChildActivity = worker.aio.provider->getTimer().now();
|
||||||
|
|
||||||
|
if (data.empty()) {
|
||||||
|
co_return result::success();
|
||||||
|
}
|
||||||
|
|
||||||
logSize += data.size();
|
logSize += data.size();
|
||||||
if (settings.maxLogSize && logSize > settings.maxLogSize) {
|
if (settings.maxLogSize && logSize > settings.maxLogSize) {
|
||||||
return tooMuchLogs();
|
co_return tooMuchLogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto c : data)
|
for (auto c : data)
|
||||||
|
@ -1319,10 +1408,22 @@ Goal::WorkResult DerivationGoal::handleChildOutput(int fd, std::string_view data
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logSink) (*logSink)(data);
|
if (logSink) (*logSink)(data);
|
||||||
return StillAlive{};
|
}
|
||||||
|
} catch (...) {
|
||||||
|
co_return std::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<Outcome<void, Goal::WorkResult>> DerivationGoal::handleHookOutput(InputStream & in) noexcept
|
||||||
|
try {
|
||||||
|
auto buf = kj::heapArray<char>(4096);
|
||||||
|
while (true) {
|
||||||
|
auto data = co_await in.read(buf);
|
||||||
|
lastChildActivity = worker.aio.provider->getTimer().now();
|
||||||
|
|
||||||
|
if (data.empty()) {
|
||||||
|
co_return result::success();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hook && fd == hook->fromHook.get()) {
|
|
||||||
for (auto c : data)
|
for (auto c : data)
|
||||||
if (c == '\n') {
|
if (c == '\n') {
|
||||||
auto json = parseJSONMessage(currentHookLine);
|
auto json = parseJSONMessage(currentHookLine);
|
||||||
|
@ -1338,7 +1439,7 @@ Goal::WorkResult DerivationGoal::handleChildOutput(int fd, std::string_view data
|
||||||
(fields.size() > 0 ? fields[0].get<std::string>() : "") + "\n";
|
(fields.size() > 0 ? fields[0].get<std::string>() : "") + "\n";
|
||||||
logSize += logLine.size();
|
logSize += logLine.size();
|
||||||
if (settings.maxLogSize && logSize > settings.maxLogSize) {
|
if (settings.maxLogSize && logSize > settings.maxLogSize) {
|
||||||
return tooMuchLogs();
|
co_return tooMuchLogs();
|
||||||
}
|
}
|
||||||
(*logSink)(logLine);
|
(*logSink)(logLine);
|
||||||
} else if (type == resSetPhase && ! fields.is_null()) {
|
} else if (type == resSetPhase && ! fields.is_null()) {
|
||||||
|
@ -1362,16 +1463,83 @@ Goal::WorkResult DerivationGoal::handleChildOutput(int fd, std::string_view data
|
||||||
} else
|
} else
|
||||||
currentHookLine += c;
|
currentHookLine += c;
|
||||||
}
|
}
|
||||||
|
} catch (...) {
|
||||||
return StillAlive{};
|
co_return std::current_exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kj::Promise<Outcome<void, Goal::WorkResult>> DerivationGoal::handleChildOutput() noexcept
|
||||||
|
try {
|
||||||
|
assert(builderOutFD);
|
||||||
|
|
||||||
void DerivationGoal::handleEOF(int fd)
|
auto builderIn = kj::heap<InputStream>(worker.aio.unixEventPort, builderOutFD->get());
|
||||||
{
|
kj::Own<InputStream> hookIn;
|
||||||
|
if (hook) {
|
||||||
|
hookIn = kj::heap<InputStream>(worker.aio.unixEventPort, hook->fromHook.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto handlers = handleChildStreams(*builderIn, hookIn.get()).attach(std::move(builderIn), std::move(hookIn));
|
||||||
|
|
||||||
|
if (respectsTimeouts() && settings.buildTimeout != 0) {
|
||||||
|
handlers = handlers.exclusiveJoin(
|
||||||
|
worker.aio.provider->getTimer()
|
||||||
|
.afterDelay(settings.buildTimeout.get() * kj::SECONDS)
|
||||||
|
.then([this]() -> Outcome<void, WorkResult> {
|
||||||
|
return timedOut(
|
||||||
|
Error("%1% timed out after %2% seconds", name, settings.buildTimeout)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return handlers.then([this](auto r) -> Outcome<void, WorkResult> {
|
||||||
if (!currentLogLine.empty()) flushLine();
|
if (!currentLogLine.empty()) flushLine();
|
||||||
|
return r;
|
||||||
|
});
|
||||||
|
} catch (...) {
|
||||||
|
return {std::current_exception()};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kj::Promise<Outcome<void, Goal::WorkResult>> DerivationGoal::monitorForSilence() noexcept
|
||||||
|
{
|
||||||
|
while (true) {
|
||||||
|
const auto stash = lastChildActivity;
|
||||||
|
auto waitUntil = lastChildActivity + settings.maxSilentTime.get() * kj::SECONDS;
|
||||||
|
co_await worker.aio.provider->getTimer().atTime(waitUntil);
|
||||||
|
if (lastChildActivity == stash) {
|
||||||
|
co_return timedOut(
|
||||||
|
Error("%1% timed out after %2% seconds of silence", name, settings.maxSilentTime)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<Outcome<void, Goal::WorkResult>>
|
||||||
|
DerivationGoal::handleChildStreams(InputStream & builderIn, InputStream * hookIn) noexcept
|
||||||
|
{
|
||||||
|
lastChildActivity = worker.aio.provider->getTimer().now();
|
||||||
|
|
||||||
|
auto handlers = kj::joinPromisesFailFast([&] {
|
||||||
|
kj::Vector<kj::Promise<Outcome<void, WorkResult>>> parts{2};
|
||||||
|
|
||||||
|
parts.add(handleBuilderOutput(builderIn));
|
||||||
|
if (hookIn) {
|
||||||
|
parts.add(handleHookOutput(*hookIn));
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.releaseAsArray();
|
||||||
|
}());
|
||||||
|
|
||||||
|
if (respectsTimeouts() && settings.maxSilentTime != 0) {
|
||||||
|
handlers = handlers.exclusiveJoin(monitorForSilence().then([](auto r) {
|
||||||
|
return kj::arr(std::move(r));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto r : co_await handlers) {
|
||||||
|
BOOST_OUTCOME_CO_TRYV(r);
|
||||||
|
}
|
||||||
|
co_return result::success();
|
||||||
|
}
|
||||||
|
|
||||||
void DerivationGoal::flushLine()
|
void DerivationGoal::flushLine()
|
||||||
{
|
{
|
||||||
|
@ -1512,11 +1680,13 @@ SingleDrvOutputs DerivationGoal::assertPathValidity()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Goal::Finished DerivationGoal::done(
|
Goal::WorkResult DerivationGoal::done(
|
||||||
BuildResult::Status status,
|
BuildResult::Status status,
|
||||||
SingleDrvOutputs builtOutputs,
|
SingleDrvOutputs builtOutputs,
|
||||||
std::optional<Error> ex)
|
std::optional<Error> ex)
|
||||||
{
|
{
|
||||||
|
isDone = true;
|
||||||
|
|
||||||
outputLocks.unlock();
|
outputLocks.unlock();
|
||||||
buildResult.status = status;
|
buildResult.status = status;
|
||||||
if (ex)
|
if (ex)
|
||||||
|
@ -1543,8 +1713,13 @@ Goal::Finished DerivationGoal::done(
|
||||||
fs << worker.store.printStorePath(drvPath) << "\t" << buildResult.toString() << std::endl;
|
fs << worker.store.printStorePath(drvPath) << "\t" << buildResult.toString() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Finished{
|
if (ex && isDependency) {
|
||||||
.result = buildResult.success() ? ecSuccess : ecFailed,
|
logError(ex->info());
|
||||||
|
}
|
||||||
|
|
||||||
|
return WorkResult{
|
||||||
|
.exitCode = buildResult.success() ? ecSuccess : ecFailed,
|
||||||
|
.result = buildResult,
|
||||||
.ex = ex ? std::make_shared<Error>(std::move(*ex)) : nullptr,
|
.ex = ex ? std::make_shared<Error>(std::move(*ex)) : nullptr,
|
||||||
.permanentFailure = buildResult.status == BuildResult::PermanentFailure,
|
.permanentFailure = buildResult.status == BuildResult::PermanentFailure,
|
||||||
.timedOut = buildResult.status == BuildResult::TimedOut,
|
.timedOut = buildResult.status == BuildResult::TimedOut,
|
||||||
|
@ -1581,5 +1756,4 @@ void DerivationGoal::waiteeDone(GoalPtr waitee)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
|
#include "notifying-counter.hh"
|
||||||
#include "parsed-derivations.hh"
|
#include "parsed-derivations.hh"
|
||||||
#include "lock.hh"
|
#include "lock.hh"
|
||||||
#include "outputs-spec.hh"
|
#include "outputs-spec.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "pathlocks.hh"
|
#include "pathlocks.hh"
|
||||||
#include "goal.hh"
|
#include "goal.hh"
|
||||||
|
#include <kj/time.h>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -16,7 +18,7 @@ struct HookInstance;
|
||||||
|
|
||||||
struct HookReplyBase {
|
struct HookReplyBase {
|
||||||
struct [[nodiscard]] Accept {
|
struct [[nodiscard]] Accept {
|
||||||
std::set<int> fds;
|
kj::Promise<Outcome<void, Goal::WorkResult>> promise;
|
||||||
};
|
};
|
||||||
struct [[nodiscard]] Decline {};
|
struct [[nodiscard]] Decline {};
|
||||||
struct [[nodiscard]] Postpone {};
|
struct [[nodiscard]] Postpone {};
|
||||||
|
@ -61,7 +63,7 @@ struct InitialOutputStatus {
|
||||||
struct InitialOutput {
|
struct InitialOutput {
|
||||||
bool wanted;
|
bool wanted;
|
||||||
Hash outputHash;
|
Hash outputHash;
|
||||||
std::optional<InitialOutputStatus> known;
|
std::optional<InitialOutputStatus> known = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -69,6 +71,14 @@ struct InitialOutput {
|
||||||
*/
|
*/
|
||||||
struct DerivationGoal : public Goal
|
struct DerivationGoal : public Goal
|
||||||
{
|
{
|
||||||
|
struct InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this goal has completed. Completed goals can not be
|
||||||
|
* asked for more outputs, a new goal must be created instead.
|
||||||
|
*/
|
||||||
|
bool isDone = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to use an on-disk .drv file.
|
* Whether to use an on-disk .drv file.
|
||||||
*/
|
*/
|
||||||
|
@ -174,6 +184,11 @@ struct DerivationGoal : public Goal
|
||||||
|
|
||||||
std::map<std::string, InitialOutput> initialOutputs;
|
std::map<std::string, InitialOutput> initialOutputs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build result.
|
||||||
|
*/
|
||||||
|
BuildResult buildResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File descriptor for the log file.
|
* File descriptor for the log file.
|
||||||
*/
|
*/
|
||||||
|
@ -212,12 +227,9 @@ struct DerivationGoal : public Goal
|
||||||
*/
|
*/
|
||||||
std::optional<DerivationType> derivationType;
|
std::optional<DerivationType> derivationType;
|
||||||
|
|
||||||
typedef WorkResult (DerivationGoal::*GoalState)(bool inBuildSlot);
|
|
||||||
GoalState state;
|
|
||||||
|
|
||||||
BuildMode buildMode;
|
BuildMode buildMode;
|
||||||
|
|
||||||
std::unique_ptr<MaintainCount<uint64_t>> mcExpectedBuilds, mcRunningBuilds;
|
NotifyingCounter<uint64_t>::Bump mcExpectedBuilds, mcRunningBuilds;
|
||||||
|
|
||||||
std::unique_ptr<Activity> act;
|
std::unique_ptr<Activity> act;
|
||||||
|
|
||||||
|
@ -234,44 +246,42 @@ struct DerivationGoal : public Goal
|
||||||
std::string machineName;
|
std::string machineName;
|
||||||
|
|
||||||
DerivationGoal(const StorePath & drvPath,
|
DerivationGoal(const StorePath & drvPath,
|
||||||
const OutputsSpec & wantedOutputs, Worker & worker,
|
const OutputsSpec & wantedOutputs, Worker & worker, bool isDependency,
|
||||||
BuildMode buildMode = bmNormal);
|
BuildMode buildMode = bmNormal);
|
||||||
DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
|
DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
|
||||||
const OutputsSpec & wantedOutputs, Worker & worker,
|
const OutputsSpec & wantedOutputs, Worker & worker, bool isDependency,
|
||||||
BuildMode buildMode = bmNormal);
|
BuildMode buildMode = bmNormal);
|
||||||
virtual ~DerivationGoal() noexcept(false);
|
virtual ~DerivationGoal() noexcept(false);
|
||||||
|
|
||||||
Finished timedOut(Error && ex) override;
|
WorkResult timedOut(Error && ex);
|
||||||
|
|
||||||
std::string key() override;
|
kj::Promise<Result<WorkResult>> workImpl() noexcept override;
|
||||||
|
|
||||||
WorkResult work(bool inBuildSlot) override;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add wanted outputs to an already existing derivation goal.
|
* Add wanted outputs to an already existing derivation goal.
|
||||||
*/
|
*/
|
||||||
void addWantedOutputs(const OutputsSpec & outputs);
|
bool addWantedOutputs(const OutputsSpec & outputs);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The states.
|
* The states.
|
||||||
*/
|
*/
|
||||||
WorkResult getDerivation(bool inBuildSlot);
|
kj::Promise<Result<WorkResult>> getDerivation() noexcept;
|
||||||
WorkResult loadDerivation(bool inBuildSlot);
|
kj::Promise<Result<WorkResult>> loadDerivation() noexcept;
|
||||||
WorkResult haveDerivation(bool inBuildSlot);
|
kj::Promise<Result<WorkResult>> haveDerivation() noexcept;
|
||||||
WorkResult outputsSubstitutionTried(bool inBuildSlot);
|
kj::Promise<Result<WorkResult>> outputsSubstitutionTried() noexcept;
|
||||||
WorkResult gaveUpOnSubstitution(bool inBuildSlot);
|
kj::Promise<Result<WorkResult>> gaveUpOnSubstitution() noexcept;
|
||||||
WorkResult closureRepaired(bool inBuildSlot);
|
kj::Promise<Result<WorkResult>> closureRepaired() noexcept;
|
||||||
WorkResult inputsRealised(bool inBuildSlot);
|
kj::Promise<Result<WorkResult>> inputsRealised() noexcept;
|
||||||
WorkResult tryToBuild(bool inBuildSlot);
|
kj::Promise<Result<WorkResult>> tryToBuild() noexcept;
|
||||||
virtual WorkResult tryLocalBuild(bool inBuildSlot);
|
virtual kj::Promise<Result<WorkResult>> tryLocalBuild() noexcept;
|
||||||
WorkResult buildDone(bool inBuildSlot);
|
kj::Promise<Result<WorkResult>> buildDone() noexcept;
|
||||||
|
|
||||||
WorkResult resolvedFinished(bool inBuildSlot);
|
kj::Promise<Result<WorkResult>> resolvedFinished() noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is the build hook willing to perform the build?
|
* Is the build hook willing to perform the build?
|
||||||
*/
|
*/
|
||||||
HookReply tryBuildHook(bool inBuildSlot);
|
HookReply tryBuildHook();
|
||||||
|
|
||||||
virtual int getChildStatus();
|
virtual int getChildStatus();
|
||||||
|
|
||||||
|
@ -311,13 +321,19 @@ struct DerivationGoal : public Goal
|
||||||
virtual void cleanupPostOutputsRegisteredModeCheck();
|
virtual void cleanupPostOutputsRegisteredModeCheck();
|
||||||
virtual void cleanupPostOutputsRegisteredModeNonCheck();
|
virtual void cleanupPostOutputsRegisteredModeNonCheck();
|
||||||
|
|
||||||
/**
|
protected:
|
||||||
* Callback used by the worker to write to the log.
|
kj::TimePoint lastChildActivity = kj::minValue;
|
||||||
*/
|
|
||||||
WorkResult handleChildOutput(int fd, std::string_view data) override;
|
kj::Promise<Outcome<void, WorkResult>> handleChildOutput() noexcept;
|
||||||
void handleEOF(int fd) override;
|
kj::Promise<Outcome<void, WorkResult>>
|
||||||
|
handleChildStreams(InputStream & builderIn, InputStream * hookIn) noexcept;
|
||||||
|
kj::Promise<Outcome<void, WorkResult>> handleBuilderOutput(InputStream & in) noexcept;
|
||||||
|
kj::Promise<Outcome<void, WorkResult>> handleHookOutput(InputStream & in) noexcept;
|
||||||
|
kj::Promise<Outcome<void, WorkResult>> monitorForSilence() noexcept;
|
||||||
|
WorkResult tooMuchLogs();
|
||||||
void flushLine();
|
void flushLine();
|
||||||
|
|
||||||
|
public:
|
||||||
/**
|
/**
|
||||||
* Wrappers around the corresponding Store methods that first consult the
|
* Wrappers around the corresponding Store methods that first consult the
|
||||||
* derivation. This is currently needed because when there is no drv file
|
* derivation. This is currently needed because when there is no drv file
|
||||||
|
@ -345,17 +361,22 @@ struct DerivationGoal : public Goal
|
||||||
*/
|
*/
|
||||||
virtual void killChild();
|
virtual void killChild();
|
||||||
|
|
||||||
WorkResult repairClosure();
|
kj::Promise<Result<WorkResult>> repairClosure() noexcept;
|
||||||
|
|
||||||
void started();
|
void started();
|
||||||
|
|
||||||
Finished done(
|
WorkResult done(
|
||||||
BuildResult::Status status,
|
BuildResult::Status status,
|
||||||
SingleDrvOutputs builtOutputs = {},
|
SingleDrvOutputs builtOutputs = {},
|
||||||
std::optional<Error> ex = {});
|
std::optional<Error> ex = {});
|
||||||
|
|
||||||
void waiteeDone(GoalPtr waitee) override;
|
void waiteeDone(GoalPtr waitee) override;
|
||||||
|
|
||||||
|
virtual bool respectsTimeouts()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
StorePathSet exportReferences(const StorePathSet & storePaths);
|
StorePathSet exportReferences(const StorePathSet & storePaths);
|
||||||
|
|
||||||
JobCategory jobCategory() const override {
|
JobCategory jobCategory() const override {
|
||||||
|
|
|
@ -1,48 +1,53 @@
|
||||||
#include "drv-output-substitution-goal.hh"
|
#include "drv-output-substitution-goal.hh"
|
||||||
|
#include "build-result.hh"
|
||||||
#include "finally.hh"
|
#include "finally.hh"
|
||||||
#include "worker.hh"
|
#include "worker.hh"
|
||||||
#include "substitution-goal.hh"
|
#include "substitution-goal.hh"
|
||||||
#include "signals.hh"
|
#include "signals.hh"
|
||||||
|
#include <kj/array.h>
|
||||||
|
#include <kj/async.h>
|
||||||
|
#include <kj/vector.h>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
DrvOutputSubstitutionGoal::DrvOutputSubstitutionGoal(
|
DrvOutputSubstitutionGoal::DrvOutputSubstitutionGoal(
|
||||||
const DrvOutput & id,
|
const DrvOutput & id,
|
||||||
Worker & worker,
|
Worker & worker,
|
||||||
|
bool isDependency,
|
||||||
RepairFlag repair,
|
RepairFlag repair,
|
||||||
std::optional<ContentAddress> ca)
|
std::optional<ContentAddress> ca)
|
||||||
: Goal(worker)
|
: Goal(worker, isDependency)
|
||||||
, id(id)
|
, id(id)
|
||||||
{
|
{
|
||||||
state = &DrvOutputSubstitutionGoal::init;
|
|
||||||
name = fmt("substitution of '%s'", id.to_string());
|
name = fmt("substitution of '%s'", id.to_string());
|
||||||
trace("created");
|
trace("created");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Goal::WorkResult DrvOutputSubstitutionGoal::init(bool inBuildSlot)
|
kj::Promise<Result<Goal::WorkResult>> DrvOutputSubstitutionGoal::workImpl() noexcept
|
||||||
{
|
try {
|
||||||
trace("init");
|
trace("init");
|
||||||
|
|
||||||
/* If the derivation already exists, we’re done */
|
/* If the derivation already exists, we’re done */
|
||||||
if (worker.store.queryRealisation(id)) {
|
if (worker.store.queryRealisation(id)) {
|
||||||
return Finished{ecSuccess};
|
co_return WorkResult{ecSuccess};
|
||||||
}
|
}
|
||||||
|
|
||||||
subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
|
subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
|
||||||
return tryNext(inBuildSlot);
|
co_return co_await tryNext();
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::failure(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
Goal::WorkResult DrvOutputSubstitutionGoal::tryNext(bool inBuildSlot)
|
kj::Promise<Result<Goal::WorkResult>> DrvOutputSubstitutionGoal::tryNext() noexcept
|
||||||
{
|
try {
|
||||||
trace("trying next substituter");
|
trace("trying next substituter");
|
||||||
|
|
||||||
if (!inBuildSlot) {
|
if (!slotToken.valid()) {
|
||||||
return WaitForSlot{};
|
slotToken = co_await worker.substitutions.acquire();
|
||||||
}
|
}
|
||||||
|
|
||||||
maintainRunningSubstitutions =
|
maintainRunningSubstitutions = worker.runningSubstitutions.addTemporarily(1);
|
||||||
std::make_unique<MaintainCount<uint64_t>>(worker.runningSubstitutions);
|
|
||||||
|
|
||||||
if (subs.size() == 0) {
|
if (subs.size() == 0) {
|
||||||
/* None left. Terminate this goal and let someone else deal
|
/* None left. Terminate this goal and let someone else deal
|
||||||
|
@ -56,7 +61,7 @@ Goal::WorkResult DrvOutputSubstitutionGoal::tryNext(bool inBuildSlot)
|
||||||
/* Hack: don't indicate failure if there were no substituters.
|
/* Hack: don't indicate failure if there were no substituters.
|
||||||
In that case the calling derivation should just do a
|
In that case the calling derivation should just do a
|
||||||
build. */
|
build. */
|
||||||
return Finished{substituterFailed ? ecFailed : ecNoSubstituters};
|
co_return WorkResult{substituterFailed ? ecFailed : ecNoSubstituters};
|
||||||
}
|
}
|
||||||
|
|
||||||
sub = subs.front();
|
sub = subs.front();
|
||||||
|
@ -66,23 +71,26 @@ Goal::WorkResult DrvOutputSubstitutionGoal::tryNext(bool inBuildSlot)
|
||||||
some other error occurs), so it must not touch `this`. So put
|
some other error occurs), so it must not touch `this`. So put
|
||||||
the shared state in a separate refcounted object. */
|
the shared state in a separate refcounted object. */
|
||||||
downloadState = std::make_shared<DownloadState>();
|
downloadState = std::make_shared<DownloadState>();
|
||||||
downloadState->outPipe.create();
|
auto pipe = kj::newPromiseAndCrossThreadFulfiller<void>();
|
||||||
|
downloadState->outPipe = kj::mv(pipe.fulfiller);
|
||||||
|
|
||||||
downloadState->result =
|
downloadState->result =
|
||||||
std::async(std::launch::async, [downloadState{downloadState}, id{id}, sub{sub}] {
|
std::async(std::launch::async, [downloadState{downloadState}, id{id}, sub{sub}] {
|
||||||
|
Finally updateStats([&]() { downloadState->outPipe->fulfill(); });
|
||||||
ReceiveInterrupts receiveInterrupts;
|
ReceiveInterrupts receiveInterrupts;
|
||||||
Finally updateStats([&]() { downloadState->outPipe.writeSide.close(); });
|
|
||||||
return sub->queryRealisation(id);
|
return sub->queryRealisation(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
state = &DrvOutputSubstitutionGoal::realisationFetched;
|
co_await pipe.promise;
|
||||||
return WaitForWorld{{downloadState->outPipe.readSide.get()}, true};
|
co_return co_await realisationFetched();
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::failure(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
Goal::WorkResult DrvOutputSubstitutionGoal::realisationFetched(bool inBuildSlot)
|
kj::Promise<Result<Goal::WorkResult>> DrvOutputSubstitutionGoal::realisationFetched() noexcept
|
||||||
{
|
try {
|
||||||
worker.childTerminated(this);
|
|
||||||
maintainRunningSubstitutions.reset();
|
maintainRunningSubstitutions.reset();
|
||||||
|
slotToken = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
outputInfo = downloadState->result.get();
|
outputInfo = downloadState->result.get();
|
||||||
|
@ -92,10 +100,10 @@ Goal::WorkResult DrvOutputSubstitutionGoal::realisationFetched(bool inBuildSlot)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!outputInfo) {
|
if (!outputInfo) {
|
||||||
return tryNext(inBuildSlot);
|
co_return co_await tryNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
WaitForGoals result;
|
kj::Vector<std::pair<GoalPtr, kj::Promise<Result<WorkResult>>>> dependencies;
|
||||||
for (const auto & [depId, depPath] : outputInfo->dependentRealisations) {
|
for (const auto & [depId, depPath] : outputInfo->dependentRealisations) {
|
||||||
if (depId != id) {
|
if (depId != id) {
|
||||||
if (auto localOutputInfo = worker.store.queryRealisation(depId);
|
if (auto localOutputInfo = worker.store.queryRealisation(depId);
|
||||||
|
@ -109,55 +117,46 @@ Goal::WorkResult DrvOutputSubstitutionGoal::realisationFetched(bool inBuildSlot)
|
||||||
worker.store.printStorePath(localOutputInfo->outPath),
|
worker.store.printStorePath(localOutputInfo->outPath),
|
||||||
worker.store.printStorePath(depPath)
|
worker.store.printStorePath(depPath)
|
||||||
);
|
);
|
||||||
return tryNext(inBuildSlot);
|
co_return co_await tryNext();
|
||||||
}
|
}
|
||||||
result.goals.insert(worker.makeDrvOutputSubstitutionGoal(depId));
|
dependencies.add(worker.goalFactory().makeDrvOutputSubstitutionGoal(depId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.goals.insert(worker.makePathSubstitutionGoal(outputInfo->outPath));
|
dependencies.add(worker.goalFactory().makePathSubstitutionGoal(outputInfo->outPath));
|
||||||
|
|
||||||
if (result.goals.empty()) {
|
if (!dependencies.empty()) {
|
||||||
return outPathValid(inBuildSlot);
|
(co_await waitForGoals(dependencies.releaseAsArray())).value();
|
||||||
} else {
|
|
||||||
state = &DrvOutputSubstitutionGoal::outPathValid;
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
co_return co_await outPathValid();
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::failure(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
Goal::WorkResult DrvOutputSubstitutionGoal::outPathValid(bool inBuildSlot)
|
kj::Promise<Result<Goal::WorkResult>> DrvOutputSubstitutionGoal::outPathValid() noexcept
|
||||||
{
|
try {
|
||||||
assert(outputInfo);
|
assert(outputInfo);
|
||||||
trace("output path substituted");
|
trace("output path substituted");
|
||||||
|
|
||||||
if (nrFailed > 0) {
|
if (nrFailed > 0) {
|
||||||
debug("The output path of the derivation output '%s' could not be substituted", id.to_string());
|
debug("The output path of the derivation output '%s' could not be substituted", id.to_string());
|
||||||
return Finished{
|
return {WorkResult{
|
||||||
nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed
|
nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed,
|
||||||
};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.store.registerDrvOutput(*outputInfo);
|
worker.store.registerDrvOutput(*outputInfo);
|
||||||
return finished();
|
return finished();
|
||||||
|
} catch (...) {
|
||||||
|
return {std::current_exception()};
|
||||||
}
|
}
|
||||||
|
|
||||||
Goal::WorkResult DrvOutputSubstitutionGoal::finished()
|
kj::Promise<Result<Goal::WorkResult>> DrvOutputSubstitutionGoal::finished() noexcept
|
||||||
{
|
try {
|
||||||
trace("finished");
|
trace("finished");
|
||||||
return Finished{ecSuccess};
|
return {WorkResult{ecSuccess}};
|
||||||
|
} catch (...) {
|
||||||
|
return {std::current_exception()};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string DrvOutputSubstitutionGoal::key()
|
|
||||||
{
|
|
||||||
/* "a$" ensures substitution goals happen before derivation
|
|
||||||
goals. */
|
|
||||||
return "a$" + std::string(id.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
Goal::WorkResult DrvOutputSubstitutionGoal::work(bool inBuildSlot)
|
|
||||||
{
|
|
||||||
return (this->*state)(inBuildSlot);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
|
#include "notifying-counter.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "goal.hh"
|
#include "goal.hh"
|
||||||
#include "realisation.hh"
|
#include "realisation.hh"
|
||||||
|
@ -40,11 +41,11 @@ class DrvOutputSubstitutionGoal : public Goal {
|
||||||
*/
|
*/
|
||||||
std::shared_ptr<Store> sub;
|
std::shared_ptr<Store> sub;
|
||||||
|
|
||||||
std::unique_ptr<MaintainCount<uint64_t>> maintainRunningSubstitutions;
|
NotifyingCounter<uint64_t>::Bump maintainRunningSubstitutions;
|
||||||
|
|
||||||
struct DownloadState
|
struct DownloadState
|
||||||
{
|
{
|
||||||
Pipe outPipe;
|
kj::Own<kj::CrossThreadPromiseFulfiller<void>> outPipe;
|
||||||
std::future<std::shared_ptr<const Realisation>> result;
|
std::future<std::shared_ptr<const Realisation>> result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -56,22 +57,20 @@ class DrvOutputSubstitutionGoal : public Goal {
|
||||||
bool substituterFailed = false;
|
bool substituterFailed = false;
|
||||||
|
|
||||||
public:
|
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)(bool inBuildSlot);
|
kj::Promise<Result<WorkResult>> tryNext() noexcept;
|
||||||
GoalState state;
|
kj::Promise<Result<WorkResult>> realisationFetched() noexcept;
|
||||||
|
kj::Promise<Result<WorkResult>> outPathValid() noexcept;
|
||||||
|
kj::Promise<Result<WorkResult>> finished() noexcept;
|
||||||
|
|
||||||
WorkResult init(bool inBuildSlot);
|
kj::Promise<Result<WorkResult>> workImpl() noexcept override;
|
||||||
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(bool inBuildSlot) override;
|
|
||||||
|
|
||||||
JobCategory jobCategory() const override {
|
JobCategory jobCategory() const override {
|
||||||
return JobCategory::Substitution;
|
return JobCategory::Substitution;
|
||||||
|
|
|
@ -2,29 +2,37 @@
|
||||||
#include "substitution-goal.hh"
|
#include "substitution-goal.hh"
|
||||||
#include "derivation-goal.hh"
|
#include "derivation-goal.hh"
|
||||||
#include "local-store.hh"
|
#include "local-store.hh"
|
||||||
|
#include "strings.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
static auto runWorker(Worker & worker, auto mkGoals)
|
||||||
|
{
|
||||||
|
return worker.run(mkGoals);
|
||||||
|
}
|
||||||
|
|
||||||
void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMode, std::shared_ptr<Store> evalStore)
|
void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMode, std::shared_ptr<Store> evalStore)
|
||||||
{
|
{
|
||||||
Worker worker(*this, evalStore ? *evalStore : *this);
|
auto aio = kj::setupAsyncIo();
|
||||||
|
Worker worker(*this, evalStore ? *evalStore : *this, aio);
|
||||||
|
|
||||||
Goals goals;
|
auto goals = runWorker(worker, [&](GoalFactory & gf) {
|
||||||
|
Worker::Targets goals;
|
||||||
for (auto & br : reqs)
|
for (auto & br : reqs)
|
||||||
goals.insert(worker.makeGoal(br, buildMode));
|
goals.emplace(gf.makeGoal(br, buildMode));
|
||||||
|
return goals;
|
||||||
worker.run(goals);
|
});
|
||||||
|
|
||||||
StringSet failed;
|
StringSet failed;
|
||||||
std::shared_ptr<Error> ex;
|
std::shared_ptr<Error> ex;
|
||||||
for (auto & i : goals) {
|
for (auto & [i, result] : goals) {
|
||||||
if (i->ex) {
|
if (result.ex) {
|
||||||
if (ex)
|
if (ex)
|
||||||
logError(i->ex->info());
|
logError(result.ex->info());
|
||||||
else
|
else
|
||||||
ex = i->ex;
|
ex = result.ex;
|
||||||
}
|
}
|
||||||
if (i->exitCode != Goal::ecSuccess) {
|
if (result.exitCode != Goal::ecSuccess) {
|
||||||
if (auto i2 = dynamic_cast<DerivationGoal *>(i.get()))
|
if (auto i2 = dynamic_cast<DerivationGoal *>(i.get()))
|
||||||
failed.insert(printStorePath(i2->drvPath));
|
failed.insert(printStorePath(i2->drvPath));
|
||||||
else if (auto i2 = dynamic_cast<PathSubstitutionGoal *>(i.get()))
|
else if (auto i2 = dynamic_cast<PathSubstitutionGoal *>(i.get()))
|
||||||
|
@ -46,23 +54,25 @@ std::vector<KeyedBuildResult> Store::buildPathsWithResults(
|
||||||
BuildMode buildMode,
|
BuildMode buildMode,
|
||||||
std::shared_ptr<Store> evalStore)
|
std::shared_ptr<Store> evalStore)
|
||||||
{
|
{
|
||||||
Worker worker(*this, evalStore ? *evalStore : *this);
|
auto aio = kj::setupAsyncIo();
|
||||||
|
Worker worker(*this, evalStore ? *evalStore : *this, aio);
|
||||||
|
|
||||||
Goals goals;
|
|
||||||
std::vector<std::pair<const DerivedPath &, GoalPtr>> state;
|
std::vector<std::pair<const DerivedPath &, GoalPtr>> state;
|
||||||
|
|
||||||
|
auto goals = runWorker(worker, [&](GoalFactory & gf) {
|
||||||
|
Worker::Targets goals;
|
||||||
for (const auto & req : reqs) {
|
for (const auto & req : reqs) {
|
||||||
auto goal = worker.makeGoal(req, buildMode);
|
auto goal = gf.makeGoal(req, buildMode);
|
||||||
goals.insert(goal);
|
state.push_back({req, goal.first});
|
||||||
state.push_back({req, goal});
|
goals.emplace(std::move(goal));
|
||||||
}
|
}
|
||||||
|
return goals;
|
||||||
worker.run(goals);
|
});
|
||||||
|
|
||||||
std::vector<KeyedBuildResult> results;
|
std::vector<KeyedBuildResult> results;
|
||||||
|
|
||||||
for (auto & [req, goalPtr] : state)
|
for (auto & [req, goalPtr] : state)
|
||||||
results.emplace_back(goalPtr->buildResult.restrictTo(req));
|
results.emplace_back(goals[goalPtr].result.restrictTo(req));
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
@ -70,12 +80,17 @@ std::vector<KeyedBuildResult> Store::buildPathsWithResults(
|
||||||
BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
||||||
BuildMode buildMode)
|
BuildMode buildMode)
|
||||||
{
|
{
|
||||||
Worker worker(*this, *this);
|
auto aio = kj::setupAsyncIo();
|
||||||
auto goal = worker.makeBasicDerivationGoal(drvPath, drv, OutputsSpec::All {}, buildMode);
|
Worker worker(*this, *this, aio);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
worker.run(Goals{goal});
|
auto goals = runWorker(worker, [&](GoalFactory & gf) {
|
||||||
return goal->buildResult.restrictTo(DerivedPath::Built {
|
Worker::Targets goals;
|
||||||
|
goals.emplace(gf.makeBasicDerivationGoal(drvPath, drv, OutputsSpec::All{}, buildMode));
|
||||||
|
return goals;
|
||||||
|
});
|
||||||
|
auto [goal, result] = *goals.begin();
|
||||||
|
return result.result.restrictTo(DerivedPath::Built {
|
||||||
.drvPath = makeConstantStorePathRef(drvPath),
|
.drvPath = makeConstantStorePathRef(drvPath),
|
||||||
.outputs = OutputsSpec::All {},
|
.outputs = OutputsSpec::All {},
|
||||||
});
|
});
|
||||||
|
@ -93,16 +108,20 @@ void Store::ensurePath(const StorePath & path)
|
||||||
/* If the path is already valid, we're done. */
|
/* If the path is already valid, we're done. */
|
||||||
if (isValidPath(path)) return;
|
if (isValidPath(path)) return;
|
||||||
|
|
||||||
Worker worker(*this, *this);
|
auto aio = kj::setupAsyncIo();
|
||||||
GoalPtr goal = worker.makePathSubstitutionGoal(path);
|
Worker worker(*this, *this, aio);
|
||||||
Goals goals = {goal};
|
|
||||||
|
|
||||||
worker.run(goals);
|
auto goals = runWorker(worker, [&](GoalFactory & gf) {
|
||||||
|
Worker::Targets goals;
|
||||||
|
goals.emplace(gf.makePathSubstitutionGoal(path));
|
||||||
|
return goals;
|
||||||
|
});
|
||||||
|
auto [goal, result] = *goals.begin();
|
||||||
|
|
||||||
if (goal->exitCode != Goal::ecSuccess) {
|
if (result.exitCode != Goal::ecSuccess) {
|
||||||
if (goal->ex) {
|
if (result.ex) {
|
||||||
goal->ex->withExitStatus(worker.failingExitStatus());
|
result.ex->withExitStatus(worker.failingExitStatus());
|
||||||
throw std::move(*goal->ex);
|
throw std::move(*result.ex);
|
||||||
} else
|
} else
|
||||||
throw Error(worker.failingExitStatus(), "path '%s' does not exist and cannot be created", printStorePath(path));
|
throw Error(worker.failingExitStatus(), "path '%s' does not exist and cannot be created", printStorePath(path));
|
||||||
}
|
}
|
||||||
|
@ -111,24 +130,33 @@ void Store::ensurePath(const StorePath & path)
|
||||||
|
|
||||||
void Store::repairPath(const StorePath & path)
|
void Store::repairPath(const StorePath & path)
|
||||||
{
|
{
|
||||||
Worker worker(*this, *this);
|
auto aio = kj::setupAsyncIo();
|
||||||
GoalPtr goal = worker.makePathSubstitutionGoal(path, Repair);
|
Worker worker(*this, *this, aio);
|
||||||
Goals goals = {goal};
|
|
||||||
|
|
||||||
worker.run(goals);
|
auto goals = runWorker(worker, [&](GoalFactory & gf) {
|
||||||
|
Worker::Targets goals;
|
||||||
|
goals.emplace(gf.makePathSubstitutionGoal(path, Repair));
|
||||||
|
return goals;
|
||||||
|
});
|
||||||
|
auto [goal, result] = *goals.begin();
|
||||||
|
|
||||||
if (goal->exitCode != Goal::ecSuccess) {
|
if (result.exitCode != Goal::ecSuccess) {
|
||||||
/* Since substituting the path didn't work, if we have a valid
|
/* Since substituting the path didn't work, if we have a valid
|
||||||
deriver, then rebuild the deriver. */
|
deriver, then rebuild the deriver. */
|
||||||
auto info = queryPathInfo(path);
|
auto info = queryPathInfo(path);
|
||||||
if (info->deriver && isValidPath(*info->deriver)) {
|
if (info->deriver && isValidPath(*info->deriver)) {
|
||||||
goals.clear();
|
worker.run([&](GoalFactory & gf) {
|
||||||
goals.insert(worker.makeGoal(DerivedPath::Built {
|
Worker::Targets goals;
|
||||||
|
goals.emplace(gf.makeGoal(
|
||||||
|
DerivedPath::Built{
|
||||||
.drvPath = makeConstantStorePathRef(*info->deriver),
|
.drvPath = makeConstantStorePathRef(*info->deriver),
|
||||||
// FIXME: Should just build the specific output we need.
|
// FIXME: Should just build the specific output we need.
|
||||||
.outputs = OutputsSpec::All{},
|
.outputs = OutputsSpec::All{},
|
||||||
}, bmRepair));
|
},
|
||||||
worker.run(goals);
|
bmRepair
|
||||||
|
));
|
||||||
|
return goals;
|
||||||
|
});
|
||||||
} else
|
} else
|
||||||
throw Error(worker.failingExitStatus(), "cannot repair path '%s'", printStorePath(path));
|
throw Error(worker.failingExitStatus(), "cannot repair path '%s'", printStorePath(path));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,73 @@
|
||||||
#include "goal.hh"
|
#include "goal.hh"
|
||||||
|
#include "async-collect.hh"
|
||||||
|
#include "worker.hh"
|
||||||
|
#include <boost/outcome/try.hpp>
|
||||||
|
#include <kj/time.h>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
bool CompareGoalPtrs::operator() (const GoalPtr & a, const GoalPtr & b) const {
|
|
||||||
std::string s1 = a->key();
|
|
||||||
std::string s2 = b->key();
|
|
||||||
return s1 < s2;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Goal::trace(std::string_view s)
|
void Goal::trace(std::string_view s)
|
||||||
{
|
{
|
||||||
debug("%1%: %2%", name, s);
|
debug("%1%: %2%", name, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kj::Promise<void> Goal::waitForAWhile()
|
||||||
|
{
|
||||||
|
trace("wait for a while");
|
||||||
|
/* If we are polling goals that are waiting for a lock, then wake
|
||||||
|
up after a few seconds at most. */
|
||||||
|
return worker.aio.provider->getTimer().afterDelay(settings.pollInterval.get() * kj::SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<Result<Goal::WorkResult>> Goal::work() noexcept
|
||||||
|
try {
|
||||||
|
BOOST_OUTCOME_CO_TRY(auto result, co_await workImpl());
|
||||||
|
|
||||||
|
trace("done");
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
|
||||||
|
co_return std::move(result);
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::failure(std::current_exception());
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<Result<void>>
|
||||||
|
Goal::waitForGoals(kj::Array<std::pair<GoalPtr, kj::Promise<Result<WorkResult>>>> dependencies) noexcept
|
||||||
|
try {
|
||||||
|
auto left = dependencies.size();
|
||||||
|
for (auto & [dep, p] : dependencies) {
|
||||||
|
p = p.then([this, dep, &left](auto _result) -> Result<WorkResult> {
|
||||||
|
BOOST_OUTCOME_TRY(auto result, _result);
|
||||||
|
|
||||||
|
left--;
|
||||||
|
trace(fmt("waitee '%s' done; %d left", dep->name, left));
|
||||||
|
|
||||||
|
if (result.exitCode != Goal::ecSuccess) ++nrFailed;
|
||||||
|
if (result.exitCode == Goal::ecNoSubstituters) ++nrNoSubstituters;
|
||||||
|
if (result.exitCode == Goal::ecIncompleteClosure) ++nrIncompleteClosure;
|
||||||
|
|
||||||
|
return std::move(result);
|
||||||
|
}).eagerlyEvaluate(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto collectDeps = asyncCollect(std::move(dependencies));
|
||||||
|
|
||||||
|
while (auto item = co_await collectDeps.next()) {
|
||||||
|
auto & [dep, _result] = *item;
|
||||||
|
BOOST_OUTCOME_CO_TRY(auto result, _result);
|
||||||
|
|
||||||
|
waiteeDone(dep);
|
||||||
|
|
||||||
|
if (result.exitCode == ecFailed && !settings.keepGoing) {
|
||||||
|
co_return result::success();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
co_return result::success();
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::failure(std::current_exception());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
|
#include "async-semaphore.hh"
|
||||||
|
#include "result.hh"
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "build-result.hh"
|
#include "build-result.hh"
|
||||||
|
#include <concepts> // IWYU pragma: keep
|
||||||
|
#include <kj/async.h>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -17,22 +21,11 @@ class Worker;
|
||||||
* A pointer to a goal.
|
* A pointer to a goal.
|
||||||
*/
|
*/
|
||||||
typedef std::shared_ptr<Goal> GoalPtr;
|
typedef std::shared_ptr<Goal> GoalPtr;
|
||||||
typedef std::weak_ptr<Goal> WeakGoalPtr;
|
|
||||||
|
|
||||||
struct CompareGoalPtrs {
|
|
||||||
bool operator() (const GoalPtr & a, const GoalPtr & b) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set of goals.
|
* Set of goals.
|
||||||
*/
|
*/
|
||||||
typedef std::set<GoalPtr, CompareGoalPtrs> Goals;
|
typedef std::set<GoalPtr> Goals;
|
||||||
typedef std::set<WeakGoalPtr, std::owner_less<WeakGoalPtr>> WeakGoals;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A map of paths to goals (and the other way around).
|
|
||||||
*/
|
|
||||||
typedef std::map<StorePath, WeakGoalPtr> WeakGoalMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used as a hint to the worker on how to schedule a particular goal. For example,
|
* Used as a hint to the worker on how to schedule a particular goal. For example,
|
||||||
|
@ -61,15 +54,11 @@ struct Goal
|
||||||
Worker & worker;
|
Worker & worker;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Goals that this goal is waiting for.
|
* 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.
|
||||||
*/
|
*/
|
||||||
Goals waitees;
|
const bool isDependency;
|
||||||
|
|
||||||
/**
|
|
||||||
* Goals waiting for this one to finish. Must use weak pointers
|
|
||||||
* here to prevent cycles.
|
|
||||||
*/
|
|
||||||
WeakGoals waiters;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of goals we are/were waiting for that have failed.
|
* Number of goals we are/were waiting for that have failed.
|
||||||
|
@ -93,58 +82,40 @@ struct Goal
|
||||||
*/
|
*/
|
||||||
std::string name;
|
std::string name;
|
||||||
|
|
||||||
/**
|
protected:
|
||||||
* Whether the goal is finished.
|
AsyncSemaphore::Token slotToken;
|
||||||
*/
|
|
||||||
std::optional<ExitCode> exitCode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build result.
|
|
||||||
*/
|
|
||||||
BuildResult buildResult;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
struct [[nodiscard]] WorkResult {
|
||||||
struct [[nodiscard]] StillAlive {};
|
ExitCode exitCode;
|
||||||
struct [[nodiscard]] WaitForSlot {};
|
BuildResult result = {};
|
||||||
struct [[nodiscard]] WaitForAWhile {};
|
std::shared_ptr<Error> ex = {};
|
||||||
struct [[nodiscard]] ContinueImmediately {};
|
|
||||||
struct [[nodiscard]] WaitForGoals {
|
|
||||||
Goals goals;
|
|
||||||
};
|
|
||||||
struct [[nodiscard]] WaitForWorld {
|
|
||||||
std::set<int> fds;
|
|
||||||
bool inBuildSlot;
|
|
||||||
};
|
|
||||||
struct [[nodiscard]] Finished {
|
|
||||||
ExitCode result;
|
|
||||||
std::shared_ptr<Error> ex;
|
|
||||||
bool permanentFailure = false;
|
bool permanentFailure = false;
|
||||||
bool timedOut = false;
|
bool timedOut = false;
|
||||||
bool hashMismatch = false;
|
bool hashMismatch = false;
|
||||||
bool checkMismatch = false;
|
bool checkMismatch = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct [[nodiscard]] WorkResult : std::variant<
|
protected:
|
||||||
StillAlive,
|
kj::Promise<void> waitForAWhile();
|
||||||
WaitForSlot,
|
kj::Promise<Result<void>>
|
||||||
WaitForAWhile,
|
waitForGoals(kj::Array<std::pair<GoalPtr, kj::Promise<Result<WorkResult>>>> dependencies) noexcept;
|
||||||
ContinueImmediately,
|
|
||||||
WaitForGoals,
|
template<std::derived_from<Goal>... G>
|
||||||
WaitForWorld,
|
kj::Promise<Result<void>>
|
||||||
Finished>
|
waitForGoals(std::pair<std::shared_ptr<G>, kj::Promise<Result<WorkResult>>>... goals) noexcept
|
||||||
{
|
{
|
||||||
WorkResult() = delete;
|
return waitForGoals(
|
||||||
using variant::variant;
|
kj::arrOf<std::pair<GoalPtr, kj::Promise<Result<WorkResult>>>>(std::move(goals)...)
|
||||||
};
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
virtual kj::Promise<Result<WorkResult>> workImpl() noexcept = 0;
|
||||||
* Exception containing an error message, if any.
|
|
||||||
*/
|
|
||||||
std::shared_ptr<Error> ex;
|
|
||||||
|
|
||||||
explicit Goal(Worker & worker)
|
public:
|
||||||
|
explicit Goal(Worker & worker, bool isDependency)
|
||||||
: worker(worker)
|
: worker(worker)
|
||||||
|
, isDependency(isDependency)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
virtual ~Goal() noexcept(false)
|
virtual ~Goal() noexcept(false)
|
||||||
|
@ -152,24 +123,10 @@ public:
|
||||||
trace("goal destroyed");
|
trace("goal destroyed");
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual WorkResult work(bool inBuildSlot) = 0;
|
kj::Promise<Result<WorkResult>> work() noexcept;
|
||||||
|
|
||||||
virtual void waiteeDone(GoalPtr waitee) { }
|
virtual void waiteeDone(GoalPtr waitee) { }
|
||||||
|
|
||||||
virtual WorkResult handleChildOutput(int fd, std::string_view data)
|
|
||||||
{
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void handleEOF(int fd)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool respectsTimeouts()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void trace(std::string_view s);
|
void trace(std::string_view s);
|
||||||
|
|
||||||
std::string getName() const
|
std::string getName() const
|
||||||
|
@ -177,15 +134,6 @@ public:
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback in case of a timeout. It should wake up its waiters,
|
|
||||||
* get rid of any running child processes that are being monitored
|
|
||||||
* by the worker (important!), etc.
|
|
||||||
*/
|
|
||||||
virtual Finished timedOut(Error && ex) = 0;
|
|
||||||
|
|
||||||
virtual std::string key() = 0;
|
|
||||||
|
|
||||||
virtual void cleanup() { }
|
virtual void cleanup() { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
#include "child.hh"
|
#include "child.hh"
|
||||||
|
#include "error.hh"
|
||||||
#include "file-system.hh"
|
#include "file-system.hh"
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
#include "hook-instance.hh"
|
#include "hook-instance.hh"
|
||||||
|
#include "strings.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -85,7 +87,7 @@ HookInstance::~HookInstance()
|
||||||
toHook.reset();
|
toHook.reset();
|
||||||
if (pid) pid.kill();
|
if (pid) pid.kill();
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
ignoreException();
|
ignoreExceptionInDestructor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
#include "local-derivation-goal.hh"
|
#include "local-derivation-goal.hh"
|
||||||
|
#include "error.hh"
|
||||||
#include "indirect-root-store.hh"
|
#include "indirect-root-store.hh"
|
||||||
#include "hook-instance.hh"
|
|
||||||
#include "machines.hh"
|
#include "machines.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "worker.hh"
|
#include "worker.hh"
|
||||||
#include "builtins.hh"
|
#include "builtins.hh"
|
||||||
#include "builtins/buildenv.hh"
|
#include "builtins/buildenv.hh"
|
||||||
#include "path-references.hh"
|
#include "path-references.hh"
|
||||||
#include "finally.hh"
|
|
||||||
#include "archive.hh"
|
#include "archive.hh"
|
||||||
#include "compression.hh"
|
|
||||||
#include "daemon.hh"
|
#include "daemon.hh"
|
||||||
#include "topo-sort.hh"
|
#include "topo-sort.hh"
|
||||||
#include "json-utils.hh"
|
#include "json-utils.hh"
|
||||||
|
@ -19,6 +17,7 @@
|
||||||
#include "child.hh"
|
#include "child.hh"
|
||||||
#include "unix-domain-socket.hh"
|
#include "unix-domain-socket.hh"
|
||||||
#include "mount.hh"
|
#include "mount.hh"
|
||||||
|
#include "strings.hh"
|
||||||
|
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
@ -100,9 +99,9 @@ LocalDerivationGoal::~LocalDerivationGoal() noexcept(false)
|
||||||
{
|
{
|
||||||
/* Careful: we should never ever throw an exception from a
|
/* Careful: we should never ever throw an exception from a
|
||||||
destructor. */
|
destructor. */
|
||||||
try { deleteTmpDir(false); } catch (...) { ignoreException(); }
|
try { deleteTmpDir(false); } catch (...) { ignoreExceptionInDestructor(); }
|
||||||
try { killChild(); } catch (...) { ignoreException(); }
|
try { killChild(); } catch (...) { ignoreExceptionInDestructor(); }
|
||||||
try { stopDaemon(); } catch (...) { ignoreException(); }
|
try { stopDaemon(); } catch (...) { ignoreExceptionInDestructor(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,8 +122,6 @@ LocalStore & LocalDerivationGoal::getLocalStore()
|
||||||
void LocalDerivationGoal::killChild()
|
void LocalDerivationGoal::killChild()
|
||||||
{
|
{
|
||||||
if (pid) {
|
if (pid) {
|
||||||
worker.childTerminated(this);
|
|
||||||
|
|
||||||
/* If we're using a build user, then there is a tricky race
|
/* If we're using a build user, then there is a tricky race
|
||||||
condition: if we kill the build user before the child has
|
condition: if we kill the build user before the child has
|
||||||
done its setuid() to the build user uid, then it won't be
|
done its setuid() to the build user uid, then it won't be
|
||||||
|
@ -151,17 +148,18 @@ void LocalDerivationGoal::killSandbox(bool getStats)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Goal::WorkResult LocalDerivationGoal::tryLocalBuild(bool inBuildSlot)
|
kj::Promise<Result<Goal::WorkResult>> LocalDerivationGoal::tryLocalBuild() noexcept
|
||||||
{
|
try {
|
||||||
|
retry:
|
||||||
#if __APPLE__
|
#if __APPLE__
|
||||||
additionalSandboxProfile = parsedDrv->getStringAttr("__sandboxProfile").value_or("");
|
additionalSandboxProfile = parsedDrv->getStringAttr("__sandboxProfile").value_or("");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!inBuildSlot) {
|
if (!slotToken.valid()) {
|
||||||
state = &DerivationGoal::tryToBuild;
|
|
||||||
outputLocks.unlock();
|
outputLocks.unlock();
|
||||||
if (0U != settings.maxBuildJobs) {
|
if (worker.localBuilds.capacity() > 0) {
|
||||||
return WaitForSlot{};
|
slotToken = co_await worker.localBuilds.acquire();
|
||||||
|
co_return co_await tryToBuild();
|
||||||
}
|
}
|
||||||
if (getMachines().empty()) {
|
if (getMachines().empty()) {
|
||||||
throw Error(
|
throw Error(
|
||||||
|
@ -216,7 +214,9 @@ Goal::WorkResult LocalDerivationGoal::tryLocalBuild(bool inBuildSlot)
|
||||||
if (!actLock)
|
if (!actLock)
|
||||||
actLock = std::make_unique<Activity>(*logger, lvlWarn, actBuildWaiting,
|
actLock = std::make_unique<Activity>(*logger, lvlWarn, actBuildWaiting,
|
||||||
fmt("waiting for a free build user ID for '%s'", Magenta(worker.store.printStorePath(drvPath))));
|
fmt("waiting for a free build user ID for '%s'", Magenta(worker.store.printStorePath(drvPath))));
|
||||||
return WaitForAWhile{};
|
co_await waitForAWhile();
|
||||||
|
// we can loop very often, and `co_return co_await` always allocates a new frame
|
||||||
|
goto retry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,22 +245,29 @@ Goal::WorkResult LocalDerivationGoal::tryLocalBuild(bool inBuildSlot)
|
||||||
try {
|
try {
|
||||||
|
|
||||||
/* Okay, we have to build. */
|
/* Okay, we have to build. */
|
||||||
auto fds = startBuilder();
|
auto promise = startBuilder();
|
||||||
|
|
||||||
/* This state will be reached when we get EOF on the child's
|
|
||||||
log pipe. */
|
|
||||||
state = &DerivationGoal::buildDone;
|
|
||||||
|
|
||||||
started();
|
started();
|
||||||
return WaitForWorld{std::move(fds), true};
|
auto r = co_await promise;
|
||||||
|
if (r.has_value()) {
|
||||||
|
// all good so far
|
||||||
|
} else if (r.has_error()) {
|
||||||
|
co_return r.assume_error();
|
||||||
|
} else {
|
||||||
|
co_return r.assume_exception();
|
||||||
|
}
|
||||||
|
|
||||||
} catch (BuildError & e) {
|
} catch (BuildError & e) {
|
||||||
outputLocks.unlock();
|
outputLocks.unlock();
|
||||||
buildUser.reset();
|
buildUser.reset();
|
||||||
auto report = done(BuildResult::InputRejected, {}, std::move(e));
|
auto report = done(BuildResult::InputRejected, {}, std::move(e));
|
||||||
report.permanentFailure = true;
|
report.permanentFailure = true;
|
||||||
return report;
|
co_return report;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
co_return co_await buildDone();
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::failure(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -390,7 +397,9 @@ void LocalDerivationGoal::cleanupPostOutputsRegisteredModeNonCheck()
|
||||||
cleanupPostOutputsRegisteredModeCheck();
|
cleanupPostOutputsRegisteredModeCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::set<int> LocalDerivationGoal::startBuilder()
|
// NOTE this one isn't noexcept because it's called from places that expect
|
||||||
|
// exceptions to signal failure to launch. we should change this some time.
|
||||||
|
kj::Promise<Outcome<void, Goal::WorkResult>> LocalDerivationGoal::startBuilder()
|
||||||
{
|
{
|
||||||
if ((buildUser && buildUser->getUIDCount() != 1)
|
if ((buildUser && buildUser->getUIDCount() != 1)
|
||||||
#if __linux__
|
#if __linux__
|
||||||
|
@ -779,7 +788,7 @@ std::set<int> LocalDerivationGoal::startBuilder()
|
||||||
msgs.push_back(std::move(msg));
|
msgs.push_back(std::move(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {builderOutPTY.get()};
|
return handleChildOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1241,7 +1250,7 @@ void LocalDerivationGoal::startDaemon()
|
||||||
NotTrusted, daemon::Recursive);
|
NotTrusted, daemon::Recursive);
|
||||||
debug("terminated daemon connection");
|
debug("terminated daemon connection");
|
||||||
} catch (SysError &) {
|
} catch (SysError &) {
|
||||||
ignoreException();
|
ignoreExceptionExceptInterrupt();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1361,14 +1370,21 @@ void LocalDerivationGoal::runChild()
|
||||||
|
|
||||||
bool setUser = true;
|
bool setUser = true;
|
||||||
|
|
||||||
/* Make the contents of netrc available to builtin:fetchurl
|
/* Make the contents of netrc and the CA certificate bundle
|
||||||
(which may run under a different uid and/or in a sandbox). */
|
available to builtin:fetchurl (which may run under a
|
||||||
|
different uid and/or in a sandbox). */
|
||||||
std::string netrcData;
|
std::string netrcData;
|
||||||
|
std::string caFileData;
|
||||||
|
if (drv->isBuiltin() && drv->builder == "builtin:fetchurl" && !derivationType->isSandboxed()) {
|
||||||
try {
|
try {
|
||||||
if (drv->isBuiltin() && drv->builder == "builtin:fetchurl" && !derivationType->isSandboxed())
|
|
||||||
netrcData = readFile(settings.netrcFile);
|
netrcData = readFile(settings.netrcFile);
|
||||||
} catch (SysError &) { }
|
} catch (SysError &) { }
|
||||||
|
|
||||||
|
try {
|
||||||
|
caFileData = readFile(settings.caFile);
|
||||||
|
} catch (SysError &) { }
|
||||||
|
}
|
||||||
|
|
||||||
#if __linux__
|
#if __linux__
|
||||||
if (useChroot) {
|
if (useChroot) {
|
||||||
|
|
||||||
|
@ -1802,7 +1818,7 @@ void LocalDerivationGoal::runChild()
|
||||||
e.second = rewriteStrings(e.second, inputRewrites);
|
e.second = rewriteStrings(e.second, inputRewrites);
|
||||||
|
|
||||||
if (drv->builder == "builtin:fetchurl")
|
if (drv->builder == "builtin:fetchurl")
|
||||||
builtinFetchurl(drv2, netrcData);
|
builtinFetchurl(drv2, netrcData, caFileData);
|
||||||
else if (drv->builder == "builtin:buildenv")
|
else if (drv->builder == "builtin:buildenv")
|
||||||
builtinBuildenv(drv2);
|
builtinBuildenv(drv2);
|
||||||
else if (drv->builder == "builtin:unpack-channel")
|
else if (drv->builder == "builtin:unpack-channel")
|
||||||
|
|
|
@ -182,10 +182,11 @@ struct LocalDerivationGoal : public DerivationGoal
|
||||||
* Create a LocalDerivationGoal without an on-disk .drv file,
|
* Create a LocalDerivationGoal without an on-disk .drv file,
|
||||||
* possibly a platform-specific subclass
|
* possibly a platform-specific subclass
|
||||||
*/
|
*/
|
||||||
static std::shared_ptr<LocalDerivationGoal> makeLocalDerivationGoal(
|
static std::unique_ptr<LocalDerivationGoal> makeLocalDerivationGoal(
|
||||||
const StorePath & drvPath,
|
const StorePath & drvPath,
|
||||||
const OutputsSpec & wantedOutputs,
|
const OutputsSpec & wantedOutputs,
|
||||||
Worker & worker,
|
Worker & worker,
|
||||||
|
bool isDependency,
|
||||||
BuildMode buildMode
|
BuildMode buildMode
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -193,11 +194,12 @@ struct LocalDerivationGoal : public DerivationGoal
|
||||||
* Create a LocalDerivationGoal for an on-disk .drv file,
|
* Create a LocalDerivationGoal for an on-disk .drv file,
|
||||||
* possibly a platform-specific subclass
|
* possibly a platform-specific subclass
|
||||||
*/
|
*/
|
||||||
static std::shared_ptr<LocalDerivationGoal> makeLocalDerivationGoal(
|
static std::unique_ptr<LocalDerivationGoal> makeLocalDerivationGoal(
|
||||||
const StorePath & drvPath,
|
const StorePath & drvPath,
|
||||||
const BasicDerivation & drv,
|
const BasicDerivation & drv,
|
||||||
const OutputsSpec & wantedOutputs,
|
const OutputsSpec & wantedOutputs,
|
||||||
Worker & worker,
|
Worker & worker,
|
||||||
|
bool isDependency,
|
||||||
BuildMode buildMode
|
BuildMode buildMode
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -211,12 +213,12 @@ struct LocalDerivationGoal : public DerivationGoal
|
||||||
/**
|
/**
|
||||||
* The additional states.
|
* The additional states.
|
||||||
*/
|
*/
|
||||||
WorkResult tryLocalBuild(bool inBuildSlot) override;
|
kj::Promise<Result<WorkResult>> tryLocalBuild() noexcept override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start building a derivation.
|
* Start building a derivation.
|
||||||
*/
|
*/
|
||||||
std::set<int> startBuilder();
|
kj::Promise<Outcome<void, WorkResult>> startBuilder();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fill in the environment for the builder.
|
* Fill in the environment for the builder.
|
||||||
|
|
|
@ -3,19 +3,26 @@
|
||||||
#include "nar-info.hh"
|
#include "nar-info.hh"
|
||||||
#include "signals.hh"
|
#include "signals.hh"
|
||||||
#include "finally.hh"
|
#include "finally.hh"
|
||||||
|
#include <kj/array.h>
|
||||||
|
#include <kj/vector.h>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
PathSubstitutionGoal::PathSubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair, std::optional<ContentAddress> ca)
|
PathSubstitutionGoal::PathSubstitutionGoal(
|
||||||
: Goal(worker)
|
const StorePath & storePath,
|
||||||
|
Worker & worker,
|
||||||
|
bool isDependency,
|
||||||
|
RepairFlag repair,
|
||||||
|
std::optional<ContentAddress> ca
|
||||||
|
)
|
||||||
|
: Goal(worker, isDependency)
|
||||||
, storePath(storePath)
|
, storePath(storePath)
|
||||||
, repair(repair)
|
, repair(repair)
|
||||||
, ca(ca)
|
, ca(ca)
|
||||||
{
|
{
|
||||||
state = &PathSubstitutionGoal::init;
|
|
||||||
name = fmt("substitution of '%s'", worker.store.printStorePath(this->storePath));
|
name = fmt("substitution of '%s'", worker.store.printStorePath(this->storePath));
|
||||||
trace("created");
|
trace("created");
|
||||||
maintainExpectedSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.expectedSubstitutions);
|
maintainExpectedSubstitutions = worker.expectedSubstitutions.addTemporarily(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,35 +32,29 @@ PathSubstitutionGoal::~PathSubstitutionGoal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Goal::Finished PathSubstitutionGoal::done(
|
Goal::WorkResult PathSubstitutionGoal::done(
|
||||||
ExitCode result,
|
ExitCode result,
|
||||||
BuildResult::Status status,
|
BuildResult::Status status,
|
||||||
std::optional<std::string> errorMsg)
|
std::optional<std::string> errorMsg)
|
||||||
{
|
{
|
||||||
buildResult.status = status;
|
BuildResult buildResult{.status = status};
|
||||||
if (errorMsg) {
|
if (errorMsg) {
|
||||||
debug(*errorMsg);
|
debug(*errorMsg);
|
||||||
buildResult.errorMsg = *errorMsg;
|
buildResult.errorMsg = *errorMsg;
|
||||||
}
|
}
|
||||||
return Finished{result};
|
return WorkResult{result, std::move(buildResult)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Goal::WorkResult PathSubstitutionGoal::work(bool inBuildSlot)
|
kj::Promise<Result<Goal::WorkResult>> PathSubstitutionGoal::workImpl() noexcept
|
||||||
{
|
try {
|
||||||
return (this->*state)(inBuildSlot);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Goal::WorkResult PathSubstitutionGoal::init(bool inBuildSlot)
|
|
||||||
{
|
|
||||||
trace("init");
|
trace("init");
|
||||||
|
|
||||||
worker.store.addTempRoot(storePath);
|
worker.store.addTempRoot(storePath);
|
||||||
|
|
||||||
/* If the path already exists we're done. */
|
/* If the path already exists we're done. */
|
||||||
if (!repair && worker.store.isValidPath(storePath)) {
|
if (!repair && worker.store.isValidPath(storePath)) {
|
||||||
return done(ecSuccess, BuildResult::AlreadyValid);
|
return {done(ecSuccess, BuildResult::AlreadyValid)};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.readOnlyMode)
|
if (settings.readOnlyMode)
|
||||||
|
@ -61,12 +62,14 @@ Goal::WorkResult PathSubstitutionGoal::init(bool inBuildSlot)
|
||||||
|
|
||||||
subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
|
subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
|
||||||
|
|
||||||
return tryNext(inBuildSlot);
|
return tryNext();
|
||||||
|
} catch (...) {
|
||||||
|
return {std::current_exception()};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Goal::WorkResult PathSubstitutionGoal::tryNext(bool inBuildSlot)
|
kj::Promise<Result<Goal::WorkResult>> PathSubstitutionGoal::tryNext() noexcept
|
||||||
{
|
try {
|
||||||
trace("trying next substituter");
|
trace("trying next substituter");
|
||||||
|
|
||||||
cleanup();
|
cleanup();
|
||||||
|
@ -81,7 +84,7 @@ Goal::WorkResult PathSubstitutionGoal::tryNext(bool inBuildSlot)
|
||||||
/* Hack: don't indicate failure if there were no substituters.
|
/* Hack: don't indicate failure if there were no substituters.
|
||||||
In that case the calling derivation should just do a
|
In that case the calling derivation should just do a
|
||||||
build. */
|
build. */
|
||||||
return done(
|
co_return done(
|
||||||
substituterFailed ? ecFailed : ecNoSubstituters,
|
substituterFailed ? ecFailed : ecNoSubstituters,
|
||||||
BuildResult::NoSubstituters,
|
BuildResult::NoSubstituters,
|
||||||
fmt("path '%s' is required, but there is no substituter that can build it", worker.store.printStorePath(storePath)));
|
fmt("path '%s' is required, but there is no substituter that can build it", worker.store.printStorePath(storePath)));
|
||||||
|
@ -97,26 +100,28 @@ Goal::WorkResult PathSubstitutionGoal::tryNext(bool inBuildSlot)
|
||||||
if (sub->storeDir == worker.store.storeDir)
|
if (sub->storeDir == worker.store.storeDir)
|
||||||
assert(subPath == storePath);
|
assert(subPath == storePath);
|
||||||
} else if (sub->storeDir != worker.store.storeDir) {
|
} else if (sub->storeDir != worker.store.storeDir) {
|
||||||
return tryNext(inBuildSlot);
|
co_return co_await tryNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
try {
|
try {
|
||||||
// FIXME: make async
|
// FIXME: make async
|
||||||
info = sub->queryPathInfo(subPath ? *subPath : storePath);
|
info = sub->queryPathInfo(subPath ? *subPath : storePath);
|
||||||
|
break;
|
||||||
} catch (InvalidPath &) {
|
} catch (InvalidPath &) {
|
||||||
return tryNext(inBuildSlot);
|
|
||||||
} catch (SubstituterDisabled &) {
|
} catch (SubstituterDisabled &) {
|
||||||
if (settings.tryFallback) {
|
if (!settings.tryFallback) {
|
||||||
return tryNext(inBuildSlot);
|
|
||||||
}
|
|
||||||
throw;
|
throw;
|
||||||
|
}
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
if (settings.tryFallback) {
|
if (settings.tryFallback) {
|
||||||
logError(e.info());
|
logError(e.info());
|
||||||
return tryNext(inBuildSlot);
|
} else {
|
||||||
}
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
co_return co_await tryNext();
|
||||||
|
} while (false);
|
||||||
|
|
||||||
if (info->path != storePath) {
|
if (info->path != storePath) {
|
||||||
if (info->isContentAddressed(*sub) && info->references.empty()) {
|
if (info->isContentAddressed(*sub) && info->references.empty()) {
|
||||||
|
@ -126,18 +131,18 @@ Goal::WorkResult PathSubstitutionGoal::tryNext(bool inBuildSlot)
|
||||||
} else {
|
} else {
|
||||||
printError("asked '%s' for '%s' but got '%s'",
|
printError("asked '%s' for '%s' but got '%s'",
|
||||||
sub->getUri(), worker.store.printStorePath(storePath), sub->printStorePath(info->path));
|
sub->getUri(), worker.store.printStorePath(storePath), sub->printStorePath(info->path));
|
||||||
return tryNext(inBuildSlot);
|
co_return co_await tryNext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update the total expected download size. */
|
/* Update the total expected download size. */
|
||||||
auto narInfo = std::dynamic_pointer_cast<const NarInfo>(info);
|
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 =
|
maintainExpectedDownload =
|
||||||
narInfo && narInfo->fileSize
|
narInfo && narInfo->fileSize
|
||||||
? std::make_unique<MaintainCount<uint64_t>>(worker.expectedDownloadSize, narInfo->fileSize)
|
? worker.expectedDownloadSize.addTemporarily(narInfo->fileSize)
|
||||||
: nullptr;
|
: nullptr;
|
||||||
|
|
||||||
/* Bail out early if this substituter lacks a valid
|
/* Bail out early if this substituter lacks a valid
|
||||||
|
@ -147,65 +152,67 @@ Goal::WorkResult PathSubstitutionGoal::tryNext(bool inBuildSlot)
|
||||||
{
|
{
|
||||||
warn("ignoring substitute for '%s' from '%s', as it's not signed by any of the keys in 'trusted-public-keys'",
|
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());
|
worker.store.printStorePath(storePath), sub->getUri());
|
||||||
return tryNext(inBuildSlot);
|
co_return co_await tryNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* To maintain the closure invariant, we first have to realise the
|
/* To maintain the closure invariant, we first have to realise the
|
||||||
paths referenced by this one. */
|
paths referenced by this one. */
|
||||||
WaitForGoals result;
|
kj::Vector<std::pair<GoalPtr, kj::Promise<Result<WorkResult>>>> dependencies;
|
||||||
for (auto & i : info->references)
|
for (auto & i : info->references)
|
||||||
if (i != storePath) /* ignore self-references */
|
if (i != storePath) /* ignore self-references */
|
||||||
result.goals.insert(worker.makePathSubstitutionGoal(i));
|
dependencies.add(worker.goalFactory().makePathSubstitutionGoal(i));
|
||||||
|
|
||||||
if (result.goals.empty()) {/* to prevent hang (no wake-up event) */
|
if (!dependencies.empty()) {/* to prevent hang (no wake-up event) */
|
||||||
return referencesValid(inBuildSlot);
|
(co_await waitForGoals(dependencies.releaseAsArray())).value();
|
||||||
} else {
|
|
||||||
state = &PathSubstitutionGoal::referencesValid;
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
co_return co_await referencesValid();
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::failure(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Goal::WorkResult PathSubstitutionGoal::referencesValid(bool inBuildSlot)
|
kj::Promise<Result<Goal::WorkResult>> PathSubstitutionGoal::referencesValid() noexcept
|
||||||
{
|
try {
|
||||||
trace("all references realised");
|
trace("all references realised");
|
||||||
|
|
||||||
if (nrFailed > 0) {
|
if (nrFailed > 0) {
|
||||||
return done(
|
return {done(
|
||||||
nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed,
|
nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed,
|
||||||
BuildResult::DependencyFailed,
|
BuildResult::DependencyFailed,
|
||||||
fmt("some references of path '%s' could not be realised", worker.store.printStorePath(storePath)));
|
fmt("some references of path '%s' could not be realised", worker.store.printStorePath(storePath)))};
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto & i : info->references)
|
for (auto & i : info->references)
|
||||||
if (i != storePath) /* ignore self-references */
|
if (i != storePath) /* ignore self-references */
|
||||||
assert(worker.store.isValidPath(i));
|
assert(worker.store.isValidPath(i));
|
||||||
|
|
||||||
state = &PathSubstitutionGoal::tryToRun;
|
return tryToRun();
|
||||||
return ContinueImmediately{};
|
} catch (...) {
|
||||||
|
return {std::current_exception()};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Goal::WorkResult PathSubstitutionGoal::tryToRun(bool inBuildSlot)
|
kj::Promise<Result<Goal::WorkResult>> PathSubstitutionGoal::tryToRun() noexcept
|
||||||
{
|
try {
|
||||||
trace("trying to run");
|
trace("trying to run");
|
||||||
|
|
||||||
if (!inBuildSlot) {
|
if (!slotToken.valid()) {
|
||||||
return WaitForSlot{};
|
slotToken = co_await worker.substitutions.acquire();
|
||||||
}
|
}
|
||||||
|
|
||||||
maintainRunningSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.runningSubstitutions);
|
maintainRunningSubstitutions = worker.runningSubstitutions.addTemporarily(1);
|
||||||
|
|
||||||
outPipe.create();
|
auto pipe = kj::newPromiseAndCrossThreadFulfiller<void>();
|
||||||
|
outPipe = kj::mv(pipe.fulfiller);
|
||||||
|
|
||||||
thr = std::async(std::launch::async, [this]() {
|
thr = std::async(std::launch::async, [this]() {
|
||||||
|
/* Wake up the worker loop when we're done. */
|
||||||
|
Finally updateStats([this]() { outPipe->fulfill(); });
|
||||||
|
|
||||||
auto & fetchPath = subPath ? *subPath : storePath;
|
auto & fetchPath = subPath ? *subPath : storePath;
|
||||||
try {
|
try {
|
||||||
ReceiveInterrupts receiveInterrupts;
|
ReceiveInterrupts receiveInterrupts;
|
||||||
|
|
||||||
/* Wake up the worker loop when we're done. */
|
|
||||||
Finally updateStats([this]() { outPipe.writeSide.close(); });
|
|
||||||
|
|
||||||
Activity act(*logger, actSubstitute, Logger::Fields{worker.store.printStorePath(storePath), sub->getUri()});
|
Activity act(*logger, actSubstitute, Logger::Fields{worker.store.printStorePath(storePath), sub->getUri()});
|
||||||
PushActivity pact(act.id);
|
PushActivity pact(act.id);
|
||||||
|
|
||||||
|
@ -221,19 +228,22 @@ Goal::WorkResult PathSubstitutionGoal::tryToRun(bool inBuildSlot)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
state = &PathSubstitutionGoal::finished;
|
co_await pipe.promise;
|
||||||
return WaitForWorld{{outPipe.readSide.get()}, true};
|
co_return co_await finished();
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::failure(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Goal::WorkResult PathSubstitutionGoal::finished(bool inBuildSlot)
|
kj::Promise<Result<Goal::WorkResult>> PathSubstitutionGoal::finished() noexcept
|
||||||
{
|
try {
|
||||||
trace("substitute finished");
|
trace("substitute finished");
|
||||||
|
|
||||||
worker.childTerminated(this);
|
do {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
slotToken = {};
|
||||||
thr.get();
|
thr.get();
|
||||||
|
break;
|
||||||
} catch (std::exception & e) {
|
} catch (std::exception & e) {
|
||||||
printError(e.what());
|
printError(e.what());
|
||||||
|
|
||||||
|
@ -247,11 +257,10 @@ Goal::WorkResult PathSubstitutionGoal::finished(bool inBuildSlot)
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
substituterFailed = true;
|
substituterFailed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Try the next substitute. */
|
|
||||||
state = &PathSubstitutionGoal::tryNext;
|
|
||||||
return ContinueImmediately{};
|
|
||||||
}
|
}
|
||||||
|
/* Try the next substitute. */
|
||||||
|
co_return co_await tryNext();
|
||||||
|
} while (false);
|
||||||
|
|
||||||
worker.markContentsGood(storePath);
|
worker.markContentsGood(storePath);
|
||||||
|
|
||||||
|
@ -262,22 +271,15 @@ Goal::WorkResult PathSubstitutionGoal::finished(bool inBuildSlot)
|
||||||
maintainExpectedSubstitutions.reset();
|
maintainExpectedSubstitutions.reset();
|
||||||
worker.doneSubstitutions++;
|
worker.doneSubstitutions++;
|
||||||
|
|
||||||
if (maintainExpectedDownload) {
|
worker.doneDownloadSize += maintainExpectedDownload.delta();
|
||||||
auto fileSize = maintainExpectedDownload->delta;
|
|
||||||
maintainExpectedDownload.reset();
|
maintainExpectedDownload.reset();
|
||||||
worker.doneDownloadSize += fileSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
worker.doneNarSize += maintainExpectedNar->delta;
|
worker.doneNarSize += maintainExpectedNar.delta();
|
||||||
maintainExpectedNar.reset();
|
maintainExpectedNar.reset();
|
||||||
|
|
||||||
return done(ecSuccess, BuildResult::Substituted);
|
co_return done(ecSuccess, BuildResult::Substituted);
|
||||||
}
|
} catch (...) {
|
||||||
|
co_return result::failure(std::current_exception());
|
||||||
|
|
||||||
Goal::WorkResult PathSubstitutionGoal::handleChildOutput(int fd, std::string_view data)
|
|
||||||
{
|
|
||||||
return StillAlive{};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -287,12 +289,9 @@ void PathSubstitutionGoal::cleanup()
|
||||||
if (thr.valid()) {
|
if (thr.valid()) {
|
||||||
// FIXME: signal worker thread to quit.
|
// FIXME: signal worker thread to quit.
|
||||||
thr.get();
|
thr.get();
|
||||||
worker.childTerminated(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
outPipe.close();
|
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
ignoreException();
|
ignoreExceptionInDestructor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include "lock.hh"
|
#include "lock.hh"
|
||||||
|
#include "notifying-counter.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "goal.hh"
|
#include "goal.hh"
|
||||||
|
|
||||||
|
@ -45,7 +46,7 @@ struct PathSubstitutionGoal : public Goal
|
||||||
/**
|
/**
|
||||||
* Pipe for the substituter's standard output.
|
* Pipe for the substituter's standard output.
|
||||||
*/
|
*/
|
||||||
Pipe outPipe;
|
kj::Own<kj::CrossThreadPromiseFulfiller<void>> outPipe;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The substituter thread.
|
* The substituter thread.
|
||||||
|
@ -63,52 +64,38 @@ struct PathSubstitutionGoal : public Goal
|
||||||
*/
|
*/
|
||||||
Path destPath;
|
Path destPath;
|
||||||
|
|
||||||
std::unique_ptr<MaintainCount<uint64_t>> maintainExpectedSubstitutions,
|
NotifyingCounter<uint64_t>::Bump maintainExpectedSubstitutions,
|
||||||
maintainRunningSubstitutions, maintainExpectedNar, maintainExpectedDownload;
|
maintainRunningSubstitutions, maintainExpectedNar, maintainExpectedDownload;
|
||||||
|
|
||||||
typedef WorkResult (PathSubstitutionGoal::*GoalState)(bool inBuildSlot);
|
|
||||||
GoalState state;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Content address for recomputing store path
|
* Content address for recomputing store path
|
||||||
*/
|
*/
|
||||||
std::optional<ContentAddress> ca;
|
std::optional<ContentAddress> ca;
|
||||||
|
|
||||||
Finished done(
|
WorkResult done(
|
||||||
ExitCode result,
|
ExitCode result,
|
||||||
BuildResult::Status status,
|
BuildResult::Status status,
|
||||||
std::optional<std::string> errorMsg = {});
|
std::optional<std::string> errorMsg = {});
|
||||||
|
|
||||||
public:
|
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();
|
~PathSubstitutionGoal();
|
||||||
|
|
||||||
Finished timedOut(Error && ex) override { abort(); };
|
kj::Promise<Result<WorkResult>> workImpl() noexcept override;
|
||||||
|
|
||||||
/**
|
|
||||||
* We prepend "a$" to the key name to ensure substitution goals
|
|
||||||
* happen before derivation goals.
|
|
||||||
*/
|
|
||||||
std::string key() override
|
|
||||||
{
|
|
||||||
return "a$" + std::string(storePath.name()) + "$" + worker.store.printStorePath(storePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
WorkResult work(bool inBuildSlot) override;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The states.
|
* The states.
|
||||||
*/
|
*/
|
||||||
WorkResult init(bool inBuildSlot);
|
kj::Promise<Result<WorkResult>> tryNext() noexcept;
|
||||||
WorkResult tryNext(bool inBuildSlot);
|
kj::Promise<Result<WorkResult>> referencesValid() noexcept;
|
||||||
WorkResult referencesValid(bool inBuildSlot);
|
kj::Promise<Result<WorkResult>> tryToRun() noexcept;
|
||||||
WorkResult tryToRun(bool inBuildSlot);
|
kj::Promise<Result<WorkResult>> finished() noexcept;
|
||||||
WorkResult finished(bool inBuildSlot);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback used by the worker to write to the log.
|
|
||||||
*/
|
|
||||||
WorkResult handleChildOutput(int fd, std::string_view data) override;
|
|
||||||
|
|
||||||
/* Called by destructor, can't be overridden */
|
/* Called by destructor, can't be overridden */
|
||||||
void cleanup() override final;
|
void cleanup() override final;
|
||||||
|
|
|
@ -1,26 +1,42 @@
|
||||||
|
#include "async-collect.hh"
|
||||||
#include "charptr-cast.hh"
|
#include "charptr-cast.hh"
|
||||||
#include "worker.hh"
|
#include "worker.hh"
|
||||||
|
#include "finally.hh"
|
||||||
#include "substitution-goal.hh"
|
#include "substitution-goal.hh"
|
||||||
#include "drv-output-substitution-goal.hh"
|
#include "drv-output-substitution-goal.hh"
|
||||||
#include "local-derivation-goal.hh"
|
#include "local-derivation-goal.hh"
|
||||||
#include "signals.hh"
|
#include "signals.hh"
|
||||||
#include "hook-instance.hh" // IWYU pragma: keep
|
#include "hook-instance.hh" // IWYU pragma: keep
|
||||||
|
#include <boost/outcome/try.hpp>
|
||||||
#include <poll.h>
|
#include <kj/vector.h>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
Worker::Worker(Store & store, Store & evalStore)
|
namespace {
|
||||||
|
struct ErrorHandler : kj::TaskSet::ErrorHandler
|
||||||
|
{
|
||||||
|
void taskFailed(kj::Exception && e) override
|
||||||
|
{
|
||||||
|
printError("unexpected async failure in Worker: %s", kj::str(e).cStr());
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
} errorHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
Worker::Worker(Store & store, Store & evalStore, kj::AsyncIoContext & aio)
|
||||||
: act(*logger, actRealise)
|
: act(*logger, actRealise)
|
||||||
, actDerivations(*logger, actBuilds)
|
, actDerivations(*logger, actBuilds)
|
||||||
, actSubstitutions(*logger, actCopyPaths)
|
, actSubstitutions(*logger, actCopyPaths)
|
||||||
, store(store)
|
, store(store)
|
||||||
, evalStore(evalStore)
|
, evalStore(evalStore)
|
||||||
|
, aio(aio)
|
||||||
|
/* Make sure that we are always allowed to run at least one substitution.
|
||||||
|
This prevents infinite waiting. */
|
||||||
|
, substitutions(std::max<unsigned>(1, settings.maxSubstitutionJobs))
|
||||||
|
, localBuilds(settings.maxBuildJobs)
|
||||||
|
, children(errorHandler)
|
||||||
{
|
{
|
||||||
/* Debugging: prevent recursive workers. */
|
/* Debugging: prevent recursive workers. */
|
||||||
nrLocalBuilds = 0;
|
|
||||||
nrSubstitutions = 0;
|
|
||||||
lastWokenUp = steady_time_point::min();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,7 +46,11 @@ Worker::~Worker()
|
||||||
goals that refer to this worker should be gone. (Otherwise we
|
goals that refer to this worker should be gone. (Otherwise we
|
||||||
are in trouble, since goals may call childTerminated() etc. in
|
are in trouble, since goals may call childTerminated() etc. in
|
||||||
their destructors). */
|
their destructors). */
|
||||||
topGoals.clear();
|
children.clear();
|
||||||
|
|
||||||
|
derivationGoals.clear();
|
||||||
|
drvOutputSubstitutionGoals.clear();
|
||||||
|
substitutionGoals.clear();
|
||||||
|
|
||||||
assert(expectedSubstitutions == 0);
|
assert(expectedSubstitutions == 0);
|
||||||
assert(expectedDownloadSize == 0);
|
assert(expectedDownloadSize == 0);
|
||||||
|
@ -38,326 +58,158 @@ Worker::~Worker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon(
|
template<typename ID, std::derived_from<Goal> G>
|
||||||
const StorePath & drvPath,
|
std::pair<std::shared_ptr<G>, kj::Promise<Result<Goal::WorkResult>>> Worker::makeGoalCommon(
|
||||||
const OutputsSpec & wantedOutputs,
|
std::map<ID, CachedGoal<G>> & map,
|
||||||
std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal)
|
const ID & key,
|
||||||
|
InvocableR<std::unique_ptr<G>> auto create,
|
||||||
|
InvocableR<bool, G &> auto modify
|
||||||
|
)
|
||||||
{
|
{
|
||||||
std::weak_ptr<DerivationGoal> & goal_weak = derivationGoals[drvPath];
|
auto [it, _inserted] = map.try_emplace(key);
|
||||||
std::shared_ptr<DerivationGoal> goal = goal_weak.lock();
|
// try twice to create the goal. we can only loop if we hit the continue,
|
||||||
|
// and then we only want to recreate the goal *once*. concurrent accesses
|
||||||
|
// to the worker are not sound, we want to catch them if at all possible.
|
||||||
|
for ([[maybe_unused]] auto _attempt : {1, 2}) {
|
||||||
|
auto & cachedGoal = it->second;
|
||||||
|
auto & goal = cachedGoal.goal;
|
||||||
if (!goal) {
|
if (!goal) {
|
||||||
goal = mkDrvGoal();
|
goal = create();
|
||||||
goal_weak = goal;
|
// do not start working immediately. if we are not yet running we
|
||||||
wakeUp(goal);
|
// may create dependencies as though they were toplevel goals, in
|
||||||
|
// which case the dependencies will not report build errors. when
|
||||||
|
// we are running we may be called for this same goal more times,
|
||||||
|
// and then we want to modify rather than recreate when possible.
|
||||||
|
auto removeWhenDone = [goal, &map, it] {
|
||||||
|
// c++ lambda coroutine capture semantics are *so* fucked up.
|
||||||
|
return [](auto goal, auto & map, auto it) -> kj::Promise<Result<Goal::WorkResult>> {
|
||||||
|
auto result = co_await goal->work();
|
||||||
|
// a concurrent call to makeGoalCommon may have reset our
|
||||||
|
// cached goal and replaced it with a new instance. don't
|
||||||
|
// remove the goal in this case, otherwise we will crash.
|
||||||
|
if (goal == it->second.goal) {
|
||||||
|
map.erase(it);
|
||||||
|
}
|
||||||
|
co_return result;
|
||||||
|
}(goal, map, it);
|
||||||
|
};
|
||||||
|
cachedGoal.promise = kj::evalLater(std::move(removeWhenDone)).fork();
|
||||||
|
children.add(cachedGoal.promise.addBranch().then([this](auto _result) {
|
||||||
|
if (_result.has_value()) {
|
||||||
|
auto & result = _result.value();
|
||||||
|
permanentFailure |= result.permanentFailure;
|
||||||
|
timedOut |= result.timedOut;
|
||||||
|
hashMismatch |= result.hashMismatch;
|
||||||
|
checkMismatch |= result.checkMismatch;
|
||||||
|
}
|
||||||
|
}));
|
||||||
} else {
|
} else {
|
||||||
goal->addWantedOutputs(wantedOutputs);
|
if (!modify(*goal)) {
|
||||||
|
cachedGoal = {};
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
return goal;
|
}
|
||||||
|
return {goal, cachedGoal.promise.addBranch()};
|
||||||
|
}
|
||||||
|
assert(false && "could not make a goal. possible concurrent worker access");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(const StorePath & drvPath,
|
std::pair<std::shared_ptr<DerivationGoal>, kj::Promise<Result<Goal::WorkResult>>> Worker::makeDerivationGoal(
|
||||||
const OutputsSpec & wantedOutputs, BuildMode buildMode)
|
const StorePath & drvPath, const OutputsSpec & wantedOutputs, BuildMode buildMode
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
|
return makeGoalCommon(
|
||||||
|
derivationGoals,
|
||||||
|
drvPath,
|
||||||
|
[&]() -> std::unique_ptr<DerivationGoal> {
|
||||||
return !dynamic_cast<LocalStore *>(&store)
|
return !dynamic_cast<LocalStore *>(&store)
|
||||||
? std::make_shared<DerivationGoal>(drvPath, wantedOutputs, *this, buildMode)
|
? std::make_unique<DerivationGoal>(
|
||||||
: LocalDerivationGoal::makeLocalDerivationGoal(drvPath, wantedOutputs, *this, buildMode);
|
drvPath, wantedOutputs, *this, running, buildMode
|
||||||
});
|
)
|
||||||
|
: LocalDerivationGoal::makeLocalDerivationGoal(
|
||||||
|
drvPath, wantedOutputs, *this, running, buildMode
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[&](DerivationGoal & g) { return g.addWantedOutputs(wantedOutputs); }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::shared_ptr<DerivationGoal> Worker::makeBasicDerivationGoal(const StorePath & drvPath,
|
std::pair<std::shared_ptr<DerivationGoal>, kj::Promise<Result<Goal::WorkResult>>> Worker::makeBasicDerivationGoal(
|
||||||
const BasicDerivation & drv, const OutputsSpec & wantedOutputs, BuildMode buildMode)
|
const StorePath & drvPath,
|
||||||
|
const BasicDerivation & drv,
|
||||||
|
const OutputsSpec & wantedOutputs,
|
||||||
|
BuildMode buildMode
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
|
return makeGoalCommon(
|
||||||
|
derivationGoals,
|
||||||
|
drvPath,
|
||||||
|
[&]() -> std::unique_ptr<DerivationGoal> {
|
||||||
return !dynamic_cast<LocalStore *>(&store)
|
return !dynamic_cast<LocalStore *>(&store)
|
||||||
? std::make_shared<DerivationGoal>(drvPath, drv, wantedOutputs, *this, buildMode)
|
? std::make_unique<DerivationGoal>(
|
||||||
: LocalDerivationGoal::makeLocalDerivationGoal(drvPath, drv, wantedOutputs, *this, buildMode);
|
drvPath, drv, wantedOutputs, *this, running, buildMode
|
||||||
});
|
)
|
||||||
|
: LocalDerivationGoal::makeLocalDerivationGoal(
|
||||||
|
drvPath, drv, wantedOutputs, *this, running, buildMode
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[&](DerivationGoal & g) { return g.addWantedOutputs(wantedOutputs); }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::shared_ptr<PathSubstitutionGoal> Worker::makePathSubstitutionGoal(const StorePath & path, RepairFlag repair, std::optional<ContentAddress> ca)
|
std::pair<std::shared_ptr<PathSubstitutionGoal>, kj::Promise<Result<Goal::WorkResult>>>
|
||||||
|
Worker::makePathSubstitutionGoal(
|
||||||
|
const StorePath & path, RepairFlag repair, std::optional<ContentAddress> ca
|
||||||
|
)
|
||||||
{
|
{
|
||||||
std::weak_ptr<PathSubstitutionGoal> & goal_weak = substitutionGoals[path];
|
return makeGoalCommon(
|
||||||
auto goal = goal_weak.lock(); // FIXME
|
substitutionGoals,
|
||||||
if (!goal) {
|
path,
|
||||||
goal = std::make_shared<PathSubstitutionGoal>(path, *this, repair, ca);
|
[&] { return std::make_unique<PathSubstitutionGoal>(path, *this, running, repair, ca); },
|
||||||
goal_weak = goal;
|
[&](auto &) { return true; }
|
||||||
wakeUp(goal);
|
);
|
||||||
}
|
|
||||||
return goal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::shared_ptr<DrvOutputSubstitutionGoal> Worker::makeDrvOutputSubstitutionGoal(const DrvOutput& id, RepairFlag repair, std::optional<ContentAddress> ca)
|
std::pair<std::shared_ptr<DrvOutputSubstitutionGoal>, kj::Promise<Result<Goal::WorkResult>>>
|
||||||
|
Worker::makeDrvOutputSubstitutionGoal(
|
||||||
|
const DrvOutput & id, RepairFlag repair, std::optional<ContentAddress> ca
|
||||||
|
)
|
||||||
{
|
{
|
||||||
std::weak_ptr<DrvOutputSubstitutionGoal> & goal_weak = drvOutputSubstitutionGoals[id];
|
return makeGoalCommon(
|
||||||
auto goal = goal_weak.lock(); // FIXME
|
drvOutputSubstitutionGoals,
|
||||||
if (!goal) {
|
id,
|
||||||
goal = std::make_shared<DrvOutputSubstitutionGoal>(id, *this, repair, ca);
|
[&] { return std::make_unique<DrvOutputSubstitutionGoal>(id, *this, running, repair, ca); },
|
||||||
goal_weak = goal;
|
[&](auto &) { return true; }
|
||||||
wakeUp(goal);
|
);
|
||||||
}
|
|
||||||
return goal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
GoalPtr Worker::makeGoal(const DerivedPath & req, BuildMode buildMode)
|
std::pair<GoalPtr, kj::Promise<Result<Goal::WorkResult>>> Worker::makeGoal(const DerivedPath & req, BuildMode buildMode)
|
||||||
{
|
{
|
||||||
return std::visit(overloaded {
|
return std::visit(overloaded {
|
||||||
[&](const DerivedPath::Built & bfd) -> GoalPtr {
|
[&](const DerivedPath::Built & bfd) -> std::pair<GoalPtr, kj::Promise<Result<Goal::WorkResult>>> {
|
||||||
if (auto bop = std::get_if<DerivedPath::Opaque>(&*bfd.drvPath))
|
if (auto bop = std::get_if<DerivedPath::Opaque>(&*bfd.drvPath))
|
||||||
return makeDerivationGoal(bop->path, bfd.outputs, buildMode);
|
return makeDerivationGoal(bop->path, bfd.outputs, buildMode);
|
||||||
else
|
else
|
||||||
throw UnimplementedError("Building dynamic derivations in one shot is not yet implemented.");
|
throw UnimplementedError("Building dynamic derivations in one shot is not yet implemented.");
|
||||||
},
|
},
|
||||||
[&](const DerivedPath::Opaque & bo) -> GoalPtr {
|
[&](const DerivedPath::Opaque & bo) -> std::pair<GoalPtr, kj::Promise<Result<Goal::WorkResult>>> {
|
||||||
return makePathSubstitutionGoal(bo.path, buildMode == bmRepair ? Repair : NoRepair);
|
return makePathSubstitutionGoal(bo.path, buildMode == bmRepair ? Repair : NoRepair);
|
||||||
},
|
},
|
||||||
}, req.raw());
|
}, req.raw());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kj::Promise<Result<Worker::Results>> Worker::updateStatistics()
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
statisticsUpdateInhibitor = co_await statisticsUpdateSignal.acquire();
|
||||||
|
|
||||||
template<typename K, typename G>
|
// only update progress info while running. this notably excludes updating
|
||||||
static void removeGoal(std::shared_ptr<G> goal, std::map<K, std::weak_ptr<G>> & goalMap)
|
// progress info while destroying, which causes the progress bar to assert
|
||||||
{
|
|
||||||
/* !!! inefficient */
|
|
||||||
for (auto i = goalMap.begin();
|
|
||||||
i != goalMap.end(); )
|
|
||||||
if (i->second.lock() == goal) {
|
|
||||||
auto j = i; ++j;
|
|
||||||
goalMap.erase(i);
|
|
||||||
i = j;
|
|
||||||
}
|
|
||||||
else ++i;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Worker::goalFinished(GoalPtr goal, Goal::Finished & f)
|
|
||||||
{
|
|
||||||
goal->trace("done");
|
|
||||||
assert(!goal->exitCode.has_value());
|
|
||||||
goal->exitCode = f.result;
|
|
||||||
|
|
||||||
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 = f.ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto & i : goal->waiters) {
|
|
||||||
if (GoalPtr waiting = i.lock()) {
|
|
||||||
assert(waiting->waitees.count(goal));
|
|
||||||
waiting->waitees.erase(goal);
|
|
||||||
|
|
||||||
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 (waiting->waitees.empty() || (f.result == Goal::ecFailed && !settings.keepGoing)) {
|
|
||||||
/* If we failed and keepGoing is not set, we remove all
|
|
||||||
remaining waitees. */
|
|
||||||
for (auto & i : waiting->waitees) {
|
|
||||||
i->waiters.extract(waiting);
|
|
||||||
}
|
|
||||||
waiting->waitees.clear();
|
|
||||||
|
|
||||||
wakeUp(waiting);
|
|
||||||
}
|
|
||||||
|
|
||||||
waiting->waiteeDone(goal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
goal->waiters.clear();
|
|
||||||
removeGoal(goal);
|
|
||||||
goal->cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Worker::handleWorkResult(GoalPtr goal, Goal::WorkResult how)
|
|
||||||
{
|
|
||||||
std::visit(
|
|
||||||
overloaded{
|
|
||||||
[&](Goal::StillAlive) {},
|
|
||||||
[&](Goal::WaitForSlot) { waitForBuildSlot(goal); },
|
|
||||||
[&](Goal::WaitForAWhile) { waitForAWhile(goal); },
|
|
||||||
[&](Goal::ContinueImmediately) { wakeUp(goal); },
|
|
||||||
[&](Goal::WaitForGoals & w) {
|
|
||||||
for (auto & dep : w.goals) {
|
|
||||||
goal->waitees.insert(dep);
|
|
||||||
dep->waiters.insert(goal);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[&](Goal::WaitForWorld & w) { childStarted(goal, w.fds, w.inBuildSlot); },
|
|
||||||
[&](Goal::Finished & f) { goalFinished(goal, f); },
|
|
||||||
},
|
|
||||||
how
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Worker::removeGoal(GoalPtr goal)
|
|
||||||
{
|
|
||||||
if (auto drvGoal = std::dynamic_pointer_cast<DerivationGoal>(goal))
|
|
||||||
nix::removeGoal(drvGoal, derivationGoals);
|
|
||||||
else if (auto subGoal = std::dynamic_pointer_cast<PathSubstitutionGoal>(goal))
|
|
||||||
nix::removeGoal(subGoal, substitutionGoals);
|
|
||||||
else if (auto subGoal = std::dynamic_pointer_cast<DrvOutputSubstitutionGoal>(goal))
|
|
||||||
nix::removeGoal(subGoal, drvOutputSubstitutionGoals);
|
|
||||||
else
|
|
||||||
assert(false);
|
|
||||||
|
|
||||||
if (topGoals.find(goal) != topGoals.end()) {
|
|
||||||
topGoals.erase(goal);
|
|
||||||
/* If a top-level goal failed, then kill all other goals
|
|
||||||
(unless keepGoing was set). */
|
|
||||||
if (goal->exitCode == Goal::ecFailed && !settings.keepGoing)
|
|
||||||
topGoals.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Worker::wakeUp(GoalPtr goal)
|
|
||||||
{
|
|
||||||
goal->trace("woken up");
|
|
||||||
awake.insert(goal);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Worker::childStarted(GoalPtr goal, const std::set<int> & fds,
|
|
||||||
bool inBuildSlot)
|
|
||||||
{
|
|
||||||
Child child;
|
|
||||||
child.goal = goal;
|
|
||||||
child.goal2 = goal.get();
|
|
||||||
child.fds = fds;
|
|
||||||
child.timeStarted = child.lastOutput = steady_time_point::clock::now();
|
|
||||||
child.inBuildSlot = inBuildSlot;
|
|
||||||
children.emplace_back(child);
|
|
||||||
if (inBuildSlot) {
|
|
||||||
switch (goal->jobCategory()) {
|
|
||||||
case JobCategory::Substitution:
|
|
||||||
nrSubstitutions++;
|
|
||||||
break;
|
|
||||||
case JobCategory::Build:
|
|
||||||
nrLocalBuilds++;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Worker::childTerminated(Goal * goal)
|
|
||||||
{
|
|
||||||
auto i = std::find_if(children.begin(), children.end(),
|
|
||||||
[&](const Child & child) { return child.goal2 == goal; });
|
|
||||||
if (i == children.end()) return;
|
|
||||||
|
|
||||||
if (i->inBuildSlot) {
|
|
||||||
switch (goal->jobCategory()) {
|
|
||||||
case JobCategory::Substitution:
|
|
||||||
assert(nrSubstitutions > 0);
|
|
||||||
nrSubstitutions--;
|
|
||||||
break;
|
|
||||||
case JobCategory::Build:
|
|
||||||
assert(nrLocalBuilds > 0);
|
|
||||||
nrLocalBuilds--;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
children.erase(i);
|
|
||||||
|
|
||||||
/* Wake up goals waiting for a build slot. */
|
|
||||||
for (auto & j : wantingToBuild) {
|
|
||||||
GoalPtr goal = j.lock();
|
|
||||||
if (goal) wakeUp(goal);
|
|
||||||
}
|
|
||||||
|
|
||||||
wantingToBuild.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Worker::waitForBuildSlot(GoalPtr goal)
|
|
||||||
{
|
|
||||||
goal->trace("wait for build slot");
|
|
||||||
bool isSubstitutionGoal = goal->jobCategory() == JobCategory::Substitution;
|
|
||||||
if ((!isSubstitutionGoal && nrLocalBuilds < settings.maxBuildJobs) ||
|
|
||||||
(isSubstitutionGoal && nrSubstitutions < settings.maxSubstitutionJobs))
|
|
||||||
wakeUp(goal); /* we can do it right away */
|
|
||||||
else
|
|
||||||
wantingToBuild.insert(goal);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Worker::waitForAWhile(GoalPtr goal)
|
|
||||||
{
|
|
||||||
debug("wait for a while");
|
|
||||||
waitingForAWhile.insert(goal);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Worker::run(const Goals & _topGoals)
|
|
||||||
{
|
|
||||||
std::vector<nix::DerivedPath> topPaths;
|
|
||||||
|
|
||||||
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});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Call queryMissing() to efficiently query substitutes. */
|
|
||||||
StorePathSet willBuild, willSubstitute, unknown;
|
|
||||||
uint64_t downloadSize, narSize;
|
|
||||||
store.queryMissing(topPaths, willBuild, willSubstitute, unknown, downloadSize, narSize);
|
|
||||||
|
|
||||||
debug("entered goal loop");
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
|
|
||||||
checkInterrupt();
|
|
||||||
|
|
||||||
// TODO GC interface?
|
|
||||||
if (auto localStore = dynamic_cast<LocalStore *>(&store))
|
|
||||||
localStore->autoGC(false);
|
|
||||||
|
|
||||||
/* Call every wake goal (in the ordering established by
|
|
||||||
CompareGoalPtrs). */
|
|
||||||
while (!awake.empty() && !topGoals.empty()) {
|
|
||||||
Goals awake2;
|
|
||||||
for (auto & i : awake) {
|
|
||||||
GoalPtr goal = i.lock();
|
|
||||||
if (goal) awake2.insert(goal);
|
|
||||||
}
|
|
||||||
awake.clear();
|
|
||||||
for (auto & goal : awake2) {
|
|
||||||
checkInterrupt();
|
|
||||||
/* 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));
|
|
||||||
|
|
||||||
actDerivations.progress(
|
actDerivations.progress(
|
||||||
doneBuilds, expectedBuilds + doneBuilds, runningBuilds, failedBuilds
|
doneBuilds, expectedBuilds + doneBuilds, runningBuilds, failedBuilds
|
||||||
);
|
);
|
||||||
|
@ -370,174 +222,82 @@ void Worker::run(const Goals & _topGoals)
|
||||||
act.setExpected(actFileTransfer, expectedDownloadSize + doneDownloadSize);
|
act.setExpected(actFileTransfer, expectedDownloadSize + doneDownloadSize);
|
||||||
act.setExpected(actCopyPath, expectedNarSize + doneNarSize);
|
act.setExpected(actCopyPath, expectedNarSize + doneNarSize);
|
||||||
|
|
||||||
if (topGoals.empty()) break; // stuff may have been cancelled
|
// limit to 50fps. that should be more than good enough for anything we do
|
||||||
|
co_await aio.provider->getTimer().afterDelay(20 * kj::MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::failure(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topGoals.empty()) break;
|
Worker::Results Worker::run(std::function<Targets (GoalFactory &)> req)
|
||||||
|
{
|
||||||
|
auto topGoals = req(goalFactory());
|
||||||
|
|
||||||
/* Wait for input. */
|
assert(!running);
|
||||||
if (!children.empty() || !waitingForAWhile.empty())
|
running = true;
|
||||||
waitForInput();
|
Finally const _stop([&] { running = false; });
|
||||||
else {
|
|
||||||
assert(!awake.empty());
|
auto onInterrupt = kj::newPromiseAndCrossThreadFulfiller<Result<Results>>();
|
||||||
|
auto interruptCallback = createInterruptCallback([&] {
|
||||||
|
return result::failure(std::make_exception_ptr(makeInterrupted()));
|
||||||
|
});
|
||||||
|
|
||||||
|
auto promise = runImpl(std::move(topGoals))
|
||||||
|
.exclusiveJoin(updateStatistics())
|
||||||
|
.exclusiveJoin(std::move(onInterrupt.promise));
|
||||||
|
|
||||||
|
// TODO GC interface?
|
||||||
|
if (auto localStore = dynamic_cast<LocalStore *>(&store); localStore && settings.minFree != 0u) {
|
||||||
|
// Periodically wake up to see if we need to run the garbage collector.
|
||||||
|
promise = promise.exclusiveJoin(boopGC(*localStore));
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise.wait(aio.waitScope).value();
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<Result<Worker::Results>> Worker::runImpl(Targets topGoals)
|
||||||
|
try {
|
||||||
|
debug("entered goal loop");
|
||||||
|
|
||||||
|
kj::Vector<Targets::value_type> promises(topGoals.size());
|
||||||
|
for (auto & gp : topGoals) {
|
||||||
|
promises.add(std::move(gp));
|
||||||
|
}
|
||||||
|
|
||||||
|
Results results;
|
||||||
|
|
||||||
|
auto collect = AsyncCollect(promises.releaseAsArray());
|
||||||
|
while (auto done = co_await collect.next()) {
|
||||||
|
// propagate goal exceptions outward
|
||||||
|
BOOST_OUTCOME_CO_TRY(auto result, done->second);
|
||||||
|
results.emplace(done->first, result);
|
||||||
|
|
||||||
|
/* If a top-level goal failed, then kill all other goals
|
||||||
|
(unless keepGoing was set). */
|
||||||
|
if (result.exitCode == Goal::ecFailed && !settings.keepGoing) {
|
||||||
|
children.clear();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If --keep-going is not set, it's possible that the main goal
|
/* If --keep-going is not set, it's possible that the main goal
|
||||||
exited while some of its subgoals were still active. But if
|
exited while some of its subgoals were still active. But if
|
||||||
--keep-going *is* set, then they must all be finished now. */
|
--keep-going *is* set, then they must all be finished now. */
|
||||||
assert(!settings.keepGoing || awake.empty());
|
assert(!settings.keepGoing || children.isEmpty());
|
||||||
assert(!settings.keepGoing || wantingToBuild.empty());
|
|
||||||
assert(!settings.keepGoing || children.empty());
|
co_return std::move(results);
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::failure(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Worker::waitForInput()
|
kj::Promise<Result<Worker::Results>> Worker::boopGC(LocalStore & localStore)
|
||||||
{
|
try {
|
||||||
printMsg(lvlVomit, "waiting for children");
|
while (true) {
|
||||||
|
co_await aio.provider->getTimer().afterDelay(10 * kj::SECONDS);
|
||||||
/* Process output from the file descriptors attached to the
|
localStore.autoGC(false);
|
||||||
children, namely log output and output path creation commands.
|
|
||||||
We also use this to detect child termination: if we get EOF on
|
|
||||||
the logger pipe of a build, we assume that the builder has
|
|
||||||
terminated. */
|
|
||||||
|
|
||||||
bool useTimeout = false;
|
|
||||||
long timeout = 0;
|
|
||||||
auto before = steady_time_point::clock::now();
|
|
||||||
|
|
||||||
/* If we're monitoring for silence on stdout/stderr, or if there
|
|
||||||
is a build timeout, then wait for input until the first
|
|
||||||
deadline for any child. */
|
|
||||||
auto nearest = steady_time_point::max(); // nearest deadline
|
|
||||||
if (settings.minFree.get() != 0)
|
|
||||||
// Periodicallty wake up to see if we need to run the garbage collector.
|
|
||||||
nearest = before + std::chrono::seconds(10);
|
|
||||||
for (auto & i : children) {
|
|
||||||
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());
|
|
||||||
useTimeout = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If we are polling goals that are waiting for a lock, then wake
|
|
||||||
up after a few seconds at most. */
|
|
||||||
if (!waitingForAWhile.empty()) {
|
|
||||||
useTimeout = true;
|
|
||||||
if (lastWokenUp == steady_time_point::min() || lastWokenUp > before) lastWokenUp = before;
|
|
||||||
timeout = std::max(1L,
|
|
||||||
(long) std::chrono::duration_cast<std::chrono::seconds>(
|
|
||||||
lastWokenUp + std::chrono::seconds(settings.pollInterval) - before).count());
|
|
||||||
} else lastWokenUp = steady_time_point::min();
|
|
||||||
|
|
||||||
if (useTimeout)
|
|
||||||
vomit("sleeping %d seconds", timeout);
|
|
||||||
|
|
||||||
/* Use select() to wait for the input side of any logger pipe to
|
|
||||||
become `available'. Note that `available' (i.e., non-blocking)
|
|
||||||
includes EOF. */
|
|
||||||
std::vector<struct pollfd> pollStatus;
|
|
||||||
std::map<int, size_t> fdToPollStatus;
|
|
||||||
for (auto & i : children) {
|
|
||||||
for (auto & j : i.fds) {
|
|
||||||
pollStatus.push_back((struct pollfd) { .fd = j, .events = POLLIN });
|
|
||||||
fdToPollStatus[j] = pollStatus.size() - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (poll(pollStatus.data(), pollStatus.size(),
|
|
||||||
useTimeout ? timeout * 1000 : -1) == -1) {
|
|
||||||
if (errno == EINTR) return;
|
|
||||||
throw SysError("waiting for input");
|
|
||||||
}
|
|
||||||
|
|
||||||
auto after = steady_time_point::clock::now();
|
|
||||||
|
|
||||||
/* Process all available file descriptors. FIXME: this is
|
|
||||||
O(children * fds). */
|
|
||||||
decltype(children)::iterator i;
|
|
||||||
for (auto j = children.begin(); j != children.end(); j = i) {
|
|
||||||
i = std::next(j);
|
|
||||||
|
|
||||||
checkInterrupt();
|
|
||||||
|
|
||||||
GoalPtr goal = j->goal.lock();
|
|
||||||
assert(goal);
|
|
||||||
|
|
||||||
if (!goal->exitCode.has_value() &&
|
|
||||||
0 != settings.maxSilentTime &&
|
|
||||||
goal->respectsTimeouts() &&
|
|
||||||
after - j->lastOutput >= std::chrono::seconds(settings.maxSilentTime))
|
|
||||||
{
|
|
||||||
handleWorkResult(
|
|
||||||
goal,
|
|
||||||
goal->timedOut(Error(
|
|
||||||
"%1% timed out after %2% seconds of silence",
|
|
||||||
goal->getName(),
|
|
||||||
settings.maxSilentTime
|
|
||||||
))
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (!goal->exitCode.has_value() &&
|
|
||||||
0 != settings.buildTimeout &&
|
|
||||||
goal->respectsTimeouts() &&
|
|
||||||
after - j->timeStarted >= std::chrono::seconds(settings.buildTimeout))
|
|
||||||
{
|
|
||||||
handleWorkResult(
|
|
||||||
goal,
|
|
||||||
goal->timedOut(
|
|
||||||
Error("%1% timed out after %2% seconds", goal->getName(), settings.buildTimeout)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::set<int> fds2(j->fds);
|
|
||||||
std::vector<unsigned char> buffer(4096);
|
|
||||||
for (auto & k : fds2) {
|
|
||||||
const auto fdPollStatusId = get(fdToPollStatus, k);
|
|
||||||
assert(fdPollStatusId);
|
|
||||||
assert(*fdPollStatusId < pollStatus.size());
|
|
||||||
if (pollStatus.at(*fdPollStatusId).revents) {
|
|
||||||
ssize_t rd = ::read(k, buffer.data(), buffer.size());
|
|
||||||
// FIXME: is there a cleaner way to handle pt close
|
|
||||||
// than EIO? Is this even standard?
|
|
||||||
if (rd == 0 || (rd == -1 && errno == EIO)) {
|
|
||||||
debug("%1%: got EOF", goal->getName());
|
|
||||||
goal->handleEOF(k);
|
|
||||||
handleWorkResult(goal, Goal::ContinueImmediately{});
|
|
||||||
j->fds.erase(k);
|
|
||||||
} else if (rd == -1) {
|
|
||||||
if (errno != EINTR)
|
|
||||||
throw SysError("%s: read failed", goal->getName());
|
|
||||||
} else {
|
|
||||||
printMsg(lvlVomit, "%1%: read %2% bytes",
|
|
||||||
goal->getName(), rd);
|
|
||||||
std::string_view data(charptr_cast<char *>(buffer.data()), rd);
|
|
||||||
j->lastOutput = after;
|
|
||||||
handleWorkResult(goal, goal->handleChildOutput(k, data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!waitingForAWhile.empty() && lastWokenUp + std::chrono::seconds(settings.pollInterval) <= after) {
|
|
||||||
lastWokenUp = after;
|
|
||||||
for (auto & i : waitingForAWhile) {
|
|
||||||
GoalPtr goal = i.lock();
|
|
||||||
if (goal) wakeUp(goal);
|
|
||||||
}
|
|
||||||
waitingForAWhile.clear();
|
|
||||||
}
|
}
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::failure(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
|
#include "async-semaphore.hh"
|
||||||
|
#include "concepts.hh"
|
||||||
|
#include "notifying-counter.hh"
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
#include "lock.hh"
|
#include "lock.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
@ -8,6 +11,7 @@
|
||||||
#include "realisation.hh"
|
#include "realisation.hh"
|
||||||
|
|
||||||
#include <future>
|
#include <future>
|
||||||
|
#include <kj/async-io.h>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
@ -16,88 +20,91 @@ namespace nix {
|
||||||
struct DerivationGoal;
|
struct DerivationGoal;
|
||||||
struct PathSubstitutionGoal;
|
struct PathSubstitutionGoal;
|
||||||
class DrvOutputSubstitutionGoal;
|
class DrvOutputSubstitutionGoal;
|
||||||
|
class LocalStore;
|
||||||
|
|
||||||
typedef std::chrono::time_point<std::chrono::steady_clock> steady_time_point;
|
typedef std::chrono::time_point<std::chrono::steady_clock> steady_time_point;
|
||||||
|
|
||||||
/**
|
|
||||||
* A mapping used to remember for each child process to what goal it
|
|
||||||
* belongs, and file descriptors for receiving log data and output
|
|
||||||
* path creation commands.
|
|
||||||
*/
|
|
||||||
struct Child
|
|
||||||
{
|
|
||||||
WeakGoalPtr goal;
|
|
||||||
Goal * goal2; // ugly hackery
|
|
||||||
std::set<int> fds;
|
|
||||||
bool inBuildSlot;
|
|
||||||
/**
|
|
||||||
* Time we last got output on stdout/stderr
|
|
||||||
*/
|
|
||||||
steady_time_point lastOutput;
|
|
||||||
steady_time_point timeStarted;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Forward definition. */
|
/* Forward definition. */
|
||||||
struct HookInstance;
|
struct HookInstance;
|
||||||
|
|
||||||
|
class GoalFactory
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual std::pair<std::shared_ptr<DerivationGoal>, kj::Promise<Result<Goal::WorkResult>>>
|
||||||
|
makeDerivationGoal(
|
||||||
|
const StorePath & drvPath, const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal
|
||||||
|
) = 0;
|
||||||
|
virtual std::pair<std::shared_ptr<DerivationGoal>, kj::Promise<Result<Goal::WorkResult>>>
|
||||||
|
makeBasicDerivationGoal(
|
||||||
|
const StorePath & drvPath,
|
||||||
|
const BasicDerivation & drv,
|
||||||
|
const OutputsSpec & wantedOutputs,
|
||||||
|
BuildMode buildMode = bmNormal
|
||||||
|
) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ref SubstitutionGoal "substitution goal"
|
||||||
|
*/
|
||||||
|
virtual std::pair<std::shared_ptr<PathSubstitutionGoal>, kj::Promise<Result<Goal::WorkResult>>>
|
||||||
|
makePathSubstitutionGoal(
|
||||||
|
const StorePath & storePath,
|
||||||
|
RepairFlag repair = NoRepair,
|
||||||
|
std::optional<ContentAddress> ca = std::nullopt
|
||||||
|
) = 0;
|
||||||
|
virtual std::pair<std::shared_ptr<DrvOutputSubstitutionGoal>, kj::Promise<Result<Goal::WorkResult>>>
|
||||||
|
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 std::pair<GoalPtr, kj::Promise<Result<Goal::WorkResult>>>
|
||||||
|
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.
|
* The worker class.
|
||||||
*/
|
*/
|
||||||
class Worker
|
class Worker : public WorkerBase
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
|
using Targets = std::map<GoalPtr, kj::Promise<Result<Goal::WorkResult>>>;
|
||||||
|
using Results = std::map<GoalPtr, Goal::WorkResult>;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/* Note: the worker should only have strong pointers to the
|
bool running = false;
|
||||||
top-level goals. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The top-level goals of the worker.
|
|
||||||
*/
|
|
||||||
Goals topGoals;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Goals that are ready to do some work.
|
|
||||||
*/
|
|
||||||
WeakGoals awake;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Goals waiting for a build slot.
|
|
||||||
*/
|
|
||||||
WeakGoals wantingToBuild;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Child processes currently running.
|
|
||||||
*/
|
|
||||||
std::list<Child> children;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Number of build slots occupied. This includes local builds but does not
|
|
||||||
* include substitutions or remote builds via the build hook.
|
|
||||||
*/
|
|
||||||
unsigned int nrLocalBuilds;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Number of substitution slots occupied.
|
|
||||||
*/
|
|
||||||
unsigned int nrSubstitutions;
|
|
||||||
|
|
||||||
|
template<typename G>
|
||||||
|
struct CachedGoal
|
||||||
|
{
|
||||||
|
std::shared_ptr<G> goal;
|
||||||
|
kj::ForkedPromise<Result<Goal::WorkResult>> promise{nullptr};
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* Maps used to prevent multiple instantiations of a goal for the
|
* Maps used to prevent multiple instantiations of a goal for the
|
||||||
* same derivation / path.
|
* same derivation / path.
|
||||||
*/
|
*/
|
||||||
std::map<StorePath, std::weak_ptr<DerivationGoal>> derivationGoals;
|
std::map<StorePath, CachedGoal<DerivationGoal>> derivationGoals;
|
||||||
std::map<StorePath, std::weak_ptr<PathSubstitutionGoal>> substitutionGoals;
|
std::map<StorePath, CachedGoal<PathSubstitutionGoal>> substitutionGoals;
|
||||||
std::map<DrvOutput, std::weak_ptr<DrvOutputSubstitutionGoal>> drvOutputSubstitutionGoals;
|
std::map<DrvOutput, CachedGoal<DrvOutputSubstitutionGoal>> drvOutputSubstitutionGoals;
|
||||||
|
|
||||||
/**
|
|
||||||
* Goals sleeping for a few seconds (polling a lock).
|
|
||||||
*/
|
|
||||||
WeakGoals waitingForAWhile;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Last time the goals in `waitingForAWhile` where woken up.
|
|
||||||
*/
|
|
||||||
steady_time_point lastWokenUp;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache for pathContentsGood().
|
* Cache for pathContentsGood().
|
||||||
|
@ -125,44 +132,24 @@ private:
|
||||||
*/
|
*/
|
||||||
bool checkMismatch = false;
|
bool checkMismatch = false;
|
||||||
|
|
||||||
void goalFinished(GoalPtr goal, Goal::Finished & f);
|
/**
|
||||||
void handleWorkResult(GoalPtr goal, Goal::WorkResult how);
|
* Pass current stats counters to the logger for progress bar updates.
|
||||||
|
*/
|
||||||
|
kj::Promise<Result<Results>> updateStatistics();
|
||||||
|
|
||||||
|
AsyncSemaphore statisticsUpdateSignal{1};
|
||||||
|
std::optional<AsyncSemaphore::Token> statisticsUpdateInhibitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Put `goal` to sleep until a build slot becomes available (which
|
* Mark statistics as outdated, such that `updateStatistics` will be called.
|
||||||
* might be right away).
|
|
||||||
*/
|
*/
|
||||||
void waitForBuildSlot(GoalPtr goal);
|
void updateStatisticsLater()
|
||||||
|
{
|
||||||
|
statisticsUpdateInhibitor = {};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
kj::Promise<Result<Results>> runImpl(Targets topGoals);
|
||||||
* Wait for a few seconds and then retry this goal. Used when
|
kj::Promise<Result<Results>> boopGC(LocalStore & localStore);
|
||||||
* waiting for a lock held by another process. This kind of
|
|
||||||
* polling is inefficient, but POSIX doesn't really provide a way
|
|
||||||
* to wait for multiple locks in the main select() loop.
|
|
||||||
*/
|
|
||||||
void waitForAWhile(GoalPtr goal);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wake up a goal (i.e., there is something for it to do).
|
|
||||||
*/
|
|
||||||
void wakeUp(GoalPtr goal);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wait for input to become available.
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
@ -172,7 +159,13 @@ public:
|
||||||
|
|
||||||
Store & store;
|
Store & store;
|
||||||
Store & evalStore;
|
Store & evalStore;
|
||||||
|
kj::AsyncIoContext & aio;
|
||||||
|
AsyncSemaphore substitutions, localBuilds;
|
||||||
|
|
||||||
|
private:
|
||||||
|
kj::TaskSet children;
|
||||||
|
|
||||||
|
public:
|
||||||
struct HookState {
|
struct HookState {
|
||||||
std::unique_ptr<HookInstance> instance;
|
std::unique_ptr<HookInstance> instance;
|
||||||
|
|
||||||
|
@ -185,21 +178,21 @@ public:
|
||||||
|
|
||||||
HookState hook;
|
HookState hook;
|
||||||
|
|
||||||
uint64_t expectedBuilds = 0;
|
NotifyingCounter<uint64_t> expectedBuilds{[this] { updateStatisticsLater(); }};
|
||||||
uint64_t doneBuilds = 0;
|
NotifyingCounter<uint64_t> doneBuilds{[this] { updateStatisticsLater(); }};
|
||||||
uint64_t failedBuilds = 0;
|
NotifyingCounter<uint64_t> failedBuilds{[this] { updateStatisticsLater(); }};
|
||||||
uint64_t runningBuilds = 0;
|
NotifyingCounter<uint64_t> runningBuilds{[this] { updateStatisticsLater(); }};
|
||||||
|
|
||||||
uint64_t expectedSubstitutions = 0;
|
NotifyingCounter<uint64_t> expectedSubstitutions{[this] { updateStatisticsLater(); }};
|
||||||
uint64_t doneSubstitutions = 0;
|
NotifyingCounter<uint64_t> doneSubstitutions{[this] { updateStatisticsLater(); }};
|
||||||
uint64_t failedSubstitutions = 0;
|
NotifyingCounter<uint64_t> failedSubstitutions{[this] { updateStatisticsLater(); }};
|
||||||
uint64_t runningSubstitutions = 0;
|
NotifyingCounter<uint64_t> runningSubstitutions{[this] { updateStatisticsLater(); }};
|
||||||
uint64_t expectedDownloadSize = 0;
|
NotifyingCounter<uint64_t> expectedDownloadSize{[this] { updateStatisticsLater(); }};
|
||||||
uint64_t doneDownloadSize = 0;
|
NotifyingCounter<uint64_t> doneDownloadSize{[this] { updateStatisticsLater(); }};
|
||||||
uint64_t expectedNarSize = 0;
|
NotifyingCounter<uint64_t> expectedNarSize{[this] { updateStatisticsLater(); }};
|
||||||
uint64_t doneNarSize = 0;
|
NotifyingCounter<uint64_t> doneNarSize{[this] { updateStatisticsLater(); }};
|
||||||
|
|
||||||
Worker(Store & store, Store & evalStore);
|
Worker(Store & store, Store & evalStore, kj::AsyncIoContext & aio);
|
||||||
~Worker();
|
~Worker();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -210,22 +203,35 @@ public:
|
||||||
* @ref DerivationGoal "derivation goal"
|
* @ref DerivationGoal "derivation goal"
|
||||||
*/
|
*/
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<DerivationGoal> makeDerivationGoalCommon(
|
template<typename ID, std::derived_from<Goal> G>
|
||||||
const StorePath & drvPath, const OutputsSpec & wantedOutputs,
|
std::pair<std::shared_ptr<G>, kj::Promise<Result<Goal::WorkResult>>> makeGoalCommon(
|
||||||
std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal);
|
std::map<ID, CachedGoal<G>> & map,
|
||||||
public:
|
const ID & key,
|
||||||
std::shared_ptr<DerivationGoal> makeDerivationGoal(
|
InvocableR<std::unique_ptr<G>> auto create,
|
||||||
|
InvocableR<bool, G &> auto modify
|
||||||
|
);
|
||||||
|
std::pair<std::shared_ptr<DerivationGoal>, kj::Promise<Result<Goal::WorkResult>>> makeDerivationGoal(
|
||||||
const StorePath & drvPath,
|
const StorePath & drvPath,
|
||||||
const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal);
|
const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal) override;
|
||||||
std::shared_ptr<DerivationGoal> makeBasicDerivationGoal(
|
std::pair<std::shared_ptr<DerivationGoal>, kj::Promise<Result<Goal::WorkResult>>> makeBasicDerivationGoal(
|
||||||
const StorePath & drvPath, const BasicDerivation & drv,
|
const StorePath & drvPath, const BasicDerivation & drv,
|
||||||
const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal);
|
const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal) override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ref SubstitutionGoal "substitution goal"
|
* @ref SubstitutionGoal "substitution goal"
|
||||||
*/
|
*/
|
||||||
std::shared_ptr<PathSubstitutionGoal> makePathSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
std::pair<std::shared_ptr<PathSubstitutionGoal>, kj::Promise<Result<Goal::WorkResult>>>
|
||||||
std::shared_ptr<DrvOutputSubstitutionGoal> makeDrvOutputSubstitutionGoal(const DrvOutput & id, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
makePathSubstitutionGoal(
|
||||||
|
const StorePath & storePath,
|
||||||
|
RepairFlag repair = NoRepair,
|
||||||
|
std::optional<ContentAddress> ca = std::nullopt
|
||||||
|
) override;
|
||||||
|
std::pair<std::shared_ptr<DrvOutputSubstitutionGoal>, kj::Promise<Result<Goal::WorkResult>>>
|
||||||
|
makeDrvOutputSubstitutionGoal(
|
||||||
|
const DrvOutput & id,
|
||||||
|
RepairFlag repair = NoRepair,
|
||||||
|
std::optional<ContentAddress> ca = std::nullopt
|
||||||
|
) override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a goal corresponding to the `DerivedPath`.
|
* Make a goal corresponding to the `DerivedPath`.
|
||||||
|
@ -233,17 +239,14 @@ public:
|
||||||
* It will be a `DerivationGoal` for a `DerivedPath::Built` or
|
* It will be a `DerivationGoal` for a `DerivedPath::Built` or
|
||||||
* a `SubstitutionGoal` for a `DerivedPath::Opaque`.
|
* a `SubstitutionGoal` for a `DerivedPath::Opaque`.
|
||||||
*/
|
*/
|
||||||
GoalPtr makeGoal(const DerivedPath & req, BuildMode buildMode = bmNormal);
|
std::pair<GoalPtr, kj::Promise<Result<Goal::WorkResult>>>
|
||||||
|
makeGoal(const DerivedPath & req, BuildMode buildMode = bmNormal) override;
|
||||||
/**
|
|
||||||
* Unregisters a running child process.
|
|
||||||
*/
|
|
||||||
void childTerminated(Goal * goal);
|
|
||||||
|
|
||||||
|
public:
|
||||||
/**
|
/**
|
||||||
* Loop until the specified top-level goals have finished.
|
* Loop until the specified top-level goals have finished.
|
||||||
*/
|
*/
|
||||||
void run(const Goals & topGoals);
|
Results run(std::function<Targets (GoalFactory &)> req);
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* The exit status in case of failure.
|
* The exit status in case of failure.
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
// TODO: make pluggable.
|
// TODO: make pluggable.
|
||||||
void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData);
|
void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData, const std::string & caFileData);
|
||||||
void builtinUnpackChannel(const BasicDerivation & drv);
|
void builtinUnpackChannel(const BasicDerivation & drv);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "buildenv.hh"
|
#include "buildenv.hh"
|
||||||
|
#include "strings.hh"
|
||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
|
@ -3,10 +3,11 @@
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "archive.hh"
|
#include "archive.hh"
|
||||||
#include "compression.hh"
|
#include "compression.hh"
|
||||||
|
#include "strings.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData)
|
void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData, const std::string & caFileData)
|
||||||
{
|
{
|
||||||
/* Make the host's netrc data available. Too bad curl requires
|
/* Make the host's netrc data available. Too bad curl requires
|
||||||
this to be stored in a file. It would be nice if we could just
|
this to be stored in a file. It would be nice if we could just
|
||||||
|
@ -16,6 +17,9 @@ void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData)
|
||||||
writeFile(settings.netrcFile, netrcData, 0600);
|
writeFile(settings.netrcFile, netrcData, 0600);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
settings.caFile = "ca-certificates.crt";
|
||||||
|
writeFile(settings.caFile, caFileData, 0600);
|
||||||
|
|
||||||
auto getAttr = [&](const std::string & name) {
|
auto getAttr = [&](const std::string & name) {
|
||||||
auto i = drv.env.find(name);
|
auto i = drv.env.find(name);
|
||||||
if (i == drv.env.end()) throw Error("attribute '%s' missing", name);
|
if (i == drv.env.end()) throw Error("attribute '%s' missing", name);
|
||||||
|
@ -32,10 +36,7 @@ void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData)
|
||||||
|
|
||||||
auto fetch = [&](const std::string & url) {
|
auto fetch = [&](const std::string & url) {
|
||||||
|
|
||||||
/* No need to do TLS verification, because we check the hash of
|
|
||||||
the result anyway. */
|
|
||||||
FileTransferRequest request(url);
|
FileTransferRequest request(url);
|
||||||
request.verifyTLS = false;
|
|
||||||
|
|
||||||
auto raw = fileTransfer->download(std::move(request));
|
auto raw = fileTransfer->download(std::move(request));
|
||||||
auto decompressor = makeDecompressionSource(
|
auto decompressor = makeDecompressionSource(
|
||||||
|
|
|
@ -20,6 +20,7 @@ namespace nix {
|
||||||
{ \
|
{ \
|
||||||
return LengthPrefixedProtoHelper<CommonProto, T >::read(store, conn); \
|
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) \
|
TEMPLATE [[nodiscard]] WireFormatGenerator CommonProto::Serialise< T >::write(const Store & store, CommonProto::WriteConn conn, const T & t) \
|
||||||
{ \
|
{ \
|
||||||
return LengthPrefixedProtoHelper<CommonProto, T >::write(store, conn, t); \
|
return LengthPrefixedProtoHelper<CommonProto, T >::write(store, conn, t); \
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "args.hh"
|
#include "args.hh"
|
||||||
#include "content-address.hh"
|
#include "content-address.hh"
|
||||||
#include "split.hh"
|
#include "split.hh"
|
||||||
|
#include "strings.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#include "monitor-fd.hh"
|
#include "monitor-fd.hh"
|
||||||
#include "worker-protocol.hh"
|
#include "worker-protocol.hh"
|
||||||
#include "worker-protocol-impl.hh"
|
#include "worker-protocol-impl.hh"
|
||||||
#include "build-result.hh"
|
#include "build-result.hh" // IWYU pragma: keep
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "store-cast.hh"
|
#include "store-cast.hh"
|
||||||
#include "gc-store.hh"
|
#include "gc-store.hh"
|
||||||
|
@ -12,6 +12,7 @@
|
||||||
#include "finally.hh"
|
#include "finally.hh"
|
||||||
#include "archive.hh"
|
#include "archive.hh"
|
||||||
#include "derivations.hh"
|
#include "derivations.hh"
|
||||||
|
#include "strings.hh"
|
||||||
#include "args.hh"
|
#include "args.hh"
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
|
@ -3,11 +3,12 @@
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
#include "split.hh"
|
|
||||||
#include "common-protocol.hh"
|
#include "common-protocol.hh"
|
||||||
#include "common-protocol-impl.hh"
|
#include "common-protocol-impl.hh"
|
||||||
#include "fs-accessor.hh"
|
|
||||||
#include "json-utils.hh"
|
#include "json-utils.hh"
|
||||||
|
#include "strings.hh"
|
||||||
|
#include "backed-string-view.hh"
|
||||||
|
|
||||||
#include <boost/container/small_vector.hpp>
|
#include <boost/container/small_vector.hpp>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "path.hh"
|
#include "path.hh"
|
||||||
#include "outputs-spec.hh"
|
#include "outputs-spec.hh"
|
||||||
#include "comparator.hh"
|
#include "comparator.hh"
|
||||||
|
#include "ref.hh"
|
||||||
|
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
|
@ -78,10 +79,12 @@ struct SingleDerivedPathBuilt {
|
||||||
DECLARE_CMP(SingleDerivedPathBuilt);
|
DECLARE_CMP(SingleDerivedPathBuilt);
|
||||||
};
|
};
|
||||||
|
|
||||||
using _SingleDerivedPathRaw = std::variant<
|
namespace derived_path::detail {
|
||||||
|
using SingleDerivedPathRaw = std::variant<
|
||||||
DerivedPathOpaque,
|
DerivedPathOpaque,
|
||||||
SingleDerivedPathBuilt
|
SingleDerivedPathBuilt
|
||||||
>;
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A "derived path" is a very simple sort of expression (not a Nix
|
* 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
|
* - built, in which case it is a pair of a derivation path and an
|
||||||
* output name.
|
* output name.
|
||||||
*/
|
*/
|
||||||
struct SingleDerivedPath : _SingleDerivedPathRaw {
|
struct SingleDerivedPath : derived_path::detail::SingleDerivedPathRaw {
|
||||||
using Raw = _SingleDerivedPathRaw;
|
using Raw = derived_path::detail::SingleDerivedPathRaw;
|
||||||
using Raw::Raw;
|
using Raw::Raw;
|
||||||
|
|
||||||
using Opaque = DerivedPathOpaque;
|
using Opaque = DerivedPathOpaque;
|
||||||
|
@ -201,10 +204,12 @@ struct DerivedPathBuilt {
|
||||||
DECLARE_CMP(DerivedPathBuilt);
|
DECLARE_CMP(DerivedPathBuilt);
|
||||||
};
|
};
|
||||||
|
|
||||||
using _DerivedPathRaw = std::variant<
|
namespace derived_path::detail {
|
||||||
|
using DerivedPathRaw = std::variant<
|
||||||
DerivedPathOpaque,
|
DerivedPathOpaque,
|
||||||
DerivedPathBuilt
|
DerivedPathBuilt
|
||||||
>;
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A "derived path" is a very simple sort of expression that evaluates
|
* 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
|
* - built, in which case it is a pair of a derivation path and some
|
||||||
* output names.
|
* output names.
|
||||||
*/
|
*/
|
||||||
struct DerivedPath : _DerivedPathRaw {
|
struct DerivedPath : derived_path::detail::DerivedPathRaw {
|
||||||
using Raw = _DerivedPathRaw;
|
using Raw = derived_path::detail::DerivedPathRaw;
|
||||||
using Raw::Raw;
|
using Raw::Raw;
|
||||||
|
|
||||||
using Opaque = DerivedPathOpaque;
|
using Opaque = DerivedPathOpaque;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#include "dummy-store.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
@ -73,6 +74,8 @@ struct DummyStore : public virtual DummyStoreConfig, public virtual Store
|
||||||
{ unsupported("getFSAccessor"); }
|
{ unsupported("getFSAccessor"); }
|
||||||
};
|
};
|
||||||
|
|
||||||
static RegisterStoreImplementation<DummyStore, DummyStoreConfig> regDummyStore;
|
void registerDummyStore() {
|
||||||
|
StoreImplementations::add<DummyStore, DummyStoreConfig>();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
8
src/libstore/dummy-store.hh
Normal file
8
src/libstore/dummy-store.hh
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#pragma once
|
||||||
|
///@file
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
void registerDummyStore();
|
||||||
|
|
||||||
|
}
|
|
@ -5,6 +5,8 @@
|
||||||
#include "s3.hh"
|
#include "s3.hh"
|
||||||
#include "signals.hh"
|
#include "signals.hh"
|
||||||
#include "compression.hh"
|
#include "compression.hh"
|
||||||
|
#include "strings.hh"
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
#if ENABLE_S3
|
#if ENABLE_S3
|
||||||
#include <aws/core/client/ClientConfiguration.h>
|
#include <aws/core/client/ClientConfiguration.h>
|
||||||
|
@ -114,7 +116,7 @@ struct curlFileTransfer : public FileTransfer
|
||||||
if (!done)
|
if (!done)
|
||||||
fail(FileTransferError(Interrupted, {}, "download of '%s' was interrupted", request.uri));
|
fail(FileTransferError(Interrupted, {}, "download of '%s' was interrupted", request.uri));
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
ignoreException();
|
ignoreExceptionInDestructor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,7 +338,7 @@ struct curlFileTransfer : public FileTransfer
|
||||||
// wrapping user `callback`s instead is not possible because the
|
// wrapping user `callback`s instead is not possible because the
|
||||||
// Callback api expects std::functions, and copying Callbacks is
|
// Callback api expects std::functions, and copying Callbacks is
|
||||||
// not possible due the promises they hold.
|
// not possible due the promises they hold.
|
||||||
if (code == CURLE_OK && !dataCallback) {
|
if (code == CURLE_OK && !dataCallback && result.data.length() > 0) {
|
||||||
result.data = decompress(encoding, result.data);
|
result.data = decompress(encoding, result.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -783,8 +785,10 @@ struct curlFileTransfer : public FileTransfer
|
||||||
|
|
||||||
size_t read(char * data, size_t len) override
|
size_t read(char * data, size_t len) override
|
||||||
{
|
{
|
||||||
auto readPartial = [this](char * data, size_t len) {
|
auto readPartial = [this](char * data, size_t len) -> size_t {
|
||||||
const auto available = std::min(len, buffered.size());
|
const auto available = std::min(len, buffered.size());
|
||||||
|
if (available == 0u) return 0u;
|
||||||
|
|
||||||
memcpy(data, buffered.data(), available);
|
memcpy(data, buffered.data(), available);
|
||||||
buffered.remove_prefix(available);
|
buffered.remove_prefix(available);
|
||||||
return available;
|
return available;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include "box_ptr.hh"
|
#include "box_ptr.hh"
|
||||||
|
#include "ref.hh"
|
||||||
#include "logging.hh"
|
#include "logging.hh"
|
||||||
#include "serialise.hh"
|
#include "serialise.hh"
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "signals.hh"
|
#include "signals.hh"
|
||||||
#include "finally.hh"
|
#include "finally.hh"
|
||||||
#include "unix-domain-socket.hh"
|
#include "unix-domain-socket.hh"
|
||||||
|
#include "strings.hh"
|
||||||
|
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
@ -922,8 +923,8 @@ void LocalStore::autoGC(bool sync)
|
||||||
|
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
// FIXME: we could propagate the exception to the
|
// FIXME: we could propagate the exception to the
|
||||||
// future, but we don't really care.
|
// future, but we don't really care. (what??)
|
||||||
ignoreException();
|
ignoreExceptionInDestructor();
|
||||||
}
|
}
|
||||||
|
|
||||||
}).detach();
|
}).detach();
|
||||||
|
|
|
@ -33,6 +33,16 @@
|
||||||
#include <sys/sysctl.h>
|
#include <sys/sysctl.h>
|
||||||
#endif
|
#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 {
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
|
@ -116,29 +126,30 @@ Settings::Settings()
|
||||||
|
|
||||||
void loadConfFile()
|
void loadConfFile()
|
||||||
{
|
{
|
||||||
auto applyConfigFile = [&](const Path & path) {
|
auto applyConfigFile = [&](const ApplyConfigOptions & options) {
|
||||||
try {
|
try {
|
||||||
std::string contents = readFile(path);
|
std::string contents = readFile(*options.path);
|
||||||
globalConfig.applyConfig(contents, path);
|
globalConfig.applyConfig(contents, options);
|
||||||
} catch (SysError &) { }
|
} 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
|
/* We only want to send overrides to the daemon, i.e. stuff from
|
||||||
~/.nix/nix.conf or the command line. */
|
~/.nix/nix.conf or the command line. */
|
||||||
globalConfig.resetOverridden();
|
globalConfig.resetOverridden();
|
||||||
|
|
||||||
auto files = settings.nixUserConfFiles;
|
auto files = settings.nixUserConfFiles;
|
||||||
|
auto home = getHome();
|
||||||
for (auto file = files.rbegin(); file != files.rend(); file++) {
|
for (auto file = files.rbegin(); file != files.rend(); file++) {
|
||||||
applyConfigFile(*file);
|
applyConfigFile(ApplyConfigOptions{.path = *file, .home = home});
|
||||||
}
|
}
|
||||||
|
|
||||||
auto nixConfEnv = getEnv("NIX_CONFIG");
|
auto nixConfEnv = getEnv("NIX_CONFIG");
|
||||||
if (nixConfEnv.has_value()) {
|
if (nixConfEnv.has_value()) {
|
||||||
globalConfig.applyConfig(nixConfEnv.value(), "NIX_CONFIG");
|
globalConfig.applyConfig(nixConfEnv.value(), ApplyConfigOptions{.fromEnvVar = true});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Path> getUserConfigFiles()
|
std::vector<Path> getUserConfigFiles()
|
||||||
|
@ -258,13 +269,33 @@ Path Settings::getDefaultSSLCertFile()
|
||||||
|
|
||||||
const std::string nixVersion = PACKAGE_VERSION;
|
const std::string nixVersion = PACKAGE_VERSION;
|
||||||
|
|
||||||
NLOHMANN_JSON_SERIALIZE_ENUM(SandboxMode, {
|
void to_json(nlohmann::json & j, const SandboxMode & e)
|
||||||
{SandboxMode::smEnabled, true},
|
{
|
||||||
{SandboxMode::smRelaxed, "relaxed"},
|
if (e == SandboxMode::smEnabled) {
|
||||||
{SandboxMode::smDisabled, false},
|
j = true;
|
||||||
});
|
} else if (e == SandboxMode::smRelaxed) {
|
||||||
|
j = "relaxed";
|
||||||
|
} else if (e == SandboxMode::smDisabled) {
|
||||||
|
j = false;
|
||||||
|
} else {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template<> SandboxMode BaseSetting<SandboxMode>::parse(const std::string & str) const
|
void from_json(const nlohmann::json & j, SandboxMode & e)
|
||||||
|
{
|
||||||
|
if (j == true) {
|
||||||
|
e = SandboxMode::smEnabled;
|
||||||
|
} else if (j == "relaxed") {
|
||||||
|
e = SandboxMode::smRelaxed;
|
||||||
|
} else if (j == false) {
|
||||||
|
e = SandboxMode::smDisabled;
|
||||||
|
} else {
|
||||||
|
throw Error("Invalid sandbox mode '%s'", std::string(j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> SandboxMode BaseSetting<SandboxMode>::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||||
{
|
{
|
||||||
if (str == "true") return smEnabled;
|
if (str == "true") return smEnabled;
|
||||||
else if (str == "relaxed") return smRelaxed;
|
else if (str == "relaxed") return smRelaxed;
|
||||||
|
@ -307,7 +338,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());
|
if (str == "auto") return std::max(1U, std::thread::hardware_concurrency());
|
||||||
else {
|
else {
|
||||||
|
@ -319,11 +350,11 @@ unsigned int MaxBuildJobsSetting::parse(const std::string & str) const
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Paths PluginFilesSetting::parse(const std::string & str) const
|
Paths PluginFilesSetting::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||||
{
|
{
|
||||||
if (pluginsLoaded)
|
if (pluginsLoaded)
|
||||||
throw UsageError("plugin-files set after plugins were loaded, you may need to move the flag before the subcommand");
|
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,12 +427,23 @@ static void preloadNSS()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void registerStoreImplementations() {
|
||||||
|
registerDummyStore();
|
||||||
|
registerHttpBinaryCacheStore();
|
||||||
|
registerLegacySSHStore();
|
||||||
|
registerLocalBinaryCacheStore();
|
||||||
|
registerLocalStore();
|
||||||
|
registerS3BinaryCacheStore();
|
||||||
|
registerSSHStore();
|
||||||
|
registerUDSRemoteStore();
|
||||||
|
}
|
||||||
|
|
||||||
static bool initLibStoreDone = false;
|
static bool initLibStoreDone = false;
|
||||||
|
|
||||||
void assertLibStoreInitialized() {
|
void assertLibStoreInitialized() {
|
||||||
if (!initLibStoreDone) {
|
if (!initLibStoreDone) {
|
||||||
printError("The program must call nix::initNix() before calling any libstore library functions.");
|
printError("The program must call nix::initNix() before calling any libstore library functions.");
|
||||||
abort();
|
std::terminate();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -433,6 +475,8 @@ void initLibStore() {
|
||||||
unsetenv("TMPDIR");
|
unsetenv("TMPDIR");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
registerStoreImplementations();
|
||||||
|
|
||||||
initLibStoreDone = true;
|
initLibStoreDone = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,9 @@ namespace nix {
|
||||||
|
|
||||||
typedef enum { smEnabled, smRelaxed, smDisabled } SandboxMode;
|
typedef enum { smEnabled, smRelaxed, smDisabled } SandboxMode;
|
||||||
|
|
||||||
|
void to_json(nlohmann::json & j, const SandboxMode & e);
|
||||||
|
void from_json(const nlohmann::json & j, SandboxMode & e);
|
||||||
|
|
||||||
struct MaxBuildJobsSetting : public BaseSetting<unsigned int>
|
struct MaxBuildJobsSetting : public BaseSetting<unsigned int>
|
||||||
{
|
{
|
||||||
MaxBuildJobsSetting(Config * options,
|
MaxBuildJobsSetting(Config * options,
|
||||||
|
@ -26,7 +29,7 @@ struct MaxBuildJobsSetting : public BaseSetting<unsigned int>
|
||||||
options->addSetting(this);
|
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>
|
struct PluginFilesSetting : public BaseSetting<Paths>
|
||||||
|
@ -43,7 +46,7 @@ struct PluginFilesSetting : public BaseSetting<Paths>
|
||||||
options->addSetting(this);
|
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 =
|
const uint32_t maxIdsPerBuild =
|
||||||
|
@ -637,10 +640,10 @@ public:
|
||||||
PathsSetting<std::optional<Path>> diffHook{
|
PathsSetting<std::optional<Path>> diffHook{
|
||||||
this, std::nullopt, "diff-hook",
|
this, std::nullopt, "diff-hook",
|
||||||
R"(
|
R"(
|
||||||
Absolute path to an executable capable of diffing build
|
Path to an executable capable of diffing build results. The hook is
|
||||||
results. The hook is executed if `run-diff-hook` is true, and the
|
executed if `run-diff-hook` is true, and the output of a build is
|
||||||
output of a build is known to not be the same. This program is not
|
known to not be the same. This program is not executed to determine
|
||||||
executed to determine if two results are the same.
|
if two results are the same.
|
||||||
|
|
||||||
The diff hook is executed by the same user and group who ran the
|
The diff hook is executed by the same user and group who ran the
|
||||||
build. However, the diff hook does not have write access to the
|
build. However, the diff hook does not have write access to the
|
||||||
|
@ -1088,6 +1091,7 @@ void loadConfFile();
|
||||||
|
|
||||||
// Used by the Settings constructor
|
// Used by the Settings constructor
|
||||||
std::vector<Path> getUserConfigFiles();
|
std::vector<Path> getUserConfigFiles();
|
||||||
|
std::vector<Path> getHomeConfigFile();
|
||||||
|
|
||||||
extern const std::string nixVersion;
|
extern const std::string nixVersion;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#include "http-binary-cache-store.hh"
|
||||||
#include "binary-cache-store.hh"
|
#include "binary-cache-store.hh"
|
||||||
#include "filetransfer.hh"
|
#include "filetransfer.hh"
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
|
@ -194,6 +195,8 @@ protected:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static RegisterStoreImplementation<HttpBinaryCacheStore, HttpBinaryCacheStoreConfig> regHttpBinaryCacheStore;
|
void registerHttpBinaryCacheStore() {
|
||||||
|
StoreImplementations::add<HttpBinaryCacheStore, HttpBinaryCacheStoreConfig>();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
8
src/libstore/http-binary-cache-store.hh
Normal file
8
src/libstore/http-binary-cache-store.hh
Normal file
|
@ -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 "archive.hh"
|
||||||
#include "pool.hh"
|
#include "pool.hh"
|
||||||
#include "remote-store.hh"
|
#include "remote-store.hh"
|
||||||
|
@ -8,6 +8,8 @@
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "path-with-outputs.hh"
|
#include "path-with-outputs.hh"
|
||||||
#include "ssh.hh"
|
#include "ssh.hh"
|
||||||
|
#include "ssh-store.hh"
|
||||||
|
#include "strings.hh"
|
||||||
#include "derivations.hh"
|
#include "derivations.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
@ -412,6 +414,8 @@ public:
|
||||||
{ unsupported("queryRealisation"); }
|
{ unsupported("queryRealisation"); }
|
||||||
};
|
};
|
||||||
|
|
||||||
static RegisterStoreImplementation<LegacySSHStore, LegacySSHStoreConfig> regLegacySSHStore;
|
void registerLegacySSHStore() {
|
||||||
|
StoreImplementations::add<LegacySSHStore, LegacySSHStoreConfig>();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
8
src/libstore/legacy-ssh-store.hh
Normal file
8
src/libstore/legacy-ssh-store.hh
Normal file
|
@ -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...>);
|
LENGTH_PREFIXED_PROTO_HELPER(Inner, std::tuple<Ts...>);
|
||||||
|
|
||||||
template<class Inner, typename K, typename V>
|
template<class Inner, typename K, typename V>
|
||||||
#define _X std::map<K, V>
|
#define DONT_SUBSTITUTE_KV_TYPE std::map<K, V>
|
||||||
LENGTH_PREFIXED_PROTO_HELPER(Inner, _X);
|
LENGTH_PREFIXED_PROTO_HELPER(Inner, DONT_SUBSTITUTE_KV_TYPE);
|
||||||
#undef _X
|
#undef DONT_SUBSTITUTE_KV_TYPE
|
||||||
|
|
||||||
template<class Inner, typename T>
|
template<class Inner, typename T>
|
||||||
std::vector<T>
|
std::vector<T>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#include "local-binary-cache-store.hh"
|
||||||
#include "binary-cache-store.hh"
|
#include "binary-cache-store.hh"
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
#include "nar-info-disk-cache.hh"
|
#include "nar-info-disk-cache.hh"
|
||||||
|
@ -124,6 +125,8 @@ std::set<std::string> LocalBinaryCacheStore::uriSchemes()
|
||||||
return {"file"};
|
return {"file"};
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterStoreImplementation<LocalBinaryCacheStore, LocalBinaryCacheStoreConfig> regLocalBinaryCacheStore;
|
void registerLocalBinaryCacheStore() {
|
||||||
|
StoreImplementations::add<LocalBinaryCacheStore, LocalBinaryCacheStoreConfig>();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
8
src/libstore/local-binary-cache-store.hh
Normal file
8
src/libstore/local-binary-cache-store.hh
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#pragma once
|
||||||
|
///@file
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
void registerLocalBinaryCacheStore();
|
||||||
|
|
||||||
|
}
|
|
@ -10,6 +10,7 @@
|
||||||
#include "signals.hh"
|
#include "signals.hh"
|
||||||
#include "finally.hh"
|
#include "finally.hh"
|
||||||
#include "compression.hh"
|
#include "compression.hh"
|
||||||
|
#include "strings.hh"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
@ -480,7 +481,7 @@ LocalStore::~LocalStore()
|
||||||
unlink(fnTempRoots.c_str());
|
unlink(fnTempRoots.c_str());
|
||||||
}
|
}
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
ignoreException();
|
ignoreExceptionInDestructor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -663,27 +664,6 @@ static void canonicalisePathMetaData_(
|
||||||
if (!(S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)))
|
if (!(S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)))
|
||||||
throw Error("file '%1%' has an unsupported type", path);
|
throw Error("file '%1%' has an unsupported type", path);
|
||||||
|
|
||||||
#if __linux__
|
|
||||||
/* Remove extended attributes / ACLs. */
|
|
||||||
ssize_t eaSize = llistxattr(path.c_str(), nullptr, 0);
|
|
||||||
|
|
||||||
if (eaSize < 0) {
|
|
||||||
if (errno != ENOTSUP && errno != ENODATA)
|
|
||||||
throw SysError("querying extended attributes of '%s'", path);
|
|
||||||
} else if (eaSize > 0) {
|
|
||||||
std::vector<char> eaBuf(eaSize);
|
|
||||||
|
|
||||||
if ((eaSize = llistxattr(path.c_str(), eaBuf.data(), eaBuf.size())) < 0)
|
|
||||||
throw SysError("querying extended attributes of '%s'", path);
|
|
||||||
|
|
||||||
for (auto & eaName: tokenizeString<Strings>(std::string(eaBuf.data(), eaSize), std::string("\000", 1))) {
|
|
||||||
if (settings.ignoredAcls.get().count(eaName)) continue;
|
|
||||||
if (lremovexattr(path.c_str(), eaName.c_str()) == -1)
|
|
||||||
throw SysError("removing extended attribute '%s' from '%s'", eaName, path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Fail if the file is not owned by the build user. This prevents
|
/* Fail if the file is not owned by the build user. This prevents
|
||||||
us from messing up the ownership/permissions of files
|
us from messing up the ownership/permissions of files
|
||||||
hard-linked into the output (e.g. "ln /etc/shadow $out/foo").
|
hard-linked into the output (e.g. "ln /etc/shadow $out/foo").
|
||||||
|
@ -698,6 +678,29 @@ static void canonicalisePathMetaData_(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if __linux__
|
||||||
|
/* Remove extended attributes / ACLs. */
|
||||||
|
ssize_t eaSize = llistxattr(path.c_str(), nullptr, 0);
|
||||||
|
|
||||||
|
if (eaSize < 0) {
|
||||||
|
if (errno != ENOTSUP && errno != ENODATA)
|
||||||
|
throw SysError("querying extended attributes of '%s'", path);
|
||||||
|
} else if (eaSize > 0) {
|
||||||
|
std::vector<char> eaBuf(eaSize);
|
||||||
|
|
||||||
|
if ((eaSize = llistxattr(path.c_str(), eaBuf.data(), eaBuf.size())) < 0)
|
||||||
|
throw SysError("querying extended attributes of '%s'", path);
|
||||||
|
|
||||||
|
if (S_ISREG(st.st_mode) || S_ISDIR(st.st_mode))
|
||||||
|
chmod(path.c_str(), st.st_mode | S_IWUSR);
|
||||||
|
for (auto & eaName: tokenizeString<Strings>(std::string(eaBuf.data(), eaSize), std::string("\000", 1))) {
|
||||||
|
if (settings.ignoredAcls.get().count(eaName)) continue;
|
||||||
|
if (lremovexattr(path.c_str(), eaName.c_str()) == -1)
|
||||||
|
throw SysError("removing extended attribute '%s' from '%s'", eaName, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
inodesSeen.insert(Inode(st.st_dev, st.st_ino));
|
inodesSeen.insert(Inode(st.st_dev, st.st_ino));
|
||||||
|
|
||||||
canonicaliseTimestampAndPermissions(path, st);
|
canonicaliseTimestampAndPermissions(path, st);
|
||||||
|
@ -1215,11 +1218,11 @@ void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
|
||||||
bool narRead = false;
|
bool narRead = false;
|
||||||
Finally cleanup = [&]() {
|
Finally cleanup = [&]() {
|
||||||
if (!narRead) {
|
if (!narRead) {
|
||||||
ParseSink sink;
|
NARParseVisitor sink;
|
||||||
try {
|
try {
|
||||||
parseDump(sink, source);
|
parseDump(sink, source);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
ignoreException();
|
ignoreExceptionExceptInterrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -421,4 +421,7 @@ void canonicaliseTimestampAndPermissions(const Path & path);
|
||||||
|
|
||||||
MakeError(PathInUse, Error);
|
MakeError(PathInUse, Error);
|
||||||
|
|
||||||
|
// Implemented by the relevant platform/ module being used.
|
||||||
|
void registerLocalStore();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,8 +73,16 @@ struct SimpleUserLock : UserLock
|
||||||
debug("trying user '%s'", i);
|
debug("trying user '%s'", i);
|
||||||
|
|
||||||
struct passwd * pw = getpwnam(i.c_str());
|
struct passwd * pw = getpwnam(i.c_str());
|
||||||
if (!pw)
|
if (!pw) {
|
||||||
throw Error("the user '%s' in the group '%s' does not exist", i, settings.buildUsersGroup);
|
#ifdef __APPLE__
|
||||||
|
#define APPLE_HINT "\n\nhint: this may be caused by an update to macOS Sequoia breaking existing Lix installations.\n" \
|
||||||
|
"See the macOS Sequoia page on the Lix wiki for detailed repair instructions: https://wiki.lix.systems/link/81"
|
||||||
|
#else
|
||||||
|
#define APPLE_HINT
|
||||||
|
#endif
|
||||||
|
throw Error("the user '%s' in the group '%s' does not exist" APPLE_HINT, i, settings.buildUsersGroup);
|
||||||
|
#undef APPLE_HINT
|
||||||
|
}
|
||||||
|
|
||||||
auto fnUserLock = fmt("%s/userpool/%s", settings.nixStateDir,pw->pw_uid);
|
auto fnUserLock = fmt("%s/userpool/%s", settings.nixStateDir,pw->pw_uid);
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include "types.hh"
|
#include <memory>
|
||||||
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include "types.hh"
|
#include "ref.hh"
|
||||||
|
#include <set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue