mirror of
https://github.com/NixOS/nix.git
synced 2025-11-19 08:49:35 +01:00
Merge remote-tracking branch 'origin/master' into configs
This commit is contained in:
commit
974e0367df
163 changed files with 3430 additions and 2774 deletions
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
|
|
@ -12,8 +12,6 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: cachix/install-nix-action@v10
|
||||
with:
|
||||
skip_adding_nixpkgs_channel: true
|
||||
- uses: cachix/install-nix-action@v11
|
||||
#- run: nix flake check
|
||||
- run: nix-build -A checks.$(if [[ `uname` = Linux ]]; then echo x86_64-linux; else echo x86_64-darwin; fi)
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -5,7 +5,6 @@ perl/Makefile.config
|
|||
/aclocal.m4
|
||||
/autom4te.cache
|
||||
/precompiled-headers.h.gch
|
||||
/precompiled-headers.h.pch
|
||||
/config.*
|
||||
/configure
|
||||
/stamp-h1
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ for more details.
|
|||
|
||||
## Installation
|
||||
|
||||
On Linux and macOS the easiest way to Install Nix is to run the following shell command
|
||||
On Linux and macOS the easiest way to install Nix is to run the following shell command
|
||||
(as a user other than root):
|
||||
|
||||
```console
|
||||
|
|
|
|||
|
|
@ -52,5 +52,4 @@ in
|
|||
|
||||
command:
|
||||
|
||||
"Title: nix\n\n"
|
||||
+ showCommand { command = "nix"; section = "#"; def = command; }
|
||||
showCommand { command = "nix"; section = "#"; def = command; }
|
||||
|
|
|
|||
|
|
@ -13,7 +13,12 @@ concatStrings (map
|
|||
then "*empty*"
|
||||
else if isBool option.value
|
||||
then (if option.value then "`true`" else "`false`")
|
||||
else "`" + toString option.value + "`") + "\n\n"
|
||||
else
|
||||
# n.b. a StringMap value type is specified as a string, but
|
||||
# this shows the value type. The empty stringmap is "null" in
|
||||
# JSON, but that converts to "{ }" here.
|
||||
(if isAttrs option.value then "`\"\"`"
|
||||
else "`" + toString option.value + "`")) + "\n\n"
|
||||
+ (if option.aliases != []
|
||||
then " **Deprecated alias:** " + (concatStringsSep ", " (map (s: "`${s}`") option.aliases)) + "\n\n"
|
||||
else "")
|
||||
|
|
|
|||
|
|
@ -18,13 +18,22 @@ dist-files += $(man-pages)
|
|||
nix-eval = $(bindir)/nix eval --experimental-features nix-command -I nix/corepkgs=corepkgs --store dummy:// --impure --raw --expr
|
||||
|
||||
$(d)/%.1: $(d)/src/command-ref/%.md
|
||||
$(trace-gen) lowdown -sT man $^ -o $@
|
||||
@printf "Title: %s\n\n" "$$(basename $@ .1)" > $^.tmp
|
||||
@cat $^ >> $^.tmp
|
||||
$(trace-gen) lowdown -sT man $^.tmp -o $@
|
||||
@rm $^.tmp
|
||||
|
||||
$(d)/%.8: $(d)/src/command-ref/%.md
|
||||
$(trace-gen) lowdown -sT man $^ -o $@
|
||||
@printf "Title: %s\n\n" "$$(basename $@ .8)" > $^.tmp
|
||||
@cat $^ >> $^.tmp
|
||||
$(trace-gen) lowdown -sT man $^.tmp -o $@
|
||||
@rm $^.tmp
|
||||
|
||||
$(d)/nix.conf.5: $(d)/src/command-ref/conf-file.md
|
||||
$(trace-gen) lowdown -sT man $^ -o $@
|
||||
@printf "Title: %s\n\n" "$$(basename $@ .5)" > $^.tmp
|
||||
@cat $^ >> $^.tmp
|
||||
$(trace-gen) lowdown -sT man $^.tmp -o $@
|
||||
@rm $^.tmp
|
||||
|
||||
$(d)/src/command-ref/nix.md: $(d)/nix.json $(d)/generate-manpage.nix $(bindir)/nix
|
||||
$(trace-gen) $(nix-eval) 'import doc/manual/generate-manpage.nix (builtins.fromJSON (builtins.readFile $<))' > $@.tmp
|
||||
|
|
@ -40,7 +49,7 @@ $(d)/nix.json: $(bindir)/nix
|
|||
@mv $@.tmp $@
|
||||
|
||||
$(d)/conf-file.json: $(bindir)/nix
|
||||
$(trace-gen) env -i NIX_CONF_DIR=/dummy HOME=/dummy $(bindir)/nix show-config --json --experimental-features nix-command > $@.tmp
|
||||
$(trace-gen) env -i NIX_CONF_DIR=/dummy HOME=/dummy NIX_SSL_CERT_FILE=/dummy/no-ca-bundle.crt $(bindir)/nix show-config --json --experimental-features nix-command > $@.tmp
|
||||
@mv $@.tmp $@
|
||||
|
||||
$(d)/src/expressions/builtins.md: $(d)/builtins.json $(d)/generate-builtins.nix $(d)/src/expressions/builtins-prefix.md $(bindir)/nix
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
Title: nix.conf
|
||||
|
||||
# Name
|
||||
|
||||
`nix.conf` - Nix configuration file
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
Title: nix-build
|
||||
|
||||
# Name
|
||||
|
||||
`nix-build` - build a Nix expression
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
Title: nix-channel
|
||||
|
||||
# Name
|
||||
|
||||
`nix-channel` - manage Nix channels
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
Title: nix-collect-garbage
|
||||
|
||||
# Name
|
||||
|
||||
`nix-collect-garbage` - delete unreachable store paths
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
Title: nix-copy-closure
|
||||
|
||||
# Name
|
||||
|
||||
`nix-copy-closure` - copy a closure to or from a remote machine via SSH
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
Title: nix-daemon
|
||||
|
||||
# Name
|
||||
|
||||
`nix-daemon` - Nix multi-user support daemon
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
Title: nix-env
|
||||
|
||||
# Name
|
||||
|
||||
`nix-env` - manipulate or query Nix user environments
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
Title: nix-hash
|
||||
|
||||
# Name
|
||||
|
||||
`nix-hash` - compute the cryptographic hash of a path
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
Title: nix-instantiate
|
||||
|
||||
# Name
|
||||
|
||||
`nix-instantiate` - instantiate store derivations from Nix expressions
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
Title: nix-prefetch-url
|
||||
|
||||
# Name
|
||||
|
||||
`nix-prefetch-url` - copy a file from a URL into the store and print its hash
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
Title: nix-shell
|
||||
|
||||
# Name
|
||||
|
||||
`nix-shell` - start an interactive shell based on a Nix expression
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
Title: nix-store
|
||||
|
||||
# Name
|
||||
|
||||
`nix-store` - manipulate or query the Nix store
|
||||
|
|
|
|||
|
|
@ -39,17 +39,17 @@ To build Nix itself in this shell:
|
|||
|
||||
```console
|
||||
[nix-shell]$ ./bootstrap.sh
|
||||
[nix-shell]$ ./configure $configureFlags --prefix=$(pwd)/inst
|
||||
[nix-shell]$ ./configure $configureFlags --prefix=$(pwd)/outputs/out
|
||||
[nix-shell]$ make -j $NIX_BUILD_CORES
|
||||
```
|
||||
|
||||
To install it in `$(pwd)/inst` and test it:
|
||||
To install it in `$(pwd)/outputs` and test it:
|
||||
|
||||
```console
|
||||
[nix-shell]$ make install
|
||||
[nix-shell]$ make installcheck
|
||||
[nix-shell]$ ./inst/bin/nix --version
|
||||
nix (Nix) 2.4
|
||||
[nix-shell]$ make installcheck -j $NIX_BUILD_CORES
|
||||
[nix-shell]$ ./outputs/out/bin/nix --version
|
||||
nix (Nix) 3.0
|
||||
```
|
||||
|
||||
To run a functional test:
|
||||
|
|
@ -58,6 +58,12 @@ To run a functional test:
|
|||
make tests/test-name-should-auto-complete.sh.test
|
||||
```
|
||||
|
||||
To run the unit-tests for C++ code:
|
||||
|
||||
```
|
||||
make check
|
||||
```
|
||||
|
||||
If you have a flakes-enabled Nix you can replace:
|
||||
|
||||
```console
|
||||
|
|
|
|||
2
local.mk
2
local.mk
|
|
@ -11,6 +11,6 @@ GLOBAL_CXXFLAGS += -Wno-deprecated-declarations
|
|||
$(foreach i, config.h $(wildcard src/lib*/*.hh), \
|
||||
$(eval $(call install-file-in, $(i), $(includedir)/nix, 0644)))
|
||||
|
||||
$(GCH) $(PCH): src/libutil/util.hh config.h
|
||||
$(GCH): src/libutil/util.hh config.h
|
||||
|
||||
GCH_CXXFLAGS = -I src/libutil
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@ function _complete_nix {
|
|||
_get_comp_words_by_ref -n ':=&' words cword cur
|
||||
local have_type
|
||||
while IFS= read -r line; do
|
||||
local completion=${line%% *}
|
||||
if [[ -z $have_type ]]; then
|
||||
have_type=1
|
||||
if [[ $line = filenames ]]; then
|
||||
if [[ $completion = filenames ]]; then
|
||||
compopt -o filenames
|
||||
fi
|
||||
else
|
||||
COMPREPLY+=("$line")
|
||||
COMPREPLY+=("$completion")
|
||||
fi
|
||||
done < <(NIX_GET_COMPLETIONS=$cword "${words[@]}")
|
||||
__ltrim_colon_completions "$cur"
|
||||
|
|
|
|||
21
misc/zsh/completion.zsh
Normal file
21
misc/zsh/completion.zsh
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
function _nix() {
|
||||
local ifs_bk="$IFS"
|
||||
local input=("${(Q)words[@]}")
|
||||
IFS=$'\n'
|
||||
local res=($(NIX_GET_COMPLETIONS=$((CURRENT - 1)) "$input[@]"))
|
||||
IFS="$ifs_bk"
|
||||
local tpe="${${res[1]}%%> *}"
|
||||
local -a suggestions
|
||||
declare -a suggestions
|
||||
for suggestion in ${res:1}; do
|
||||
# FIXME: This doesn't work properly if the suggestion word contains a `:`
|
||||
# itself
|
||||
suggestions+="${suggestion/ /:}"
|
||||
done
|
||||
if [[ "$tpe" == filenames ]]; then
|
||||
compadd -f
|
||||
fi
|
||||
_describe 'nix' suggestions
|
||||
}
|
||||
|
||||
compdef _nix nix
|
||||
|
|
@ -10,33 +10,12 @@ $(GCH): precompiled-headers.h
|
|||
@mkdir -p "$(dir $@)"
|
||||
$(trace-gen) $(CXX) -x c++-header -o $@ $< $(GLOBAL_CXXFLAGS) $(GCH_CXXFLAGS)
|
||||
|
||||
PCH = $(buildprefix)precompiled-headers.h.pch
|
||||
|
||||
$(PCH): precompiled-headers.h
|
||||
@rm -f $@
|
||||
@mkdir -p "$(dir $@)"
|
||||
$(trace-gen) $(CXX) -x c++-header -o $@ $< $(GLOBAL_CXXFLAGS) $(GCH_CXXFLAGS)
|
||||
|
||||
clean-files += $(GCH) $(PCH)
|
||||
clean-files += $(GCH)
|
||||
|
||||
ifeq ($(PRECOMPILE_HEADERS), 1)
|
||||
|
||||
ifeq ($(findstring g++,$(CXX)), g++)
|
||||
|
||||
GLOBAL_CXXFLAGS_PCH += -include $(buildprefix)precompiled-headers.h -Winvalid-pch
|
||||
|
||||
GLOBAL_ORDER_AFTER += $(GCH)
|
||||
|
||||
else ifeq ($(findstring clang++,$(CXX)), clang++)
|
||||
|
||||
GLOBAL_CXXFLAGS_PCH += -include-pch $(PCH) -Winvalid-pch
|
||||
|
||||
GLOBAL_ORDER_AFTER += $(PCH)
|
||||
|
||||
else
|
||||
|
||||
$(error Don't know how to precompile headers on $(CXX))
|
||||
|
||||
endif
|
||||
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#[allow(improper_ctypes_definitions)]
|
||||
#[cfg(not(test))]
|
||||
mod c;
|
||||
mod error;
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ impl StorePath {
|
|||
}
|
||||
Self::new_from_base_name(
|
||||
path.file_name()
|
||||
.ok_or(Error::BadStorePath(path.into()))?
|
||||
.ok_or_else(|| Error::BadStorePath(path.into()))?
|
||||
.to_str()
|
||||
.ok_or(Error::BadStorePath(path.into()))?,
|
||||
.ok_or_else(|| Error::BadStorePath(path.into()))?,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ impl StorePath {
|
|||
|
||||
pub fn new_from_base_name(base_name: &str) -> Result<Self, Error> {
|
||||
if base_name.len() < STORE_PATH_HASH_CHARS + 1
|
||||
|| base_name.as_bytes()[STORE_PATH_HASH_CHARS] != '-' as u8
|
||||
|| base_name.as_bytes()[STORE_PATH_HASH_CHARS] != b'-'
|
||||
{
|
||||
return Err(Error::BadStorePath(base_name.into()));
|
||||
}
|
||||
|
|
@ -65,7 +65,7 @@ impl StorePathHash {
|
|||
Ok(Self(bytes))
|
||||
}
|
||||
|
||||
pub fn hash<'a>(&'a self) -> &'a [u8; STORE_PATH_HASH_BYTES] {
|
||||
pub fn hash(&self) -> &[u8; STORE_PATH_HASH_BYTES] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
|
@ -98,7 +98,7 @@ pub struct StorePathName(String);
|
|||
|
||||
impl StorePathName {
|
||||
pub fn new(s: &str) -> Result<Self, Error> {
|
||||
if s.len() == 0 {
|
||||
if s.is_empty() {
|
||||
return Err(Error::StorePathNameEmpty);
|
||||
}
|
||||
|
||||
|
|
@ -106,8 +106,7 @@ impl StorePathName {
|
|||
return Err(Error::StorePathNameTooLong);
|
||||
}
|
||||
|
||||
if s.starts_with('.')
|
||||
|| !s.chars().all(|c| {
|
||||
let is_good_path_name = s.chars().all(|c| {
|
||||
c.is_ascii_alphabetic()
|
||||
|| c.is_ascii_digit()
|
||||
|| c == '+'
|
||||
|
|
@ -116,15 +115,15 @@ impl StorePathName {
|
|||
|| c == '_'
|
||||
|| c == '?'
|
||||
|| c == '='
|
||||
})
|
||||
{
|
||||
});
|
||||
if s.starts_with('.') || !is_good_path_name {
|
||||
return Err(Error::BadStorePathName);
|
||||
}
|
||||
|
||||
Ok(Self(s.to_string()))
|
||||
}
|
||||
|
||||
pub fn name<'a>(&'a self) -> &'a str {
|
||||
pub fn name(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ pub fn decoded_len(input_len: usize) -> usize {
|
|||
input_len * 5 / 8
|
||||
}
|
||||
|
||||
static BASE32_CHARS: &'static [u8; 32] = &b"0123456789abcdfghijklmnpqrsvwxyz";
|
||||
static BASE32_CHARS: &[u8; 32] = &b"0123456789abcdfghijklmnpqrsvwxyz";
|
||||
|
||||
lazy_static! {
|
||||
static ref BASE32_CHARS_REVERSE: Box<[u8; 256]> = {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
set -e
|
||||
|
||||
umask 0022
|
||||
|
||||
dest="/nix"
|
||||
self="$(dirname "$0")"
|
||||
nix="@nix@"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ oops() {
|
|||
exit 1
|
||||
}
|
||||
|
||||
umask 0022
|
||||
|
||||
tmpDir="$(mktemp -d -t nix-binary-tarball-unpack.XXXXXXXXXX || \
|
||||
oops "Can't create temporary directory for downloading the Nix binary tarball")"
|
||||
cleanup() {
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ static bool allSupportedLocally(Store & store, const std::set<std::string>& requ
|
|||
return true;
|
||||
}
|
||||
|
||||
static int _main(int argc, char * * argv)
|
||||
static int main_build_remote(int argc, char * * argv)
|
||||
{
|
||||
{
|
||||
logger = makeJSONLogger(*logger);
|
||||
|
|
@ -297,4 +297,4 @@ connected:
|
|||
}
|
||||
}
|
||||
|
||||
static RegisterLegacyCommand s1("build-remote", _main);
|
||||
static RegisterLegacyCommand r_build_remote("build-remote", main_build_remote);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace nix::eval_cache {
|
|||
|
||||
MakeError(CachedEvalError, EvalError);
|
||||
|
||||
class AttrDb;
|
||||
struct AttrDb;
|
||||
class AttrCursor;
|
||||
|
||||
class EvalCache : public std::enable_shared_from_this<EvalCache>
|
||||
|
|
|
|||
|
|
@ -2081,7 +2081,7 @@ Strings EvalSettings::getDefaultNixPath()
|
|||
|
||||
EvalSettings evalSettings;
|
||||
|
||||
static GlobalConfig::Register r1(&evalSettings);
|
||||
static GlobalConfig::Register rEvalSettings(&evalSettings);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ using namespace flake;
|
|||
|
||||
namespace flake {
|
||||
|
||||
typedef std::pair<Tree, FlakeRef> FetchedFlake;
|
||||
typedef std::pair<fetchers::Tree, FlakeRef> FetchedFlake;
|
||||
typedef std::vector<std::pair<FlakeRef, FetchedFlake>> FlakeCache;
|
||||
|
||||
static std::optional<FetchedFlake> lookupInFlakeCache(
|
||||
|
|
@ -274,7 +274,7 @@ Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup
|
|||
}
|
||||
|
||||
/* Compute an in-memory lock file for the specified top-level flake,
|
||||
and optionally write it to file, it the flake is writable. */
|
||||
and optionally write it to file, if the flake is writable. */
|
||||
LockedFlake lockFlake(
|
||||
EvalState & state,
|
||||
const FlakeRef & topRef,
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@ const static std::string subDirRegex = subDirElemRegex + "(?:/" + subDirElemRege
|
|||
|
||||
std::string FlakeRef::to_string() const
|
||||
{
|
||||
auto url = input.toURL();
|
||||
std::map<std::string, std::string> extraQuery;
|
||||
if (subdir != "")
|
||||
url.query.insert_or_assign("dir", subdir);
|
||||
return url.to_string();
|
||||
extraQuery.insert_or_assign("dir", subdir);
|
||||
return input.toURLString(extraQuery);
|
||||
}
|
||||
|
||||
fetchers::Attrs FlakeRef::toAttrs() const
|
||||
|
|
@ -157,7 +157,8 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
|||
} else {
|
||||
if (!hasPrefix(path, "/"))
|
||||
throw BadURL("flake reference '%s' is not an absolute path", url);
|
||||
path = canonPath(path);
|
||||
auto query = decodeQuery(match[2]);
|
||||
path = canonPath(path + "/" + get(query, "dir").value_or(""));
|
||||
}
|
||||
|
||||
fetchers::Attrs attrs;
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@ FlakeRef getFlakeRef(
|
|||
{
|
||||
auto i = json.find(attr);
|
||||
if (i != json.end()) {
|
||||
auto attrs = jsonToAttrs(*i);
|
||||
auto attrs = fetchers::jsonToAttrs(*i);
|
||||
// FIXME: remove when we drop support for version 5.
|
||||
if (info) {
|
||||
auto j = json.find(info);
|
||||
if (j != json.end()) {
|
||||
for (auto k : jsonToAttrs(*j))
|
||||
for (auto k : fetchers::jsonToAttrs(*j))
|
||||
attrs.insert_or_assign(k.first, k.second);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ class StorePath;
|
|||
|
||||
namespace nix::flake {
|
||||
|
||||
using namespace fetchers;
|
||||
|
||||
typedef std::vector<FlakeId> InputPath;
|
||||
|
||||
struct LockedNode;
|
||||
|
|
|
|||
|
|
@ -2236,6 +2236,10 @@ static RegisterPrimOp primop_catAttrs({
|
|||
static void prim_functionArgs(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||
{
|
||||
state.forceValue(*args[0], pos);
|
||||
if (args[0]->type == tPrimOpApp || args[0]->type == tPrimOp) {
|
||||
state.mkAttrs(v, 0);
|
||||
return;
|
||||
}
|
||||
if (args[0]->type != tLambda)
|
||||
throw TypeError({
|
||||
.hint = hintfmt("'functionArgs' requires a function"),
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include "eval.hh"
|
||||
|
||||
#include <tuple>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ static void prim_unsafeDiscardStringContext(EvalState & state, const Pos & pos,
|
|||
mkString(v, s, PathSet());
|
||||
}
|
||||
|
||||
static RegisterPrimOp r1("__unsafeDiscardStringContext", 1, prim_unsafeDiscardStringContext);
|
||||
static RegisterPrimOp primop_unsafeDiscardStringContext("__unsafeDiscardStringContext", 1, prim_unsafeDiscardStringContext);
|
||||
|
||||
|
||||
static void prim_hasContext(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||
|
|
@ -21,7 +21,7 @@ static void prim_hasContext(EvalState & state, const Pos & pos, Value * * args,
|
|||
mkBool(v, !context.empty());
|
||||
}
|
||||
|
||||
static RegisterPrimOp r2("__hasContext", 1, prim_hasContext);
|
||||
static RegisterPrimOp primop_hasContext("__hasContext", 1, prim_hasContext);
|
||||
|
||||
|
||||
/* Sometimes we want to pass a derivation path (i.e. pkg.drvPath) to a
|
||||
|
|
@ -42,7 +42,7 @@ static void prim_unsafeDiscardOutputDependency(EvalState & state, const Pos & po
|
|||
mkString(v, s, context2);
|
||||
}
|
||||
|
||||
static RegisterPrimOp r3("__unsafeDiscardOutputDependency", 1, prim_unsafeDiscardOutputDependency);
|
||||
static RegisterPrimOp primop_unsafeDiscardOutputDependency("__unsafeDiscardOutputDependency", 1, prim_unsafeDiscardOutputDependency);
|
||||
|
||||
|
||||
/* Extract the context of a string as a structured Nix value.
|
||||
|
|
@ -127,7 +127,7 @@ static void prim_getContext(EvalState & state, const Pos & pos, Value * * args,
|
|||
v.attrs->sort();
|
||||
}
|
||||
|
||||
static RegisterPrimOp r4("__getContext", 1, prim_getContext);
|
||||
static RegisterPrimOp primop_getContext("__getContext", 1, prim_getContext);
|
||||
|
||||
|
||||
/* Append the given context to a given string.
|
||||
|
|
@ -191,6 +191,6 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg
|
|||
mkString(v, orig, context);
|
||||
}
|
||||
|
||||
static RegisterPrimOp r5("__appendContext", 2, prim_appendContext);
|
||||
static RegisterPrimOp primop_appendContext("__appendContext", 2, prim_appendContext);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,6 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar
|
|||
state.allowedPaths->insert(tree.actualPath);
|
||||
}
|
||||
|
||||
static RegisterPrimOp r("fetchMercurial", 1, prim_fetchMercurial);
|
||||
static RegisterPrimOp r_fetchMercurial("fetchMercurial", 1, prim_fetchMercurial);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, V
|
|||
fetchTree(state, pos, args, v, std::nullopt);
|
||||
}
|
||||
|
||||
static RegisterPrimOp r("fetchTree", 1, prim_fetchTree);
|
||||
static RegisterPrimOp primop_fetchTree("fetchTree", 1, prim_fetchTree);
|
||||
|
||||
static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
|
||||
const string & who, bool unpack, std::string name)
|
||||
|
|
|
|||
|
|
@ -90,6 +90,6 @@ void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Value & v
|
|||
}
|
||||
}
|
||||
|
||||
static RegisterPrimOp r("fromTOML", 1, prim_fromTOML);
|
||||
static RegisterPrimOp primop_fromTOML("fromTOML", 1, prim_fromTOML);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,14 @@ ParsedURL Input::toURL() const
|
|||
return scheme->toURL(*this);
|
||||
}
|
||||
|
||||
std::string Input::toURLString(const std::map<std::string, std::string> & extraQuery) const
|
||||
{
|
||||
auto url = toURL();
|
||||
for (auto & attr : extraQuery)
|
||||
url.query.insert(attr);
|
||||
return url.to_string();
|
||||
}
|
||||
|
||||
std::string Input::to_string() const
|
||||
{
|
||||
return toURL().to_string();
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ public:
|
|||
|
||||
ParsedURL toURL() const;
|
||||
|
||||
std::string toURLString(const std::map<std::string, std::string> & extraQuery = {}) const;
|
||||
|
||||
std::string to_string() const;
|
||||
|
||||
Attrs toAttrs() const;
|
||||
|
|
@ -73,7 +75,7 @@ public:
|
|||
|
||||
StorePath computeStorePath(Store & store) const;
|
||||
|
||||
// Convience functions for common attributes.
|
||||
// Convenience functions for common attributes.
|
||||
std::string getType() const;
|
||||
std::optional<Hash> getNarHash() const;
|
||||
std::optional<std::string> getRef() const;
|
||||
|
|
@ -84,6 +86,9 @@ public:
|
|||
|
||||
struct InputScheme
|
||||
{
|
||||
virtual ~InputScheme()
|
||||
{ }
|
||||
|
||||
virtual std::optional<Input> inputFromURL(const ParsedURL & url) = 0;
|
||||
|
||||
virtual std::optional<Input> inputFromAttrs(const Attrs & attrs) = 0;
|
||||
|
|
@ -119,12 +124,14 @@ DownloadFileResult downloadFile(
|
|||
ref<Store> store,
|
||||
const std::string & url,
|
||||
const std::string & name,
|
||||
bool immutable);
|
||||
bool immutable,
|
||||
const Headers & headers = {});
|
||||
|
||||
std::pair<Tree, time_t> downloadTarball(
|
||||
ref<Store> store,
|
||||
const std::string & url,
|
||||
const std::string & name,
|
||||
bool immutable);
|
||||
bool immutable,
|
||||
const Headers & headers = {});
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -452,6 +452,6 @@ struct GitInputScheme : InputScheme
|
|||
}
|
||||
};
|
||||
|
||||
static auto r1 = OnStartup([] { registerInputScheme(std::make_unique<GitInputScheme>()); });
|
||||
static auto rGitInputScheme = OnStartup([] { registerInputScheme(std::make_unique<GitInputScheme>()); });
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,20 @@
|
|||
#include "fetchers.hh"
|
||||
#include "globals.hh"
|
||||
#include "store-api.hh"
|
||||
#include "types.hh"
|
||||
#include "url-parts.hh"
|
||||
|
||||
#include <optional>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace nix::fetchers {
|
||||
|
||||
struct DownloadUrl
|
||||
{
|
||||
std::string url;
|
||||
Headers headers;
|
||||
};
|
||||
|
||||
// A github or gitlab host
|
||||
const static std::string hostRegexS = "[a-zA-Z0-9.]*"; // FIXME: check
|
||||
std::regex hostRegex(hostRegexS, std::regex::ECMAScript);
|
||||
|
|
@ -17,6 +25,8 @@ struct GitArchiveInputScheme : InputScheme
|
|||
{
|
||||
virtual std::string type() = 0;
|
||||
|
||||
virtual std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const = 0;
|
||||
|
||||
std::optional<Input> inputFromURL(const ParsedURL & url) override
|
||||
{
|
||||
if (url.scheme != type()) return {};
|
||||
|
|
@ -130,9 +140,31 @@ struct GitArchiveInputScheme : InputScheme
|
|||
return input;
|
||||
}
|
||||
|
||||
std::optional<std::string> getAccessToken(const std::string & host) const
|
||||
{
|
||||
auto tokens = settings.accessTokens.get();
|
||||
if (auto token = get(tokens, host))
|
||||
return *token;
|
||||
return {};
|
||||
}
|
||||
|
||||
Headers makeHeadersWithAuthTokens(const std::string & host) const
|
||||
{
|
||||
Headers headers;
|
||||
auto accessToken = getAccessToken(host);
|
||||
if (accessToken) {
|
||||
auto hdr = accessHeaderFromToken(*accessToken);
|
||||
if (hdr)
|
||||
headers.push_back(*hdr);
|
||||
else
|
||||
warn("Unrecognized access token for host '%s'", host);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
virtual Hash getRevFromRef(nix::ref<Store> store, const Input & input) const = 0;
|
||||
|
||||
virtual std::string getDownloadUrl(const Input & input) const = 0;
|
||||
virtual DownloadUrl getDownloadUrl(const Input & input) const = 0;
|
||||
|
||||
std::pair<Tree, Input> fetch(ref<Store> store, const Input & _input) override
|
||||
{
|
||||
|
|
@ -161,7 +193,7 @@ struct GitArchiveInputScheme : InputScheme
|
|||
|
||||
auto url = getDownloadUrl(input);
|
||||
|
||||
auto [tree, lastModified] = downloadTarball(store, url, "source", true);
|
||||
auto [tree, lastModified] = downloadTarball(store, url.url, "source", true, url.headers);
|
||||
|
||||
input.attrs.insert_or_assign("lastModified", lastModified);
|
||||
|
||||
|
|
@ -183,49 +215,52 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
|||
{
|
||||
std::string type() override { return "github"; }
|
||||
|
||||
void addAccessToken(std::string & url) const
|
||||
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
|
||||
{
|
||||
std::string accessToken = settings.githubAccessToken.get();
|
||||
if (accessToken != "")
|
||||
url += "?access_token=" + accessToken;
|
||||
// Github supports PAT/OAuth2 tokens and HTTP Basic
|
||||
// Authentication. The former simply specifies the token, the
|
||||
// latter can use the token as the password. Only the first
|
||||
// is used here. See
|
||||
// https://developer.github.com/v3/#authentication and
|
||||
// https://docs.github.com/en/developers/apps/authorizing-oath-apps
|
||||
return std::pair<std::string, std::string>("Authorization", fmt("token %s", token));
|
||||
}
|
||||
|
||||
Hash getRevFromRef(nix::ref<Store> store, const Input & input) const override
|
||||
{
|
||||
auto host_url = maybeGetStrAttr(input.attrs, "url").value_or("github.com");
|
||||
auto host = maybeGetStrAttr(input.attrs, "host").value_or("github.com");
|
||||
auto url = fmt("https://api.%s/repos/%s/%s/commits/%s", // FIXME: check
|
||||
host_url, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), *input.getRef());
|
||||
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), *input.getRef());
|
||||
|
||||
addAccessToken(url);
|
||||
Headers headers = makeHeadersWithAuthTokens(host);
|
||||
|
||||
auto json = nlohmann::json::parse(
|
||||
readFile(
|
||||
store->toRealPath(
|
||||
downloadFile(store, url, "source", false).storePath)));
|
||||
downloadFile(store, url, "source", false, headers).storePath)));
|
||||
auto rev = Hash::parseAny(std::string { json["sha"] }, htSHA1);
|
||||
debug("HEAD revision for '%s' is %s", url, rev.gitRev());
|
||||
return rev;
|
||||
}
|
||||
|
||||
std::string getDownloadUrl(const Input & input) const override
|
||||
DownloadUrl getDownloadUrl(const Input & input) const override
|
||||
{
|
||||
// FIXME: use regular /archive URLs instead? api.github.com
|
||||
// might have stricter rate limits.
|
||||
auto host_url = maybeGetStrAttr(input.attrs, "host").value_or("github.com");
|
||||
auto host = maybeGetStrAttr(input.attrs, "host").value_or("github.com");
|
||||
auto url = fmt("https://api.%s/repos/%s/%s/tarball/%s", // FIXME: check if this is correct for self hosted instances
|
||||
host_url, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"),
|
||||
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"),
|
||||
input.getRev()->to_string(Base16, false));
|
||||
|
||||
addAccessToken(url);
|
||||
|
||||
return url;
|
||||
Headers headers = makeHeadersWithAuthTokens(host);
|
||||
return DownloadUrl { url, headers };
|
||||
}
|
||||
|
||||
void clone(const Input & input, const Path & destDir) override
|
||||
{
|
||||
auto host_url = maybeGetStrAttr(input.attrs, "url").value_or("github.com");
|
||||
auto host = maybeGetStrAttr(input.attrs, "host").value_or("github.com");
|
||||
Input::fromURL(fmt("git+ssh://git@%s/%s/%s.git",
|
||||
host_url, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo")))
|
||||
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo")))
|
||||
.applyOverrides(input.getRef().value_or("HEAD"), input.getRev())
|
||||
.clone(destDir);
|
||||
}
|
||||
|
|
@ -235,48 +270,71 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
|||
{
|
||||
std::string type() override { return "gitlab"; }
|
||||
|
||||
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
|
||||
{
|
||||
// Gitlab supports 4 kinds of authorization, two of which are
|
||||
// relevant here: OAuth2 and PAT (Private Access Token). The
|
||||
// user can indicate which token is used by specifying the
|
||||
// token as <TYPE>:<VALUE>, where type is "OAuth2" or "PAT".
|
||||
// If the <TYPE> is unrecognized, this will fall back to
|
||||
// treating this simply has <HDRNAME>:<HDRVAL>. See
|
||||
// https://docs.gitlab.com/12.10/ee/api/README.html#authentication
|
||||
auto fldsplit = token.find_first_of(':');
|
||||
// n.b. C++20 would allow: if (token.starts_with("OAuth2:")) ...
|
||||
if ("OAuth2" == token.substr(0, fldsplit))
|
||||
return std::make_pair("Authorization", fmt("Bearer %s", token.substr(fldsplit+1)));
|
||||
if ("PAT" == token.substr(0, fldsplit))
|
||||
return std::make_pair("Private-token", token.substr(fldsplit+1));
|
||||
warn("Unrecognized GitLab token type %s", token.substr(0, fldsplit));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Hash getRevFromRef(nix::ref<Store> store, const Input & input) const override
|
||||
{
|
||||
auto host_url = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com");
|
||||
auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com");
|
||||
// See rate limiting note below
|
||||
auto url = fmt("https://%s/api/v4/projects/%s%%2F%s/repository/commits?ref_name=%s",
|
||||
host_url, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), *input.getRef());
|
||||
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), *input.getRef());
|
||||
|
||||
Headers headers = makeHeadersWithAuthTokens(host);
|
||||
|
||||
auto json = nlohmann::json::parse(
|
||||
readFile(
|
||||
store->toRealPath(
|
||||
downloadFile(store, url, "source", false).storePath)));
|
||||
downloadFile(store, url, "source", false, headers).storePath)));
|
||||
auto rev = Hash::parseAny(std::string(json[0]["id"]), htSHA1);
|
||||
debug("HEAD revision for '%s' is %s", url, rev.gitRev());
|
||||
return rev;
|
||||
}
|
||||
|
||||
std::string getDownloadUrl(const Input & input) const override
|
||||
DownloadUrl getDownloadUrl(const Input & input) const override
|
||||
{
|
||||
// FIXME: This endpoint has a rate limit threshold of 5 requests per minute
|
||||
auto host_url = maybeGetStrAttr(input.attrs, "url").value_or("gitlab.com");
|
||||
// This endpoint has a rate limit threshold that may be
|
||||
// server-specific and vary based whether the user is
|
||||
// authenticated via an accessToken or not, but the usual rate
|
||||
// is 10 reqs/sec/ip-addr. See
|
||||
// https://docs.gitlab.com/ee/user/gitlab_com/index.html#gitlabcom-specific-rate-limits
|
||||
auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com");
|
||||
auto url = fmt("https://%s/api/v4/projects/%s%%2F%s/repository/archive.tar.gz?sha=%s",
|
||||
host_url, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"),
|
||||
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"),
|
||||
input.getRev()->to_string(Base16, false));
|
||||
|
||||
/* # FIXME: add privat token auth (`curl --header "PRIVATE-TOKEN: <your_access_token>"`)
|
||||
std::string accessToken = settings.githubAccessToken.get();
|
||||
if (accessToken != "")
|
||||
url += "?access_token=" + accessToken;*/
|
||||
|
||||
return url;
|
||||
Headers headers = makeHeadersWithAuthTokens(host);
|
||||
return DownloadUrl { url, headers };
|
||||
}
|
||||
|
||||
void clone(const Input & input, const Path & destDir) override
|
||||
{
|
||||
auto host_url = maybeGetStrAttr(input.attrs, "url").value_or("gitlab.com");
|
||||
auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com");
|
||||
// FIXME: get username somewhere
|
||||
Input::fromURL(fmt("git+ssh://git@%s/%s/%s.git",
|
||||
host_url, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo")))
|
||||
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo")))
|
||||
.applyOverrides(input.getRef().value_or("HEAD"), input.getRev())
|
||||
.clone(destDir);
|
||||
}
|
||||
};
|
||||
|
||||
static auto r1 = OnStartup([] { registerInputScheme(std::make_unique<GitHubInputScheme>()); });
|
||||
static auto r2 = OnStartup([] { registerInputScheme(std::make_unique<GitLabInputScheme>()); });
|
||||
static auto rGitHubInputScheme = OnStartup([] { registerInputScheme(std::make_unique<GitHubInputScheme>()); });
|
||||
static auto rGitLabInputScheme = OnStartup([] { registerInputScheme(std::make_unique<GitLabInputScheme>()); });
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,6 +100,6 @@ struct IndirectInputScheme : InputScheme
|
|||
}
|
||||
};
|
||||
|
||||
static auto r1 = OnStartup([] { registerInputScheme(std::make_unique<IndirectInputScheme>()); });
|
||||
static auto rIndirectInputScheme = OnStartup([] { registerInputScheme(std::make_unique<IndirectInputScheme>()); });
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -293,6 +293,6 @@ struct MercurialInputScheme : InputScheme
|
|||
}
|
||||
};
|
||||
|
||||
static auto r1 = OnStartup([] { registerInputScheme(std::make_unique<MercurialInputScheme>()); });
|
||||
static auto rMercurialInputScheme = OnStartup([] { registerInputScheme(std::make_unique<MercurialInputScheme>()); });
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,6 +102,6 @@ struct PathInputScheme : InputScheme
|
|||
}
|
||||
};
|
||||
|
||||
static auto r1 = OnStartup([] { registerInputScheme(std::make_unique<PathInputScheme>()); });
|
||||
static auto rPathInputScheme = OnStartup([] { registerInputScheme(std::make_unique<PathInputScheme>()); });
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include "util.hh"
|
||||
#include "globals.hh"
|
||||
#include "store-api.hh"
|
||||
#include "local-fs-store.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include "store-api.hh"
|
||||
#include "archive.hh"
|
||||
#include "tarfile.hh"
|
||||
#include "types.hh"
|
||||
|
||||
namespace nix::fetchers {
|
||||
|
||||
|
|
@ -12,7 +13,8 @@ DownloadFileResult downloadFile(
|
|||
ref<Store> store,
|
||||
const std::string & url,
|
||||
const std::string & name,
|
||||
bool immutable)
|
||||
bool immutable,
|
||||
const Headers & headers)
|
||||
{
|
||||
// FIXME: check store
|
||||
|
||||
|
|
@ -37,6 +39,7 @@ DownloadFileResult downloadFile(
|
|||
return useCached();
|
||||
|
||||
FileTransferRequest request(url);
|
||||
request.headers = headers;
|
||||
if (cached)
|
||||
request.expectedETag = getStrAttr(cached->infoAttrs, "etag");
|
||||
FileTransferResult res;
|
||||
|
|
@ -111,7 +114,8 @@ std::pair<Tree, time_t> downloadTarball(
|
|||
ref<Store> store,
|
||||
const std::string & url,
|
||||
const std::string & name,
|
||||
bool immutable)
|
||||
bool immutable,
|
||||
const Headers & headers)
|
||||
{
|
||||
Attrs inAttrs({
|
||||
{"type", "tarball"},
|
||||
|
|
@ -127,7 +131,7 @@ std::pair<Tree, time_t> downloadTarball(
|
|||
getIntAttr(cached->infoAttrs, "lastModified")
|
||||
};
|
||||
|
||||
auto res = downloadFile(store, url, name, immutable);
|
||||
auto res = downloadFile(store, url, name, immutable, headers);
|
||||
|
||||
std::optional<StorePath> unpackedStorePath;
|
||||
time_t lastModified;
|
||||
|
|
@ -227,6 +231,6 @@ struct TarballInputScheme : InputScheme
|
|||
}
|
||||
};
|
||||
|
||||
static auto r1 = OnStartup([] { registerInputScheme(std::make_unique<TarballInputScheme>()); });
|
||||
static auto rTarballInputScheme = OnStartup([] { registerInputScheme(std::make_unique<TarballInputScheme>()); });
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ MixCommonArgs::MixCommonArgs(const string & programName)
|
|||
globalConfig.getSettings(settings);
|
||||
for (auto & s : settings)
|
||||
if (hasPrefix(s.first, prefix))
|
||||
completions->insert(s.first);
|
||||
completions->add(s.first, s.second.description);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ public:
|
|||
}
|
||||
|
||||
else if (type == resBuildLogLine || type == resPostBuildLogLine) {
|
||||
auto lastLine = trim(getS(fields, 0));
|
||||
auto lastLine = chomp(getS(fields, 0));
|
||||
if (!lastLine.empty()) {
|
||||
auto i = state->its.find(act);
|
||||
assert(i != state->its.end());
|
||||
|
|
|
|||
|
|
@ -386,18 +386,12 @@ RunPager::~RunPager()
|
|||
}
|
||||
|
||||
|
||||
string showBytes(uint64_t bytes)
|
||||
{
|
||||
return (format("%.2f MiB") % (bytes / (1024.0 * 1024.0))).str();
|
||||
}
|
||||
|
||||
|
||||
PrintFreed::~PrintFreed()
|
||||
{
|
||||
if (show)
|
||||
std::cout << format("%1% store paths deleted, %2% freed\n")
|
||||
% results.paths.size()
|
||||
% showBytes(results.bytesFreed);
|
||||
std::cout << fmt("%d store paths deleted, %s freed\n",
|
||||
results.paths.size(),
|
||||
showBytes(results.bytesFreed));
|
||||
}
|
||||
|
||||
Exit::~Exit() { }
|
||||
|
|
|
|||
|
|
@ -142,17 +142,10 @@ struct FileSource : FdSource
|
|||
}
|
||||
};
|
||||
|
||||
void BinaryCacheStore::addToStore(const ValidPathInfo & info, Source & narSource,
|
||||
RepairFlag repair, CheckSigsFlag checkSigs)
|
||||
ref<const ValidPathInfo> BinaryCacheStore::addToStoreCommon(
|
||||
Source & narSource, RepairFlag repair, CheckSigsFlag checkSigs,
|
||||
std::function<ValidPathInfo(HashResult)> mkInfo)
|
||||
{
|
||||
assert(info.narSize);
|
||||
|
||||
if (!repair && isValidPath(info.path)) {
|
||||
// FIXME: copyNAR -> null sink
|
||||
narSource.drain();
|
||||
return;
|
||||
}
|
||||
|
||||
auto [fdTemp, fnTemp] = createTempFile();
|
||||
|
||||
AutoDelete autoDelete(fnTemp);
|
||||
|
|
@ -162,13 +155,15 @@ void BinaryCacheStore::addToStore(const ValidPathInfo & info, Source & narSource
|
|||
/* Read the NAR simultaneously into a CompressionSink+FileSink (to
|
||||
write the compressed NAR to disk), into a HashSink (to get the
|
||||
NAR hash), and into a NarAccessor (to get the NAR listing). */
|
||||
HashSink fileHashSink(htSHA256);
|
||||
HashSink fileHashSink { htSHA256 };
|
||||
std::shared_ptr<FSAccessor> narAccessor;
|
||||
HashSink narHashSink { htSHA256 };
|
||||
{
|
||||
FdSink fileSink(fdTemp.get());
|
||||
TeeSink teeSink(fileSink, fileHashSink);
|
||||
auto compressionSink = makeCompressionSink(compression, teeSink);
|
||||
TeeSource teeSource(narSource, *compressionSink);
|
||||
TeeSink teeSinkCompressed { fileSink, fileHashSink };
|
||||
auto compressionSink = makeCompressionSink(compression, teeSinkCompressed);
|
||||
TeeSink teeSinkUncompressed { *compressionSink, narHashSink };
|
||||
TeeSource teeSource { narSource, teeSinkUncompressed };
|
||||
narAccessor = makeNarAccessor(teeSource);
|
||||
compressionSink->finish();
|
||||
fileSink.flush();
|
||||
|
|
@ -176,9 +171,8 @@ void BinaryCacheStore::addToStore(const ValidPathInfo & info, Source & narSource
|
|||
|
||||
auto now2 = std::chrono::steady_clock::now();
|
||||
|
||||
auto info = mkInfo(narHashSink.finish());
|
||||
auto narInfo = make_ref<NarInfo>(info);
|
||||
narInfo->narSize = info.narSize;
|
||||
narInfo->narHash = info.narHash;
|
||||
narInfo->compression = compression;
|
||||
auto [fileHash, fileSize] = fileHashSink.finish();
|
||||
narInfo->fileHash = fileHash;
|
||||
|
|
@ -300,6 +294,41 @@ void BinaryCacheStore::addToStore(const ValidPathInfo & info, Source & narSource
|
|||
writeNarInfo(narInfo);
|
||||
|
||||
stats.narInfoWrite++;
|
||||
|
||||
return narInfo;
|
||||
}
|
||||
|
||||
void BinaryCacheStore::addToStore(const ValidPathInfo & info, Source & narSource,
|
||||
RepairFlag repair, CheckSigsFlag checkSigs)
|
||||
{
|
||||
if (!repair && isValidPath(info.path)) {
|
||||
// FIXME: copyNAR -> null sink
|
||||
narSource.drain();
|
||||
return;
|
||||
}
|
||||
|
||||
addToStoreCommon(narSource, repair, checkSigs, {[&](HashResult nar) {
|
||||
/* FIXME reinstate these, once we can correctly do hash modulo sink as
|
||||
needed. We need to throw here in case we uploaded a corrupted store path. */
|
||||
// assert(info.narHash == nar.first);
|
||||
// assert(info.narSize == nar.second);
|
||||
return info;
|
||||
}});
|
||||
}
|
||||
|
||||
StorePath BinaryCacheStore::addToStoreFromDump(Source & dump, const string & name,
|
||||
FileIngestionMethod method, HashType hashAlgo, RepairFlag repair)
|
||||
{
|
||||
if (method != FileIngestionMethod::Recursive || hashAlgo != htSHA256)
|
||||
unsupported("addToStoreFromDump");
|
||||
return addToStoreCommon(dump, repair, CheckSigs, [&](HashResult nar) {
|
||||
ValidPathInfo info {
|
||||
makeFixedOutputPath(method, nar.first, name),
|
||||
nar.first,
|
||||
};
|
||||
info.narSize = nar.second;
|
||||
return info;
|
||||
})->path;
|
||||
}
|
||||
|
||||
bool BinaryCacheStore::isValidPathUncached(const StorePath & storePath)
|
||||
|
|
@ -367,50 +396,52 @@ void BinaryCacheStore::queryPathInfoUncached(const StorePath & storePath,
|
|||
StorePath BinaryCacheStore::addToStore(const string & name, const Path & srcPath,
|
||||
FileIngestionMethod method, HashType hashAlgo, PathFilter & filter, RepairFlag repair)
|
||||
{
|
||||
// FIXME: some cut&paste from LocalStore::addToStore().
|
||||
/* FIXME: Make BinaryCacheStore::addToStoreCommon support
|
||||
non-recursive+sha256 so we can just use the default
|
||||
implementation of this method in terms of addToStoreFromDump. */
|
||||
|
||||
/* Read the whole path into memory. This is not a very scalable
|
||||
method for very large paths, but `copyPath' is mainly used for
|
||||
small files. */
|
||||
StringSink sink;
|
||||
std::optional<Hash> h;
|
||||
HashSink sink { hashAlgo };
|
||||
if (method == FileIngestionMethod::Recursive) {
|
||||
dumpPath(srcPath, sink, filter);
|
||||
h = hashString(hashAlgo, *sink.s);
|
||||
} else {
|
||||
auto s = readFile(srcPath);
|
||||
dumpString(s, sink);
|
||||
h = hashString(hashAlgo, s);
|
||||
readFile(srcPath, sink);
|
||||
}
|
||||
auto h = sink.finish().first;
|
||||
|
||||
auto source = sinkToSource([&](Sink & sink) {
|
||||
dumpPath(srcPath, sink, filter);
|
||||
});
|
||||
return addToStoreCommon(*source, repair, CheckSigs, [&](HashResult nar) {
|
||||
ValidPathInfo info {
|
||||
makeFixedOutputPath(method, *h, name),
|
||||
Hash::dummy, // Will be fixed in addToStore, which recomputes nar hash
|
||||
makeFixedOutputPath(method, h, name),
|
||||
nar.first,
|
||||
};
|
||||
|
||||
auto source = StringSource { *sink.s };
|
||||
addToStore(info, source, repair, CheckSigs);
|
||||
|
||||
return std::move(info.path);
|
||||
info.narSize = nar.second;
|
||||
info.ca = FixedOutputHash {
|
||||
.method = method,
|
||||
.hash = h,
|
||||
};
|
||||
return info;
|
||||
})->path;
|
||||
}
|
||||
|
||||
StorePath BinaryCacheStore::addTextToStore(const string & name, const string & s,
|
||||
const StorePathSet & references, RepairFlag repair)
|
||||
{
|
||||
ValidPathInfo info {
|
||||
computeStorePathForText(name, s, references),
|
||||
Hash::dummy, // Will be fixed in addToStore, which recomputes nar hash
|
||||
};
|
||||
auto textHash = hashString(htSHA256, s);
|
||||
auto path = makeTextPath(name, textHash, references);
|
||||
|
||||
if (!repair && isValidPath(path))
|
||||
return path;
|
||||
|
||||
auto source = StringSource { s };
|
||||
return addToStoreCommon(source, repair, CheckSigs, [&](HashResult nar) {
|
||||
ValidPathInfo info { path, nar.first };
|
||||
info.narSize = nar.second;
|
||||
info.ca = TextHash { textHash };
|
||||
info.references = references;
|
||||
|
||||
if (repair || !isValidPath(info.path)) {
|
||||
StringSink sink;
|
||||
dumpString(s, sink);
|
||||
auto source = StringSource { *sink.s };
|
||||
addToStore(info, source, repair, CheckSigs);
|
||||
}
|
||||
|
||||
return std::move(info.path);
|
||||
return info;
|
||||
})->path;
|
||||
}
|
||||
|
||||
ref<FSAccessor> BinaryCacheStore::getFSAccessor()
|
||||
|
|
|
|||
|
|
@ -72,6 +72,10 @@ private:
|
|||
|
||||
void writeNarInfo(ref<NarInfo> narInfo);
|
||||
|
||||
ref<const ValidPathInfo> addToStoreCommon(
|
||||
Source & narSource, RepairFlag repair, CheckSigsFlag checkSigs,
|
||||
std::function<ValidPathInfo(HashResult)> mkInfo);
|
||||
|
||||
public:
|
||||
|
||||
bool isValidPathUncached(const StorePath & path) override;
|
||||
|
|
@ -85,6 +89,9 @@ public:
|
|||
void addToStore(const ValidPathInfo & info, Source & narSource,
|
||||
RepairFlag repair, CheckSigsFlag checkSigs) override;
|
||||
|
||||
StorePath addToStoreFromDump(Source & dump, const string & name,
|
||||
FileIngestionMethod method, HashType hashAlgo, RepairFlag repair) override;
|
||||
|
||||
StorePath addToStore(const string & name, const Path & srcPath,
|
||||
FileIngestionMethod method, HashType hashAlgo,
|
||||
PathFilter & filter, RepairFlag repair) override;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
379
src/libstore/build/derivation-goal.hh
Normal file
379
src/libstore/build/derivation-goal.hh
Normal file
|
|
@ -0,0 +1,379 @@
|
|||
#pragma once
|
||||
|
||||
#include "parsed-derivations.hh"
|
||||
#include "lock.hh"
|
||||
#include "local-store.hh"
|
||||
#include "goal.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
using std::map;
|
||||
|
||||
struct HookInstance;
|
||||
|
||||
typedef enum {rpAccept, rpDecline, rpPostpone} HookReply;
|
||||
|
||||
/* Unless we are repairing, we don't both to test validity and just assume it,
|
||||
so the choices are `Absent` or `Valid`. */
|
||||
enum struct PathStatus {
|
||||
Corrupt,
|
||||
Absent,
|
||||
Valid,
|
||||
};
|
||||
|
||||
struct InitialOutputStatus {
|
||||
StorePath path;
|
||||
PathStatus status;
|
||||
/* Valid in the store, and additionally non-corrupt if we are repairing */
|
||||
bool isValid() const {
|
||||
return status == PathStatus::Valid;
|
||||
}
|
||||
/* Merely present, allowed to be corrupt */
|
||||
bool isPresent() const {
|
||||
return status == PathStatus::Corrupt
|
||||
|| status == PathStatus::Valid;
|
||||
}
|
||||
};
|
||||
|
||||
struct InitialOutput {
|
||||
bool wanted;
|
||||
std::optional<InitialOutputStatus> known;
|
||||
};
|
||||
|
||||
class DerivationGoal : public Goal
|
||||
{
|
||||
private:
|
||||
/* Whether to use an on-disk .drv file. */
|
||||
bool useDerivation;
|
||||
|
||||
/* The path of the derivation. */
|
||||
StorePath drvPath;
|
||||
|
||||
/* The specific outputs that we need to build. Empty means all of
|
||||
them. */
|
||||
StringSet wantedOutputs;
|
||||
|
||||
/* Whether additional wanted outputs have been added. */
|
||||
bool needRestart = false;
|
||||
|
||||
/* Whether to retry substituting the outputs after building the
|
||||
inputs. */
|
||||
bool retrySubstitution;
|
||||
|
||||
/* The derivation stored at drvPath. */
|
||||
std::unique_ptr<BasicDerivation> drv;
|
||||
|
||||
std::unique_ptr<ParsedDerivation> parsedDrv;
|
||||
|
||||
/* The remainder is state held during the build. */
|
||||
|
||||
/* Locks on (fixed) output paths. */
|
||||
PathLocks outputLocks;
|
||||
|
||||
/* All input paths (that is, the union of FS closures of the
|
||||
immediate input paths). */
|
||||
StorePathSet inputPaths;
|
||||
|
||||
std::map<std::string, InitialOutput> initialOutputs;
|
||||
|
||||
/* User selected for running the builder. */
|
||||
std::unique_ptr<UserLock> buildUser;
|
||||
|
||||
/* The process ID of the builder. */
|
||||
Pid pid;
|
||||
|
||||
/* The temporary directory. */
|
||||
Path tmpDir;
|
||||
|
||||
/* The path of the temporary directory in the sandbox. */
|
||||
Path tmpDirInSandbox;
|
||||
|
||||
/* File descriptor for the log file. */
|
||||
AutoCloseFD fdLogFile;
|
||||
std::shared_ptr<BufferedSink> logFileSink, logSink;
|
||||
|
||||
/* Number of bytes received from the builder's stdout/stderr. */
|
||||
unsigned long logSize;
|
||||
|
||||
/* The most recent log lines. */
|
||||
std::list<std::string> logTail;
|
||||
|
||||
std::string currentLogLine;
|
||||
size_t currentLogLinePos = 0; // to handle carriage return
|
||||
|
||||
std::string currentHookLine;
|
||||
|
||||
/* Pipe for the builder's standard output/error. */
|
||||
Pipe builderOut;
|
||||
|
||||
/* Pipe for synchronising updates to the builder namespaces. */
|
||||
Pipe userNamespaceSync;
|
||||
|
||||
/* The mount namespace of the builder, used to add additional
|
||||
paths to the sandbox as a result of recursive Nix calls. */
|
||||
AutoCloseFD sandboxMountNamespace;
|
||||
|
||||
/* On Linux, whether we're doing the build in its own user
|
||||
namespace. */
|
||||
bool usingUserNamespace = true;
|
||||
|
||||
/* The build hook. */
|
||||
std::unique_ptr<HookInstance> hook;
|
||||
|
||||
/* Whether we're currently doing a chroot build. */
|
||||
bool useChroot = false;
|
||||
|
||||
Path chrootRootDir;
|
||||
|
||||
/* RAII object to delete the chroot directory. */
|
||||
std::shared_ptr<AutoDelete> autoDelChroot;
|
||||
|
||||
/* The sort of derivation we are building. */
|
||||
DerivationType derivationType;
|
||||
|
||||
/* Whether to run the build in a private network namespace. */
|
||||
bool privateNetwork = false;
|
||||
|
||||
typedef void (DerivationGoal::*GoalState)();
|
||||
GoalState state;
|
||||
|
||||
/* Stuff we need to pass to initChild(). */
|
||||
struct ChrootPath {
|
||||
Path source;
|
||||
bool optional;
|
||||
ChrootPath(Path source = "", bool optional = false)
|
||||
: source(source), optional(optional)
|
||||
{ }
|
||||
};
|
||||
typedef map<Path, ChrootPath> DirsInChroot; // maps target path to source path
|
||||
DirsInChroot dirsInChroot;
|
||||
|
||||
typedef map<string, string> Environment;
|
||||
Environment env;
|
||||
|
||||
#if __APPLE__
|
||||
typedef string SandboxProfile;
|
||||
SandboxProfile additionalSandboxProfile;
|
||||
#endif
|
||||
|
||||
/* Hash rewriting. */
|
||||
StringMap inputRewrites, outputRewrites;
|
||||
typedef map<StorePath, StorePath> RedirectedOutputs;
|
||||
RedirectedOutputs redirectedOutputs;
|
||||
|
||||
/* The outputs paths used during the build.
|
||||
|
||||
- Input-addressed derivations or fixed content-addressed outputs are
|
||||
sometimes built when some of their outputs already exist, and can not
|
||||
be hidden via sandboxing. We use temporary locations instead and
|
||||
rewrite after the build. Otherwise the regular predetermined paths are
|
||||
put here.
|
||||
|
||||
- Floating content-addressed derivations do not know their final build
|
||||
output paths until the outputs are hashed, so random locations are
|
||||
used, and then renamed. The randomness helps guard against hidden
|
||||
self-references.
|
||||
*/
|
||||
OutputPathMap scratchOutputs;
|
||||
|
||||
/* The final output paths of the build.
|
||||
|
||||
- For input-addressed derivations, always the precomputed paths
|
||||
|
||||
- For content-addressed derivations, calcuated from whatever the hash
|
||||
ends up being. (Note that fixed outputs derivations that produce the
|
||||
"wrong" output still install that data under its true content-address.)
|
||||
*/
|
||||
OutputPathMap finalOutputs;
|
||||
|
||||
BuildMode buildMode;
|
||||
|
||||
/* If we're repairing without a chroot, there may be outputs that
|
||||
are valid but corrupt. So we redirect these outputs to
|
||||
temporary paths. */
|
||||
StorePathSet redirectedBadOutputs;
|
||||
|
||||
BuildResult result;
|
||||
|
||||
/* The current round, if we're building multiple times. */
|
||||
size_t curRound = 1;
|
||||
|
||||
size_t nrRounds;
|
||||
|
||||
/* Path registration info from the previous round, if we're
|
||||
building multiple times. Since this contains the hash, it
|
||||
allows us to compare whether two rounds produced the same
|
||||
result. */
|
||||
std::map<Path, ValidPathInfo> prevInfos;
|
||||
|
||||
uid_t sandboxUid() { return usingUserNamespace ? 1000 : buildUser->getUID(); }
|
||||
gid_t sandboxGid() { return usingUserNamespace ? 100 : buildUser->getGID(); }
|
||||
|
||||
const static Path homeDir;
|
||||
|
||||
std::unique_ptr<MaintainCount<uint64_t>> mcExpectedBuilds, mcRunningBuilds;
|
||||
|
||||
std::unique_ptr<Activity> act;
|
||||
|
||||
/* Activity that denotes waiting for a lock. */
|
||||
std::unique_ptr<Activity> actLock;
|
||||
|
||||
std::map<ActivityId, Activity> builderActivities;
|
||||
|
||||
/* The remote machine on which we're building. */
|
||||
std::string machineName;
|
||||
|
||||
/* The recursive Nix daemon socket. */
|
||||
AutoCloseFD daemonSocket;
|
||||
|
||||
/* The daemon main thread. */
|
||||
std::thread daemonThread;
|
||||
|
||||
/* The daemon worker threads. */
|
||||
std::vector<std::thread> daemonWorkerThreads;
|
||||
|
||||
/* Paths that were added via recursive Nix calls. */
|
||||
StorePathSet addedPaths;
|
||||
|
||||
/* Recursive Nix calls are only allowed to build or realize paths
|
||||
in the original input closure or added via a recursive Nix call
|
||||
(so e.g. you can't do 'nix-store -r /nix/store/<bla>' where
|
||||
/nix/store/<bla> is some arbitrary path in a binary cache). */
|
||||
bool isAllowed(const StorePath & path)
|
||||
{
|
||||
return inputPaths.count(path) || addedPaths.count(path);
|
||||
}
|
||||
|
||||
friend struct RestrictedStore;
|
||||
|
||||
public:
|
||||
DerivationGoal(const StorePath & drvPath,
|
||||
const StringSet & wantedOutputs, Worker & worker,
|
||||
BuildMode buildMode = bmNormal);
|
||||
DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
|
||||
const StringSet & wantedOutputs, Worker & worker,
|
||||
BuildMode buildMode = bmNormal);
|
||||
~DerivationGoal();
|
||||
|
||||
/* Whether we need to perform hash rewriting if there are valid output paths. */
|
||||
bool needsHashRewrite();
|
||||
|
||||
void timedOut(Error && ex) override;
|
||||
|
||||
string key() override;
|
||||
|
||||
void work() override;
|
||||
|
||||
StorePath getDrvPath()
|
||||
{
|
||||
return drvPath;
|
||||
}
|
||||
|
||||
/* Add wanted outputs to an already existing derivation goal. */
|
||||
void addWantedOutputs(const StringSet & outputs);
|
||||
|
||||
BuildResult getResult() { return result; }
|
||||
|
||||
private:
|
||||
/* The states. */
|
||||
void getDerivation();
|
||||
void loadDerivation();
|
||||
void haveDerivation();
|
||||
void outputsSubstitutionTried();
|
||||
void gaveUpOnSubstitution();
|
||||
void closureRepaired();
|
||||
void inputsRealised();
|
||||
void tryToBuild();
|
||||
void tryLocalBuild();
|
||||
void buildDone();
|
||||
|
||||
void resolvedFinished();
|
||||
|
||||
/* Is the build hook willing to perform the build? */
|
||||
HookReply tryBuildHook();
|
||||
|
||||
/* Start building a derivation. */
|
||||
void startBuilder();
|
||||
|
||||
/* Fill in the environment for the builder. */
|
||||
void initEnv();
|
||||
|
||||
/* Setup tmp dir location. */
|
||||
void initTmpDir();
|
||||
|
||||
/* Write a JSON file containing the derivation attributes. */
|
||||
void writeStructuredAttrs();
|
||||
|
||||
void startDaemon();
|
||||
|
||||
void stopDaemon();
|
||||
|
||||
/* Add 'path' to the set of paths that may be referenced by the
|
||||
outputs, and make it appear in the sandbox. */
|
||||
void addDependency(const StorePath & path);
|
||||
|
||||
/* Make a file owned by the builder. */
|
||||
void chownToBuilder(const Path & path);
|
||||
|
||||
/* Run the builder's process. */
|
||||
void runChild();
|
||||
|
||||
friend int childEntry(void *);
|
||||
|
||||
/* Check that the derivation outputs all exist and register them
|
||||
as valid. */
|
||||
void registerOutputs();
|
||||
|
||||
/* Check that an output meets the requirements specified by the
|
||||
'outputChecks' attribute (or the legacy
|
||||
'{allowed,disallowed}{References,Requisites}' attributes). */
|
||||
void checkOutputs(const std::map<std::string, ValidPathInfo> & outputs);
|
||||
|
||||
/* Open a log file and a pipe to it. */
|
||||
Path openLogFile();
|
||||
|
||||
/* Close the log file. */
|
||||
void closeLogFile();
|
||||
|
||||
/* Delete the temporary directory, if we have one. */
|
||||
void deleteTmpDir(bool force);
|
||||
|
||||
/* Callback used by the worker to write to the log. */
|
||||
void handleChildOutput(int fd, const string & data) override;
|
||||
void handleEOF(int fd) override;
|
||||
void flushLine();
|
||||
|
||||
/* Wrappers around the corresponding Store methods that first consult the
|
||||
derivation. This is currently needed because when there is no drv file
|
||||
there also is no DB entry. */
|
||||
std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap();
|
||||
OutputPathMap queryDerivationOutputMap();
|
||||
|
||||
/* Return the set of (in)valid paths. */
|
||||
void checkPathValidity();
|
||||
|
||||
/* Forcibly kill the child process, if any. */
|
||||
void killChild();
|
||||
|
||||
/* Create alternative path calculated from but distinct from the
|
||||
input, so we can avoid overwriting outputs (or other store paths)
|
||||
that already exist. */
|
||||
StorePath makeFallbackPath(const StorePath & path);
|
||||
/* Make a path to another based on the output name along with the
|
||||
derivation hash. */
|
||||
/* FIXME add option to randomize, so we can audit whether our
|
||||
rewrites caught everything */
|
||||
StorePath makeFallbackPath(std::string_view outputName);
|
||||
|
||||
void repairClosure();
|
||||
|
||||
void started();
|
||||
|
||||
void done(
|
||||
BuildResult::Status status,
|
||||
std::optional<Error> ex = {});
|
||||
|
||||
StorePathSet exportReferences(const StorePathSet & storePaths);
|
||||
};
|
||||
|
||||
}
|
||||
89
src/libstore/build/goal.cc
Normal file
89
src/libstore/build/goal.cc
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
#include "goal.hh"
|
||||
#include "worker.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
bool CompareGoalPtrs::operator() (const GoalPtr & a, const GoalPtr & b) const {
|
||||
string s1 = a->key();
|
||||
string s2 = b->key();
|
||||
return s1 < s2;
|
||||
}
|
||||
|
||||
|
||||
void addToWeakGoals(WeakGoals & goals, GoalPtr p)
|
||||
{
|
||||
// FIXME: necessary?
|
||||
// FIXME: O(n)
|
||||
for (auto & i : goals)
|
||||
if (i.lock() == p) return;
|
||||
goals.push_back(p);
|
||||
}
|
||||
|
||||
|
||||
void Goal::addWaitee(GoalPtr waitee)
|
||||
{
|
||||
waitees.insert(waitee);
|
||||
addToWeakGoals(waitee->waiters, shared_from_this());
|
||||
}
|
||||
|
||||
|
||||
void Goal::waiteeDone(GoalPtr waitee, ExitCode result)
|
||||
{
|
||||
assert(waitees.find(waitee) != waitees.end());
|
||||
waitees.erase(waitee);
|
||||
|
||||
trace(fmt("waitee '%s' done; %d left", waitee->name, waitees.size()));
|
||||
|
||||
if (result == ecFailed || result == ecNoSubstituters || result == ecIncompleteClosure) ++nrFailed;
|
||||
|
||||
if (result == ecNoSubstituters) ++nrNoSubstituters;
|
||||
|
||||
if (result == ecIncompleteClosure) ++nrIncompleteClosure;
|
||||
|
||||
if (waitees.empty() || (result == ecFailed && !settings.keepGoing)) {
|
||||
|
||||
/* If we failed and keepGoing is not set, we remove all
|
||||
remaining waitees. */
|
||||
for (auto & goal : waitees) {
|
||||
WeakGoals waiters2;
|
||||
for (auto & j : goal->waiters)
|
||||
if (j.lock() != shared_from_this()) waiters2.push_back(j);
|
||||
goal->waiters = waiters2;
|
||||
}
|
||||
waitees.clear();
|
||||
|
||||
worker.wakeUp(shared_from_this());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Goal::amDone(ExitCode result, std::optional<Error> ex)
|
||||
{
|
||||
trace("done");
|
||||
assert(exitCode == ecBusy);
|
||||
assert(result == ecSuccess || result == ecFailed || result == ecNoSubstituters || result == ecIncompleteClosure);
|
||||
exitCode = result;
|
||||
|
||||
if (ex) {
|
||||
if (!waiters.empty())
|
||||
logError(ex->info());
|
||||
else
|
||||
this->ex = std::move(*ex);
|
||||
}
|
||||
|
||||
for (auto & i : waiters) {
|
||||
GoalPtr goal = i.lock();
|
||||
if (goal) goal->waiteeDone(shared_from_this(), result);
|
||||
}
|
||||
waiters.clear();
|
||||
worker.removeGoal(shared_from_this());
|
||||
}
|
||||
|
||||
|
||||
void Goal::trace(const FormatOrString & fs)
|
||||
{
|
||||
debug("%1%: %2%", name, fs.s);
|
||||
}
|
||||
|
||||
}
|
||||
107
src/libstore/build/goal.hh
Normal file
107
src/libstore/build/goal.hh
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
/* Forward definition. */
|
||||
struct Goal;
|
||||
struct Worker;
|
||||
|
||||
/* A pointer to a goal. */
|
||||
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. */
|
||||
typedef set<GoalPtr, CompareGoalPtrs> Goals;
|
||||
typedef list<WeakGoalPtr> WeakGoals;
|
||||
|
||||
/* A map of paths to goals (and the other way around). */
|
||||
typedef std::map<StorePath, WeakGoalPtr> WeakGoalMap;
|
||||
|
||||
struct Goal : public std::enable_shared_from_this<Goal>
|
||||
{
|
||||
typedef enum {ecBusy, ecSuccess, ecFailed, ecNoSubstituters, ecIncompleteClosure} ExitCode;
|
||||
|
||||
/* Backlink to the worker. */
|
||||
Worker & worker;
|
||||
|
||||
/* Goals that this goal is waiting for. */
|
||||
Goals waitees;
|
||||
|
||||
/* 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. */
|
||||
unsigned int nrFailed;
|
||||
|
||||
/* Number of substitution goals we are/were waiting for that
|
||||
failed because there are no substituters. */
|
||||
unsigned int nrNoSubstituters;
|
||||
|
||||
/* Number of substitution goals we are/were waiting for that
|
||||
failed because othey had unsubstitutable references. */
|
||||
unsigned int nrIncompleteClosure;
|
||||
|
||||
/* Name of this goal for debugging purposes. */
|
||||
string name;
|
||||
|
||||
/* Whether the goal is finished. */
|
||||
ExitCode exitCode;
|
||||
|
||||
/* Exception containing an error message, if any. */
|
||||
std::optional<Error> ex;
|
||||
|
||||
Goal(Worker & worker) : worker(worker)
|
||||
{
|
||||
nrFailed = nrNoSubstituters = nrIncompleteClosure = 0;
|
||||
exitCode = ecBusy;
|
||||
}
|
||||
|
||||
virtual ~Goal()
|
||||
{
|
||||
trace("goal destroyed");
|
||||
}
|
||||
|
||||
virtual void work() = 0;
|
||||
|
||||
void addWaitee(GoalPtr waitee);
|
||||
|
||||
virtual void waiteeDone(GoalPtr waitee, ExitCode result);
|
||||
|
||||
virtual void handleChildOutput(int fd, const string & data)
|
||||
{
|
||||
abort();
|
||||
}
|
||||
|
||||
virtual void handleEOF(int fd)
|
||||
{
|
||||
abort();
|
||||
}
|
||||
|
||||
void trace(const FormatOrString & fs);
|
||||
|
||||
string getName()
|
||||
{
|
||||
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 void timedOut(Error && ex) = 0;
|
||||
|
||||
virtual string key() = 0;
|
||||
|
||||
void amDone(ExitCode result, std::optional<Error> ex = {});
|
||||
};
|
||||
|
||||
void addToWeakGoals(WeakGoals & goals, GoalPtr p);
|
||||
|
||||
}
|
||||
72
src/libstore/build/hook-instance.cc
Normal file
72
src/libstore/build/hook-instance.cc
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#include "globals.hh"
|
||||
#include "hook-instance.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
HookInstance::HookInstance()
|
||||
{
|
||||
debug("starting build hook '%s'", settings.buildHook);
|
||||
|
||||
/* Create a pipe to get the output of the child. */
|
||||
fromHook.create();
|
||||
|
||||
/* Create the communication pipes. */
|
||||
toHook.create();
|
||||
|
||||
/* Create a pipe to get the output of the builder. */
|
||||
builderOut.create();
|
||||
|
||||
/* Fork the hook. */
|
||||
pid = startProcess([&]() {
|
||||
|
||||
commonChildInit(fromHook);
|
||||
|
||||
if (chdir("/") == -1) throw SysError("changing into /");
|
||||
|
||||
/* Dup the communication pipes. */
|
||||
if (dup2(toHook.readSide.get(), STDIN_FILENO) == -1)
|
||||
throw SysError("dupping to-hook read side");
|
||||
|
||||
/* Use fd 4 for the builder's stdout/stderr. */
|
||||
if (dup2(builderOut.writeSide.get(), 4) == -1)
|
||||
throw SysError("dupping builder's stdout/stderr");
|
||||
|
||||
/* Hack: pass the read side of that fd to allow build-remote
|
||||
to read SSH error messages. */
|
||||
if (dup2(builderOut.readSide.get(), 5) == -1)
|
||||
throw SysError("dupping builder's stdout/stderr");
|
||||
|
||||
Strings args = {
|
||||
std::string(baseNameOf(settings.buildHook.get())),
|
||||
std::to_string(verbosity),
|
||||
};
|
||||
|
||||
execv(settings.buildHook.get().c_str(), stringsToCharPtrs(args).data());
|
||||
|
||||
throw SysError("executing '%s'", settings.buildHook);
|
||||
});
|
||||
|
||||
pid.setSeparatePG(true);
|
||||
fromHook.writeSide = -1;
|
||||
toHook.readSide = -1;
|
||||
|
||||
sink = FdSink(toHook.writeSide.get());
|
||||
std::map<std::string, Config::SettingInfo> settings;
|
||||
globalConfig.getSettings(settings);
|
||||
for (auto & setting : settings)
|
||||
sink << 1 << setting.first << setting.second.value;
|
||||
sink << 0;
|
||||
}
|
||||
|
||||
|
||||
HookInstance::~HookInstance()
|
||||
{
|
||||
try {
|
||||
toHook.writeSide = -1;
|
||||
if (pid != -1) pid.kill();
|
||||
} catch (...) {
|
||||
ignoreException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
31
src/libstore/build/hook-instance.hh
Normal file
31
src/libstore/build/hook-instance.hh
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
#include "logging.hh"
|
||||
#include "serialise.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct HookInstance
|
||||
{
|
||||
/* Pipes for talking to the build hook. */
|
||||
Pipe toHook;
|
||||
|
||||
/* Pipe for the hook's standard output/error. */
|
||||
Pipe fromHook;
|
||||
|
||||
/* Pipe for the builder's standard output/error. */
|
||||
Pipe builderOut;
|
||||
|
||||
/* The process ID of the hook. */
|
||||
Pid pid;
|
||||
|
||||
FdSink sink;
|
||||
|
||||
std::map<ActivityId, Activity> activities;
|
||||
|
||||
HookInstance();
|
||||
|
||||
~HookInstance();
|
||||
};
|
||||
|
||||
}
|
||||
126
src/libstore/build/local-store-build.cc
Normal file
126
src/libstore/build/local-store-build.cc
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
#include "machines.hh"
|
||||
#include "worker.hh"
|
||||
#include "substitution-goal.hh"
|
||||
#include "derivation-goal.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
static void primeCache(Store & store, const std::vector<StorePathWithOutputs> & paths)
|
||||
{
|
||||
StorePathSet willBuild, willSubstitute, unknown;
|
||||
uint64_t downloadSize, narSize;
|
||||
store.queryMissing(paths, willBuild, willSubstitute, unknown, downloadSize, narSize);
|
||||
|
||||
if (!willBuild.empty() && 0 == settings.maxBuildJobs && getMachines().empty())
|
||||
throw Error(
|
||||
"%d derivations need to be built, but neither local builds ('--max-jobs') "
|
||||
"nor remote builds ('--builders') are enabled", willBuild.size());
|
||||
}
|
||||
|
||||
|
||||
void LocalStore::buildPaths(const std::vector<StorePathWithOutputs> & drvPaths, BuildMode buildMode)
|
||||
{
|
||||
Worker worker(*this);
|
||||
|
||||
primeCache(*this, drvPaths);
|
||||
|
||||
Goals goals;
|
||||
for (auto & path : drvPaths) {
|
||||
if (path.path.isDerivation())
|
||||
goals.insert(worker.makeDerivationGoal(path.path, path.outputs, buildMode));
|
||||
else
|
||||
goals.insert(worker.makeSubstitutionGoal(path.path, buildMode == bmRepair ? Repair : NoRepair));
|
||||
}
|
||||
|
||||
worker.run(goals);
|
||||
|
||||
StorePathSet failed;
|
||||
std::optional<Error> ex;
|
||||
for (auto & i : goals) {
|
||||
if (i->ex) {
|
||||
if (ex)
|
||||
logError(i->ex->info());
|
||||
else
|
||||
ex = i->ex;
|
||||
}
|
||||
if (i->exitCode != Goal::ecSuccess) {
|
||||
DerivationGoal * i2 = dynamic_cast<DerivationGoal *>(i.get());
|
||||
if (i2) failed.insert(i2->getDrvPath());
|
||||
else failed.insert(dynamic_cast<SubstitutionGoal *>(i.get())->getStorePath());
|
||||
}
|
||||
}
|
||||
|
||||
if (failed.size() == 1 && ex) {
|
||||
ex->status = worker.exitStatus();
|
||||
throw *ex;
|
||||
} else if (!failed.empty()) {
|
||||
if (ex) logError(ex->info());
|
||||
throw Error(worker.exitStatus(), "build of %s failed", showPaths(failed));
|
||||
}
|
||||
}
|
||||
|
||||
BuildResult LocalStore::buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
||||
BuildMode buildMode)
|
||||
{
|
||||
Worker worker(*this);
|
||||
auto goal = worker.makeBasicDerivationGoal(drvPath, drv, {}, buildMode);
|
||||
|
||||
BuildResult result;
|
||||
|
||||
try {
|
||||
worker.run(Goals{goal});
|
||||
result = goal->getResult();
|
||||
} catch (Error & e) {
|
||||
result.status = BuildResult::MiscFailure;
|
||||
result.errorMsg = e.msg();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void LocalStore::ensurePath(const StorePath & path)
|
||||
{
|
||||
/* If the path is already valid, we're done. */
|
||||
if (isValidPath(path)) return;
|
||||
|
||||
primeCache(*this, {{path}});
|
||||
|
||||
Worker worker(*this);
|
||||
GoalPtr goal = worker.makeSubstitutionGoal(path);
|
||||
Goals goals = {goal};
|
||||
|
||||
worker.run(goals);
|
||||
|
||||
if (goal->exitCode != Goal::ecSuccess) {
|
||||
if (goal->ex) {
|
||||
goal->ex->status = worker.exitStatus();
|
||||
throw *goal->ex;
|
||||
} else
|
||||
throw Error(worker.exitStatus(), "path '%s' does not exist and cannot be created", printStorePath(path));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LocalStore::repairPath(const StorePath & path)
|
||||
{
|
||||
Worker worker(*this);
|
||||
GoalPtr goal = worker.makeSubstitutionGoal(path, Repair);
|
||||
Goals goals = {goal};
|
||||
|
||||
worker.run(goals);
|
||||
|
||||
if (goal->exitCode != Goal::ecSuccess) {
|
||||
/* Since substituting the path didn't work, if we have a valid
|
||||
deriver, then rebuild the deriver. */
|
||||
auto info = queryPathInfo(path);
|
||||
if (info->deriver && isValidPath(*info->deriver)) {
|
||||
goals.clear();
|
||||
goals.insert(worker.makeDerivationGoal(*info->deriver, StringSet(), bmRepair));
|
||||
worker.run(goals);
|
||||
} else
|
||||
throw Error(worker.exitStatus(), "cannot repair path '%s'", printStorePath(path));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
296
src/libstore/build/substitution-goal.cc
Normal file
296
src/libstore/build/substitution-goal.cc
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
#include "worker.hh"
|
||||
#include "substitution-goal.hh"
|
||||
#include "nar-info.hh"
|
||||
#include "finally.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
SubstitutionGoal::SubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair, std::optional<ContentAddress> ca)
|
||||
: Goal(worker)
|
||||
, storePath(storePath)
|
||||
, repair(repair)
|
||||
, ca(ca)
|
||||
{
|
||||
state = &SubstitutionGoal::init;
|
||||
name = fmt("substitution of '%s'", worker.store.printStorePath(this->storePath));
|
||||
trace("created");
|
||||
maintainExpectedSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.expectedSubstitutions);
|
||||
}
|
||||
|
||||
|
||||
SubstitutionGoal::~SubstitutionGoal()
|
||||
{
|
||||
try {
|
||||
if (thr.joinable()) {
|
||||
// FIXME: signal worker thread to quit.
|
||||
thr.join();
|
||||
worker.childTerminated(this);
|
||||
}
|
||||
} catch (...) {
|
||||
ignoreException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SubstitutionGoal::work()
|
||||
{
|
||||
(this->*state)();
|
||||
}
|
||||
|
||||
|
||||
void SubstitutionGoal::init()
|
||||
{
|
||||
trace("init");
|
||||
|
||||
worker.store.addTempRoot(storePath);
|
||||
|
||||
/* If the path already exists we're done. */
|
||||
if (!repair && worker.store.isValidPath(storePath)) {
|
||||
amDone(ecSuccess);
|
||||
return;
|
||||
}
|
||||
|
||||
if (settings.readOnlyMode)
|
||||
throw Error("cannot substitute path '%s' - no write access to the Nix store", worker.store.printStorePath(storePath));
|
||||
|
||||
subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
|
||||
|
||||
tryNext();
|
||||
}
|
||||
|
||||
|
||||
void SubstitutionGoal::tryNext()
|
||||
{
|
||||
trace("trying next substituter");
|
||||
|
||||
if (subs.size() == 0) {
|
||||
/* None left. Terminate this goal and let someone else deal
|
||||
with it. */
|
||||
debug("path '%s' is required, but there is no substituter that can build it", worker.store.printStorePath(storePath));
|
||||
|
||||
/* Hack: don't indicate failure if there were no substituters.
|
||||
In that case the calling derivation should just do a
|
||||
build. */
|
||||
amDone(substituterFailed ? ecFailed : ecNoSubstituters);
|
||||
|
||||
if (substituterFailed) {
|
||||
worker.failedSubstitutions++;
|
||||
worker.updateProgress();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub = subs.front();
|
||||
subs.pop_front();
|
||||
|
||||
if (ca) {
|
||||
subPath = sub->makeFixedOutputPathFromCA(storePath.name(), *ca);
|
||||
if (sub->storeDir == worker.store.storeDir)
|
||||
assert(subPath == storePath);
|
||||
} else if (sub->storeDir != worker.store.storeDir) {
|
||||
tryNext();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// FIXME: make async
|
||||
info = sub->queryPathInfo(subPath ? *subPath : storePath);
|
||||
} catch (InvalidPath &) {
|
||||
tryNext();
|
||||
return;
|
||||
} catch (SubstituterDisabled &) {
|
||||
if (settings.tryFallback) {
|
||||
tryNext();
|
||||
return;
|
||||
}
|
||||
throw;
|
||||
} catch (Error & e) {
|
||||
if (settings.tryFallback) {
|
||||
logError(e.info());
|
||||
tryNext();
|
||||
return;
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
if (info->path != storePath) {
|
||||
if (info->isContentAddressed(*sub) && info->references.empty()) {
|
||||
auto info2 = std::make_shared<ValidPathInfo>(*info);
|
||||
info2->path = storePath;
|
||||
info = info2;
|
||||
} else {
|
||||
printError("asked '%s' for '%s' but got '%s'",
|
||||
sub->getUri(), worker.store.printStorePath(storePath), sub->printStorePath(info->path));
|
||||
tryNext();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the total expected download size. */
|
||||
auto narInfo = std::dynamic_pointer_cast<const NarInfo>(info);
|
||||
|
||||
maintainExpectedNar = std::make_unique<MaintainCount<uint64_t>>(worker.expectedNarSize, info->narSize);
|
||||
|
||||
maintainExpectedDownload =
|
||||
narInfo && narInfo->fileSize
|
||||
? std::make_unique<MaintainCount<uint64_t>>(worker.expectedDownloadSize, narInfo->fileSize)
|
||||
: nullptr;
|
||||
|
||||
worker.updateProgress();
|
||||
|
||||
/* Bail out early if this substituter lacks a valid
|
||||
signature. LocalStore::addToStore() also checks for this, but
|
||||
only after we've downloaded the path. */
|
||||
if (worker.store.requireSigs
|
||||
&& !sub->isTrusted
|
||||
&& !info->checkSignatures(worker.store, worker.store.getPublicKeys()))
|
||||
{
|
||||
logWarning({
|
||||
.name = "Invalid path signature",
|
||||
.hint = hintfmt("substituter '%s' does not have a valid signature for path '%s'",
|
||||
sub->getUri(), worker.store.printStorePath(storePath))
|
||||
});
|
||||
tryNext();
|
||||
return;
|
||||
}
|
||||
|
||||
/* To maintain the closure invariant, we first have to realise the
|
||||
paths referenced by this one. */
|
||||
for (auto & i : info->references)
|
||||
if (i != storePath) /* ignore self-references */
|
||||
addWaitee(worker.makeSubstitutionGoal(i));
|
||||
|
||||
if (waitees.empty()) /* to prevent hang (no wake-up event) */
|
||||
referencesValid();
|
||||
else
|
||||
state = &SubstitutionGoal::referencesValid;
|
||||
}
|
||||
|
||||
|
||||
void SubstitutionGoal::referencesValid()
|
||||
{
|
||||
trace("all references realised");
|
||||
|
||||
if (nrFailed > 0) {
|
||||
debug("some references of path '%s' could not be realised", worker.store.printStorePath(storePath));
|
||||
amDone(nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto & i : info->references)
|
||||
if (i != storePath) /* ignore self-references */
|
||||
assert(worker.store.isValidPath(i));
|
||||
|
||||
state = &SubstitutionGoal::tryToRun;
|
||||
worker.wakeUp(shared_from_this());
|
||||
}
|
||||
|
||||
|
||||
void SubstitutionGoal::tryToRun()
|
||||
{
|
||||
trace("trying to run");
|
||||
|
||||
/* Make sure that we are allowed to start a build. Note that even
|
||||
if maxBuildJobs == 0 (no local builds allowed), we still allow
|
||||
a substituter to run. This is because substitutions cannot be
|
||||
distributed to another machine via the build hook. */
|
||||
if (worker.getNrLocalBuilds() >= std::max(1U, (unsigned int) settings.maxBuildJobs)) {
|
||||
worker.waitForBuildSlot(shared_from_this());
|
||||
return;
|
||||
}
|
||||
|
||||
maintainRunningSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.runningSubstitutions);
|
||||
worker.updateProgress();
|
||||
|
||||
outPipe.create();
|
||||
|
||||
promise = std::promise<void>();
|
||||
|
||||
thr = std::thread([this]() {
|
||||
try {
|
||||
/* Wake up the worker loop when we're done. */
|
||||
Finally updateStats([this]() { outPipe.writeSide = -1; });
|
||||
|
||||
Activity act(*logger, actSubstitute, Logger::Fields{worker.store.printStorePath(storePath), sub->getUri()});
|
||||
PushActivity pact(act.id);
|
||||
|
||||
copyStorePath(ref<Store>(sub), ref<Store>(worker.store.shared_from_this()),
|
||||
subPath ? *subPath : storePath, repair, sub->isTrusted ? NoCheckSigs : CheckSigs);
|
||||
|
||||
promise.set_value();
|
||||
} catch (...) {
|
||||
promise.set_exception(std::current_exception());
|
||||
}
|
||||
});
|
||||
|
||||
worker.childStarted(shared_from_this(), {outPipe.readSide.get()}, true, false);
|
||||
|
||||
state = &SubstitutionGoal::finished;
|
||||
}
|
||||
|
||||
|
||||
void SubstitutionGoal::finished()
|
||||
{
|
||||
trace("substitute finished");
|
||||
|
||||
thr.join();
|
||||
worker.childTerminated(this);
|
||||
|
||||
try {
|
||||
promise.get_future().get();
|
||||
} catch (std::exception & e) {
|
||||
printError(e.what());
|
||||
|
||||
/* Cause the parent build to fail unless --fallback is given,
|
||||
or the substitute has disappeared. The latter case behaves
|
||||
the same as the substitute never having existed in the
|
||||
first place. */
|
||||
try {
|
||||
throw;
|
||||
} catch (SubstituteGone &) {
|
||||
} catch (...) {
|
||||
substituterFailed = true;
|
||||
}
|
||||
|
||||
/* Try the next substitute. */
|
||||
state = &SubstitutionGoal::tryNext;
|
||||
worker.wakeUp(shared_from_this());
|
||||
return;
|
||||
}
|
||||
|
||||
worker.markContentsGood(storePath);
|
||||
|
||||
printMsg(lvlChatty, "substitution of path '%s' succeeded", worker.store.printStorePath(storePath));
|
||||
|
||||
maintainRunningSubstitutions.reset();
|
||||
|
||||
maintainExpectedSubstitutions.reset();
|
||||
worker.doneSubstitutions++;
|
||||
|
||||
if (maintainExpectedDownload) {
|
||||
auto fileSize = maintainExpectedDownload->delta;
|
||||
maintainExpectedDownload.reset();
|
||||
worker.doneDownloadSize += fileSize;
|
||||
}
|
||||
|
||||
worker.doneNarSize += maintainExpectedNar->delta;
|
||||
maintainExpectedNar.reset();
|
||||
|
||||
worker.updateProgress();
|
||||
|
||||
amDone(ecSuccess);
|
||||
}
|
||||
|
||||
|
||||
void SubstitutionGoal::handleChildOutput(int fd, const string & data)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void SubstitutionGoal::handleEOF(int fd)
|
||||
{
|
||||
if (fd == outPipe.readSide.get()) worker.wakeUp(shared_from_this());
|
||||
}
|
||||
|
||||
}
|
||||
89
src/libstore/build/substitution-goal.hh
Normal file
89
src/libstore/build/substitution-goal.hh
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
#pragma once
|
||||
|
||||
#include "lock.hh"
|
||||
#include "store-api.hh"
|
||||
#include "goal.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
class Worker;
|
||||
|
||||
class SubstitutionGoal : public Goal
|
||||
{
|
||||
friend class Worker;
|
||||
|
||||
private:
|
||||
/* The store path that should be realised through a substitute. */
|
||||
StorePath storePath;
|
||||
|
||||
/* The path the substituter refers to the path as. This will be
|
||||
* different when the stores have different names. */
|
||||
std::optional<StorePath> subPath;
|
||||
|
||||
/* The remaining substituters. */
|
||||
std::list<ref<Store>> subs;
|
||||
|
||||
/* The current substituter. */
|
||||
std::shared_ptr<Store> sub;
|
||||
|
||||
/* Whether a substituter failed. */
|
||||
bool substituterFailed = false;
|
||||
|
||||
/* Path info returned by the substituter's query info operation. */
|
||||
std::shared_ptr<const ValidPathInfo> info;
|
||||
|
||||
/* Pipe for the substituter's standard output. */
|
||||
Pipe outPipe;
|
||||
|
||||
/* The substituter thread. */
|
||||
std::thread thr;
|
||||
|
||||
std::promise<void> promise;
|
||||
|
||||
/* Whether to try to repair a valid path. */
|
||||
RepairFlag repair;
|
||||
|
||||
/* Location where we're downloading the substitute. Differs from
|
||||
storePath when doing a repair. */
|
||||
Path destPath;
|
||||
|
||||
std::unique_ptr<MaintainCount<uint64_t>> maintainExpectedSubstitutions,
|
||||
maintainRunningSubstitutions, maintainExpectedNar, maintainExpectedDownload;
|
||||
|
||||
typedef void (SubstitutionGoal::*GoalState)();
|
||||
GoalState state;
|
||||
|
||||
/* Content address for recomputing store path */
|
||||
std::optional<ContentAddress> ca;
|
||||
|
||||
public:
|
||||
SubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
||||
~SubstitutionGoal();
|
||||
|
||||
void timedOut(Error && ex) override { abort(); };
|
||||
|
||||
string key() override
|
||||
{
|
||||
/* "a$" ensures substitution goals happen before derivation
|
||||
goals. */
|
||||
return "a$" + std::string(storePath.name()) + "$" + worker.store.printStorePath(storePath);
|
||||
}
|
||||
|
||||
void work() override;
|
||||
|
||||
/* The states. */
|
||||
void init();
|
||||
void tryNext();
|
||||
void gotInfo();
|
||||
void referencesValid();
|
||||
void tryToRun();
|
||||
void finished();
|
||||
|
||||
/* Callback used by the worker to write to the log. */
|
||||
void handleChildOutput(int fd, const string & data) override;
|
||||
void handleEOF(int fd) override;
|
||||
|
||||
StorePath getStorePath() { return storePath; }
|
||||
};
|
||||
|
||||
}
|
||||
455
src/libstore/build/worker.cc
Normal file
455
src/libstore/build/worker.cc
Normal file
|
|
@ -0,0 +1,455 @@
|
|||
#include "machines.hh"
|
||||
#include "worker.hh"
|
||||
#include "substitution-goal.hh"
|
||||
#include "derivation-goal.hh"
|
||||
#include "hook-instance.hh"
|
||||
|
||||
#include <poll.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
Worker::Worker(LocalStore & store)
|
||||
: act(*logger, actRealise)
|
||||
, actDerivations(*logger, actBuilds)
|
||||
, actSubstitutions(*logger, actCopyPaths)
|
||||
, store(store)
|
||||
{
|
||||
/* Debugging: prevent recursive workers. */
|
||||
nrLocalBuilds = 0;
|
||||
lastWokenUp = steady_time_point::min();
|
||||
permanentFailure = false;
|
||||
timedOut = false;
|
||||
hashMismatch = false;
|
||||
checkMismatch = false;
|
||||
}
|
||||
|
||||
|
||||
Worker::~Worker()
|
||||
{
|
||||
/* Explicitly get rid of all strong pointers now. After this all
|
||||
goals that refer to this worker should be gone. (Otherwise we
|
||||
are in trouble, since goals may call childTerminated() etc. in
|
||||
their destructors). */
|
||||
topGoals.clear();
|
||||
|
||||
assert(expectedSubstitutions == 0);
|
||||
assert(expectedDownloadSize == 0);
|
||||
assert(expectedNarSize == 0);
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon(
|
||||
const StorePath & drvPath,
|
||||
const StringSet & wantedOutputs,
|
||||
std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal)
|
||||
{
|
||||
WeakGoalPtr & abstract_goal_weak = derivationGoals[drvPath];
|
||||
GoalPtr abstract_goal = abstract_goal_weak.lock(); // FIXME
|
||||
std::shared_ptr<DerivationGoal> goal;
|
||||
if (!abstract_goal) {
|
||||
goal = mkDrvGoal();
|
||||
abstract_goal_weak = goal;
|
||||
wakeUp(goal);
|
||||
} else {
|
||||
goal = std::dynamic_pointer_cast<DerivationGoal>(abstract_goal);
|
||||
assert(goal);
|
||||
goal->addWantedOutputs(wantedOutputs);
|
||||
}
|
||||
return goal;
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(const StorePath & drvPath,
|
||||
const StringSet & wantedOutputs, BuildMode buildMode)
|
||||
{
|
||||
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() {
|
||||
return std::make_shared<DerivationGoal>(drvPath, wantedOutputs, *this, buildMode);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<DerivationGoal> Worker::makeBasicDerivationGoal(const StorePath & drvPath,
|
||||
const BasicDerivation & drv, const StringSet & wantedOutputs, BuildMode buildMode)
|
||||
{
|
||||
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() {
|
||||
return std::make_shared<DerivationGoal>(drvPath, drv, wantedOutputs, *this, buildMode);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
GoalPtr Worker::makeSubstitutionGoal(const StorePath & path, RepairFlag repair, std::optional<ContentAddress> ca)
|
||||
{
|
||||
WeakGoalPtr & goal_weak = substitutionGoals[path];
|
||||
GoalPtr goal = goal_weak.lock(); // FIXME
|
||||
if (!goal) {
|
||||
goal = std::make_shared<SubstitutionGoal>(path, *this, repair, ca);
|
||||
goal_weak = goal;
|
||||
wakeUp(goal);
|
||||
}
|
||||
return goal;
|
||||
}
|
||||
|
||||
|
||||
static void removeGoal(GoalPtr goal, WeakGoalMap & goalMap)
|
||||
{
|
||||
/* !!! inefficient */
|
||||
for (WeakGoalMap::iterator i = goalMap.begin();
|
||||
i != goalMap.end(); )
|
||||
if (i->second.lock() == goal) {
|
||||
WeakGoalMap::iterator j = i; ++j;
|
||||
goalMap.erase(i);
|
||||
i = j;
|
||||
}
|
||||
else ++i;
|
||||
}
|
||||
|
||||
|
||||
void Worker::removeGoal(GoalPtr goal)
|
||||
{
|
||||
nix::removeGoal(goal, derivationGoals);
|
||||
nix::removeGoal(goal, substitutionGoals);
|
||||
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();
|
||||
}
|
||||
|
||||
/* Wake up goals waiting for any goal to finish. */
|
||||
for (auto & i : waitingForAnyGoal) {
|
||||
GoalPtr goal = i.lock();
|
||||
if (goal) wakeUp(goal);
|
||||
}
|
||||
|
||||
waitingForAnyGoal.clear();
|
||||
}
|
||||
|
||||
|
||||
void Worker::wakeUp(GoalPtr goal)
|
||||
{
|
||||
goal->trace("woken up");
|
||||
addToWeakGoals(awake, goal);
|
||||
}
|
||||
|
||||
|
||||
unsigned Worker::getNrLocalBuilds()
|
||||
{
|
||||
return nrLocalBuilds;
|
||||
}
|
||||
|
||||
|
||||
void Worker::childStarted(GoalPtr goal, const set<int> & fds,
|
||||
bool inBuildSlot, bool respectTimeouts)
|
||||
{
|
||||
Child child;
|
||||
child.goal = goal;
|
||||
child.goal2 = goal.get();
|
||||
child.fds = fds;
|
||||
child.timeStarted = child.lastOutput = steady_time_point::clock::now();
|
||||
child.inBuildSlot = inBuildSlot;
|
||||
child.respectTimeouts = respectTimeouts;
|
||||
children.emplace_back(child);
|
||||
if (inBuildSlot) nrLocalBuilds++;
|
||||
}
|
||||
|
||||
|
||||
void Worker::childTerminated(Goal * goal, bool wakeSleepers)
|
||||
{
|
||||
auto i = std::find_if(children.begin(), children.end(),
|
||||
[&](const Child & child) { return child.goal2 == goal; });
|
||||
if (i == children.end()) return;
|
||||
|
||||
if (i->inBuildSlot) {
|
||||
assert(nrLocalBuilds > 0);
|
||||
nrLocalBuilds--;
|
||||
}
|
||||
|
||||
children.erase(i);
|
||||
|
||||
if (wakeSleepers) {
|
||||
|
||||
/* 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)
|
||||
{
|
||||
debug("wait for build slot");
|
||||
if (getNrLocalBuilds() < settings.maxBuildJobs)
|
||||
wakeUp(goal); /* we can do it right away */
|
||||
else
|
||||
addToWeakGoals(wantingToBuild, goal);
|
||||
}
|
||||
|
||||
|
||||
void Worker::waitForAnyGoal(GoalPtr goal)
|
||||
{
|
||||
debug("wait for any goal");
|
||||
addToWeakGoals(waitingForAnyGoal, goal);
|
||||
}
|
||||
|
||||
|
||||
void Worker::waitForAWhile(GoalPtr goal)
|
||||
{
|
||||
debug("wait for a while");
|
||||
addToWeakGoals(waitingForAWhile, goal);
|
||||
}
|
||||
|
||||
|
||||
void Worker::run(const Goals & _topGoals)
|
||||
{
|
||||
for (auto & i : _topGoals) topGoals.insert(i);
|
||||
|
||||
debug("entered goal loop");
|
||||
|
||||
while (1) {
|
||||
|
||||
checkInterrupt();
|
||||
|
||||
store.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();
|
||||
goal->work();
|
||||
if (topGoals.empty()) break; // stuff may have been cancelled
|
||||
}
|
||||
}
|
||||
|
||||
if (topGoals.empty()) break;
|
||||
|
||||
/* Wait for input. */
|
||||
if (!children.empty() || !waitingForAWhile.empty())
|
||||
waitForInput();
|
||||
else {
|
||||
if (awake.empty() && 0 == settings.maxBuildJobs)
|
||||
{
|
||||
if (getMachines().empty())
|
||||
throw Error("unable to start any build; either increase '--max-jobs' "
|
||||
"or enable remote builds."
|
||||
"\nhttps://nixos.org/nix/manual/#chap-distributed-builds");
|
||||
else
|
||||
throw Error("unable to start any build; remote machines may not have "
|
||||
"all required system features."
|
||||
"\nhttps://nixos.org/nix/manual/#chap-distributed-builds");
|
||||
|
||||
}
|
||||
assert(!awake.empty());
|
||||
}
|
||||
}
|
||||
|
||||
/* If --keep-going is not set, it's possible that the main goal
|
||||
exited while some of its subgoals were still active. But if
|
||||
--keep-going *is* set, then they must all be finished now. */
|
||||
assert(!settings.keepGoing || awake.empty());
|
||||
assert(!settings.keepGoing || wantingToBuild.empty());
|
||||
assert(!settings.keepGoing || children.empty());
|
||||
}
|
||||
|
||||
void Worker::waitForInput()
|
||||
{
|
||||
printMsg(lvlVomit, "waiting for children");
|
||||
|
||||
/* Process output from the file descriptors attached to the
|
||||
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 (!i.respectTimeouts) continue;
|
||||
if (0 != settings.maxSilentTime)
|
||||
nearest = std::min(nearest, i.lastOutput + std::chrono::seconds(settings.maxSilentTime));
|
||||
if (0 != settings.buildTimeout)
|
||||
nearest = std::min(nearest, i.timeStarted + std::chrono::seconds(settings.buildTimeout));
|
||||
}
|
||||
if (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, int> 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);
|
||||
|
||||
set<int> fds2(j->fds);
|
||||
std::vector<unsigned char> buffer(4096);
|
||||
for (auto & k : fds2) {
|
||||
if (pollStatus.at(fdToPollStatus.at(k)).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);
|
||||
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);
|
||||
string data((char *) buffer.data(), rd);
|
||||
j->lastOutput = after;
|
||||
goal->handleChildOutput(k, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (goal->exitCode == Goal::ecBusy &&
|
||||
0 != settings.maxSilentTime &&
|
||||
j->respectTimeouts &&
|
||||
after - j->lastOutput >= std::chrono::seconds(settings.maxSilentTime))
|
||||
{
|
||||
goal->timedOut(Error(
|
||||
"%1% timed out after %2% seconds of silence",
|
||||
goal->getName(), settings.maxSilentTime));
|
||||
}
|
||||
|
||||
else if (goal->exitCode == Goal::ecBusy &&
|
||||
0 != settings.buildTimeout &&
|
||||
j->respectTimeouts &&
|
||||
after - j->timeStarted >= std::chrono::seconds(settings.buildTimeout))
|
||||
{
|
||||
goal->timedOut(Error(
|
||||
"%1% timed out after %2% seconds",
|
||||
goal->getName(), settings.buildTimeout));
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
unsigned int Worker::exitStatus()
|
||||
{
|
||||
/*
|
||||
* 1100100
|
||||
* ^^^^
|
||||
* |||`- timeout
|
||||
* ||`-- output hash mismatch
|
||||
* |`--- build failure
|
||||
* `---- not deterministic
|
||||
*/
|
||||
unsigned int mask = 0;
|
||||
bool buildFailure = permanentFailure || timedOut || hashMismatch;
|
||||
if (buildFailure)
|
||||
mask |= 0x04; // 100
|
||||
if (timedOut)
|
||||
mask |= 0x01; // 101
|
||||
if (hashMismatch)
|
||||
mask |= 0x02; // 102
|
||||
if (checkMismatch) {
|
||||
mask |= 0x08; // 104
|
||||
}
|
||||
|
||||
if (mask)
|
||||
mask |= 0x60;
|
||||
return mask ? mask : 1;
|
||||
}
|
||||
|
||||
|
||||
bool Worker::pathContentsGood(const StorePath & path)
|
||||
{
|
||||
auto i = pathContentsGoodCache.find(path);
|
||||
if (i != pathContentsGoodCache.end()) return i->second;
|
||||
printInfo("checking path '%s'...", store.printStorePath(path));
|
||||
auto info = store.queryPathInfo(path);
|
||||
bool res;
|
||||
if (!pathExists(store.printStorePath(path)))
|
||||
res = false;
|
||||
else {
|
||||
HashResult current = hashPath(info->narHash.type, store.printStorePath(path));
|
||||
Hash nullHash(htSHA256);
|
||||
res = info->narHash == nullHash || info->narHash == current.first;
|
||||
}
|
||||
pathContentsGoodCache.insert_or_assign(path, res);
|
||||
if (!res)
|
||||
logError({
|
||||
.name = "Corrupted path",
|
||||
.hint = hintfmt("path '%s' is corrupted or missing!", store.printStorePath(path))
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
void Worker::markContentsGood(const StorePath & path)
|
||||
{
|
||||
pathContentsGoodCache.insert_or_assign(path, true);
|
||||
}
|
||||
|
||||
}
|
||||
195
src/libstore/build/worker.hh
Normal file
195
src/libstore/build/worker.hh
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.hh"
|
||||
#include "lock.hh"
|
||||
#include "local-store.hh"
|
||||
#include "goal.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
/* Forward definition. */
|
||||
class DerivationGoal;
|
||||
|
||||
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
|
||||
set<int> fds;
|
||||
bool respectTimeouts;
|
||||
bool inBuildSlot;
|
||||
steady_time_point lastOutput; /* time we last got output on stdout/stderr */
|
||||
steady_time_point timeStarted;
|
||||
};
|
||||
|
||||
/* Forward definition. */
|
||||
struct HookInstance;
|
||||
|
||||
/* The worker class. */
|
||||
class Worker
|
||||
{
|
||||
private:
|
||||
|
||||
/* Note: the worker should only have strong pointers to the
|
||||
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 and
|
||||
substitutions but not remote builds via the build hook. */
|
||||
unsigned int nrLocalBuilds;
|
||||
|
||||
/* Maps used to prevent multiple instantiations of a goal for the
|
||||
same derivation / path. */
|
||||
WeakGoalMap derivationGoals;
|
||||
WeakGoalMap substitutionGoals;
|
||||
|
||||
/* Goals waiting for busy paths to be unlocked. */
|
||||
WeakGoals waitingForAnyGoal;
|
||||
|
||||
/* 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(). */
|
||||
std::map<StorePath, bool> pathContentsGoodCache;
|
||||
|
||||
public:
|
||||
|
||||
const Activity act;
|
||||
const Activity actDerivations;
|
||||
const Activity actSubstitutions;
|
||||
|
||||
/* Set if at least one derivation had a BuildError (i.e. permanent
|
||||
failure). */
|
||||
bool permanentFailure;
|
||||
|
||||
/* Set if at least one derivation had a timeout. */
|
||||
bool timedOut;
|
||||
|
||||
/* Set if at least one derivation fails with a hash mismatch. */
|
||||
bool hashMismatch;
|
||||
|
||||
/* Set if at least one derivation is not deterministic in check mode. */
|
||||
bool checkMismatch;
|
||||
|
||||
LocalStore & store;
|
||||
|
||||
std::unique_ptr<HookInstance> hook;
|
||||
|
||||
uint64_t expectedBuilds = 0;
|
||||
uint64_t doneBuilds = 0;
|
||||
uint64_t failedBuilds = 0;
|
||||
uint64_t runningBuilds = 0;
|
||||
|
||||
uint64_t expectedSubstitutions = 0;
|
||||
uint64_t doneSubstitutions = 0;
|
||||
uint64_t failedSubstitutions = 0;
|
||||
uint64_t runningSubstitutions = 0;
|
||||
uint64_t expectedDownloadSize = 0;
|
||||
uint64_t doneDownloadSize = 0;
|
||||
uint64_t expectedNarSize = 0;
|
||||
uint64_t doneNarSize = 0;
|
||||
|
||||
/* Whether to ask the build hook if it can build a derivation. If
|
||||
it answers with "decline-permanently", we don't try again. */
|
||||
bool tryBuildHook = true;
|
||||
|
||||
Worker(LocalStore & store);
|
||||
~Worker();
|
||||
|
||||
/* Make a goal (with caching). */
|
||||
|
||||
/* derivation goal */
|
||||
private:
|
||||
std::shared_ptr<DerivationGoal> makeDerivationGoalCommon(
|
||||
const StorePath & drvPath, const StringSet & wantedOutputs,
|
||||
std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal);
|
||||
public:
|
||||
std::shared_ptr<DerivationGoal> makeDerivationGoal(
|
||||
const StorePath & drvPath,
|
||||
const StringSet & wantedOutputs, BuildMode buildMode = bmNormal);
|
||||
std::shared_ptr<DerivationGoal> makeBasicDerivationGoal(
|
||||
const StorePath & drvPath, const BasicDerivation & drv,
|
||||
const StringSet & wantedOutputs, BuildMode buildMode = bmNormal);
|
||||
|
||||
/* substitution goal */
|
||||
GoalPtr makeSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
||||
|
||||
/* Remove a dead goal. */
|
||||
void removeGoal(GoalPtr goal);
|
||||
|
||||
/* Wake up a goal (i.e., there is something for it to do). */
|
||||
void wakeUp(GoalPtr goal);
|
||||
|
||||
/* Return the number of local build and substitution processes
|
||||
currently running (but not remote builds via the build
|
||||
hook). */
|
||||
unsigned int getNrLocalBuilds();
|
||||
|
||||
/* Registers a running child process. `inBuildSlot' means that
|
||||
the process counts towards the jobs limit. */
|
||||
void childStarted(GoalPtr goal, const set<int> & fds,
|
||||
bool inBuildSlot, bool respectTimeouts);
|
||||
|
||||
/* Unregisters a running child process. `wakeSleepers' should be
|
||||
false if there is no sense in waking up goals that are sleeping
|
||||
because they can't run yet (e.g., there is no free build slot,
|
||||
or the hook would still say `postpone'). */
|
||||
void childTerminated(Goal * goal, bool wakeSleepers = true);
|
||||
|
||||
/* Put `goal' to sleep until a build slot becomes available (which
|
||||
might be right away). */
|
||||
void waitForBuildSlot(GoalPtr goal);
|
||||
|
||||
/* Wait for any goal to finish. Pretty indiscriminate way to
|
||||
wait for some resource that some other goal is holding. */
|
||||
void waitForAnyGoal(GoalPtr goal);
|
||||
|
||||
/* Wait for a few seconds and then retry this goal. Used when
|
||||
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);
|
||||
|
||||
/* Loop until the specified top-level goals have finished. */
|
||||
void run(const Goals & topGoals);
|
||||
|
||||
/* Wait for input to become available. */
|
||||
void waitForInput();
|
||||
|
||||
unsigned int exitStatus();
|
||||
|
||||
/* Check whether the given valid path exists and has the right
|
||||
contents. */
|
||||
bool pathContentsGood(const StorePath & path);
|
||||
|
||||
void markContentsGood(const StorePath & path);
|
||||
|
||||
void updateProgress()
|
||||
{
|
||||
actDerivations.progress(doneBuilds, expectedBuilds + doneBuilds, runningBuilds, failedBuilds);
|
||||
actSubstitutions.progress(doneSubstitutions, expectedSubstitutions + doneSubstitutions, runningSubstitutions, failedSubstitutions);
|
||||
act.setExpected(actFileTransfer, expectedDownloadSize + doneDownloadSize);
|
||||
act.setExpected(actCopyPath, expectedNarSize + doneNarSize);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -101,17 +101,20 @@ struct TunnelLogger : public Logger
|
|||
|
||||
/* stopWork() means that we're done; stop sending stderr to the
|
||||
client. */
|
||||
void stopWork(bool success = true, const string & msg = "", unsigned int status = 0)
|
||||
void stopWork(const Error * ex = nullptr)
|
||||
{
|
||||
auto state(state_.lock());
|
||||
|
||||
state->canSendStderr = false;
|
||||
|
||||
if (success)
|
||||
if (!ex)
|
||||
to << STDERR_LAST;
|
||||
else {
|
||||
to << STDERR_ERROR << msg;
|
||||
if (status != 0) to << status;
|
||||
if (GET_PROTOCOL_MINOR(clientVersion) >= 26) {
|
||||
to << STDERR_ERROR << *ex;
|
||||
} else {
|
||||
to << STDERR_ERROR << ex->what() << ex->status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -247,7 +250,7 @@ static void writeValidPathInfo(
|
|||
{
|
||||
to << (info->deriver ? store->printStorePath(*info->deriver) : "")
|
||||
<< info->narHash.to_string(Base16, false);
|
||||
writeStorePaths(*store, to, info->references);
|
||||
worker_proto::write(*store, to, info->references);
|
||||
to << info->registrationTime << info->narSize;
|
||||
if (GET_PROTOCOL_MINOR(clientVersion) >= 16) {
|
||||
to << info->ultimate
|
||||
|
|
@ -272,11 +275,11 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
}
|
||||
|
||||
case wopQueryValidPaths: {
|
||||
auto paths = readStorePaths<StorePathSet>(*store, from);
|
||||
auto paths = worker_proto::read(*store, from, Phantom<StorePathSet> {});
|
||||
logger->startWork();
|
||||
auto res = store->queryValidPaths(paths);
|
||||
logger->stopWork();
|
||||
writeStorePaths(*store, to, res);
|
||||
worker_proto::write(*store, to, res);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -292,11 +295,11 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
}
|
||||
|
||||
case wopQuerySubstitutablePaths: {
|
||||
auto paths = readStorePaths<StorePathSet>(*store, from);
|
||||
auto paths = worker_proto::read(*store, from, Phantom<StorePathSet> {});
|
||||
logger->startWork();
|
||||
auto res = store->querySubstitutablePaths(paths);
|
||||
logger->stopWork();
|
||||
writeStorePaths(*store, to, res);
|
||||
worker_proto::write(*store, to, res);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -325,7 +328,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
paths = store->queryValidDerivers(path);
|
||||
else paths = store->queryDerivationOutputs(path);
|
||||
logger->stopWork();
|
||||
writeStorePaths(*store, to, paths);
|
||||
worker_proto::write(*store, to, paths);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -369,7 +372,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
if (GET_PROTOCOL_MINOR(clientVersion) >= 25) {
|
||||
auto name = readString(from);
|
||||
auto camStr = readString(from);
|
||||
auto refs = readStorePaths<StorePathSet>(*store, from);
|
||||
auto refs = worker_proto::read(*store, from, Phantom<StorePathSet> {});
|
||||
bool repairBool;
|
||||
from >> repairBool;
|
||||
auto repair = RepairFlag{repairBool};
|
||||
|
|
@ -449,7 +452,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
case wopAddTextToStore: {
|
||||
string suffix = readString(from);
|
||||
string s = readString(from);
|
||||
auto refs = readStorePaths<StorePathSet>(*store, from);
|
||||
auto refs = worker_proto::read(*store, from, Phantom<StorePathSet> {});
|
||||
logger->startWork();
|
||||
auto path = store->addTextToStore(suffix, s, refs, NoRepair);
|
||||
logger->stopWork();
|
||||
|
|
@ -546,6 +549,20 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
are in fact content-addressed if we don't trust them. */
|
||||
assert(derivationIsCA(drv.type()) || trusted);
|
||||
|
||||
/* Recompute the derivation path when we cannot trust the original. */
|
||||
if (!trusted) {
|
||||
/* Recomputing the derivation path for input-address derivations
|
||||
makes it harder to audit them after the fact, since we need the
|
||||
original not-necessarily-resolved derivation to verify the drv
|
||||
derivation as adequate claim to the input-addressed output
|
||||
paths. */
|
||||
assert(derivationIsCA(drv.type()));
|
||||
|
||||
Derivation drv2;
|
||||
static_cast<BasicDerivation &>(drv2) = drv;
|
||||
drvPath = writeDerivation(*store, Derivation { drv2 });
|
||||
}
|
||||
|
||||
auto res = store->buildDerivation(drvPath, drv, buildMode);
|
||||
logger->stopWork();
|
||||
to << res.status << res.errorMsg;
|
||||
|
|
@ -608,7 +625,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
case wopCollectGarbage: {
|
||||
GCOptions options;
|
||||
options.action = (GCOptions::GCAction) readInt(from);
|
||||
options.pathsToDelete = readStorePaths<StorePathSet>(*store, from);
|
||||
options.pathsToDelete = worker_proto::read(*store, from, Phantom<StorePathSet> {});
|
||||
from >> options.ignoreLiveness >> options.maxFreed;
|
||||
// obsolete fields
|
||||
readInt(from);
|
||||
|
|
@ -677,7 +694,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
else {
|
||||
to << 1
|
||||
<< (i->second.deriver ? store->printStorePath(*i->second.deriver) : "");
|
||||
writeStorePaths(*store, to, i->second.references);
|
||||
worker_proto::write(*store, to, i->second.references);
|
||||
to << i->second.downloadSize
|
||||
<< i->second.narSize;
|
||||
}
|
||||
|
|
@ -688,11 +705,11 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
SubstitutablePathInfos infos;
|
||||
StorePathCAMap pathsMap = {};
|
||||
if (GET_PROTOCOL_MINOR(clientVersion) < 22) {
|
||||
auto paths = readStorePaths<StorePathSet>(*store, from);
|
||||
auto paths = worker_proto::read(*store, from, Phantom<StorePathSet> {});
|
||||
for (auto & path : paths)
|
||||
pathsMap.emplace(path, std::nullopt);
|
||||
} else
|
||||
pathsMap = readStorePathCAMap(*store, from);
|
||||
pathsMap = worker_proto::read(*store, from, Phantom<StorePathCAMap> {});
|
||||
logger->startWork();
|
||||
store->querySubstitutablePathInfos(pathsMap, infos);
|
||||
logger->stopWork();
|
||||
|
|
@ -700,7 +717,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
for (auto & i : infos) {
|
||||
to << store->printStorePath(i.first)
|
||||
<< (i.second.deriver ? store->printStorePath(*i.second.deriver) : "");
|
||||
writeStorePaths(*store, to, i.second.references);
|
||||
worker_proto::write(*store, to, i.second.references);
|
||||
to << i.second.downloadSize << i.second.narSize;
|
||||
}
|
||||
break;
|
||||
|
|
@ -710,7 +727,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
logger->startWork();
|
||||
auto paths = store->queryAllValidPaths();
|
||||
logger->stopWork();
|
||||
writeStorePaths(*store, to, paths);
|
||||
worker_proto::write(*store, to, paths);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -782,7 +799,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
ValidPathInfo info { path, narHash };
|
||||
if (deriver != "")
|
||||
info.deriver = store->parseStorePath(deriver);
|
||||
info.references = readStorePaths<StorePathSet>(*store, from);
|
||||
info.references = worker_proto::read(*store, from, Phantom<StorePathSet> {});
|
||||
from >> info.registrationTime >> info.narSize >> info.ultimate;
|
||||
info.sigs = readStrings<StringSet>(from);
|
||||
info.ca = parseContentAddressOpt(readString(from));
|
||||
|
|
@ -835,9 +852,9 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
uint64_t downloadSize, narSize;
|
||||
store->queryMissing(targets, willBuild, willSubstitute, unknown, downloadSize, narSize);
|
||||
logger->stopWork();
|
||||
writeStorePaths(*store, to, willBuild);
|
||||
writeStorePaths(*store, to, willSubstitute);
|
||||
writeStorePaths(*store, to, unknown);
|
||||
worker_proto::write(*store, to, willBuild);
|
||||
worker_proto::write(*store, to, willSubstitute);
|
||||
worker_proto::write(*store, to, unknown);
|
||||
to << downloadSize << narSize;
|
||||
break;
|
||||
}
|
||||
|
|
@ -921,10 +938,11 @@ void processConnection(
|
|||
during addTextToStore() / importPath(). If that
|
||||
happens, just send the error message and exit. */
|
||||
bool errorAllowed = tunnelLogger->state_.lock()->canSendStderr;
|
||||
tunnelLogger->stopWork(false, e.msg(), e.status);
|
||||
tunnelLogger->stopWork(&e);
|
||||
if (!errorAllowed) throw;
|
||||
} catch (std::bad_alloc & e) {
|
||||
tunnelLogger->stopWork(false, "Nix daemon out of memory", 1);
|
||||
auto ex = Error("Nix daemon out of memory");
|
||||
tunnelLogger->stopWork(&ex);
|
||||
throw;
|
||||
}
|
||||
|
||||
|
|
@ -933,8 +951,13 @@ void processConnection(
|
|||
assert(!tunnelLogger->state_.lock()->canSendStderr);
|
||||
};
|
||||
|
||||
} catch (Error & e) {
|
||||
tunnelLogger->stopWork(&e);
|
||||
to.flush();
|
||||
return;
|
||||
} catch (std::exception & e) {
|
||||
tunnelLogger->stopWork(false, e.what(), 1);
|
||||
auto ex = Error(e.what());
|
||||
tunnelLogger->stopWork(&ex);
|
||||
to.flush();
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include "serialise.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ bool BasicDerivation::isBuiltin() const
|
|||
|
||||
|
||||
StorePath writeDerivation(Store & store,
|
||||
const Derivation & drv, RepairFlag repair)
|
||||
const Derivation & drv, RepairFlag repair, bool readOnly)
|
||||
{
|
||||
auto references = drv.inputSrcs;
|
||||
for (auto & i : drv.inputDrvs)
|
||||
|
|
@ -79,7 +79,7 @@ StorePath writeDerivation(Store & store,
|
|||
held during a garbage collection). */
|
||||
auto suffix = std::string(drv.name) + drvExtension;
|
||||
auto contents = drv.unparse(store, false);
|
||||
return settings.readOnlyMode
|
||||
return readOnly || settings.readOnlyMode
|
||||
? store.computeStorePathForText(suffix, contents, references)
|
||||
: store.addTextToStore(suffix, contents, references, repair);
|
||||
}
|
||||
|
|
@ -584,7 +584,7 @@ Source & readDerivation(Source & in, const Store & store, BasicDerivation & drv,
|
|||
drv.outputs.emplace(std::move(name), std::move(output));
|
||||
}
|
||||
|
||||
drv.inputSrcs = readStorePaths<StorePathSet>(store, in);
|
||||
drv.inputSrcs = worker_proto::read(store, in, Phantom<StorePathSet> {});
|
||||
in >> drv.platform >> drv.builder;
|
||||
drv.args = readStrings<Strings>(in);
|
||||
|
||||
|
|
@ -622,7 +622,7 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr
|
|||
},
|
||||
}, i.second.output);
|
||||
}
|
||||
writeStorePaths(store, out, drv.inputSrcs);
|
||||
worker_proto::write(store, out, drv.inputSrcs);
|
||||
out << drv.platform << drv.builder << drv.args;
|
||||
out << drv.env.size();
|
||||
for (auto & i : drv.env)
|
||||
|
|
@ -644,4 +644,57 @@ std::string downstreamPlaceholder(const Store & store, const StorePath & drvPath
|
|||
return "/" + hashString(htSHA256, clearText).to_string(Base32, false);
|
||||
}
|
||||
|
||||
|
||||
// N.B. Outputs are left unchanged
|
||||
static void rewriteDerivation(Store & store, BasicDerivation & drv, const StringMap & rewrites) {
|
||||
|
||||
debug("Rewriting the derivation");
|
||||
|
||||
for (auto &rewrite: rewrites) {
|
||||
debug("rewriting %s as %s", rewrite.first, rewrite.second);
|
||||
}
|
||||
|
||||
drv.builder = rewriteStrings(drv.builder, rewrites);
|
||||
for (auto & arg: drv.args) {
|
||||
arg = rewriteStrings(arg, rewrites);
|
||||
}
|
||||
|
||||
StringPairs newEnv;
|
||||
for (auto & envVar: drv.env) {
|
||||
auto envName = rewriteStrings(envVar.first, rewrites);
|
||||
auto envValue = rewriteStrings(envVar.second, rewrites);
|
||||
newEnv.emplace(envName, envValue);
|
||||
}
|
||||
drv.env = newEnv;
|
||||
}
|
||||
|
||||
|
||||
Sync<DrvPathResolutions> drvPathResolutions;
|
||||
|
||||
std::optional<BasicDerivation> Derivation::tryResolve(Store & store) {
|
||||
BasicDerivation resolved { *this };
|
||||
|
||||
// Input paths that we'll want to rewrite in the derivation
|
||||
StringMap inputRewrites;
|
||||
|
||||
for (auto & input : inputDrvs) {
|
||||
auto inputDrvOutputs = store.queryPartialDerivationOutputMap(input.first);
|
||||
StringSet newOutputNames;
|
||||
for (auto & outputName : input.second) {
|
||||
auto actualPathOpt = inputDrvOutputs.at(outputName);
|
||||
if (!actualPathOpt)
|
||||
return std::nullopt;
|
||||
auto actualPath = *actualPathOpt;
|
||||
inputRewrites.emplace(
|
||||
downstreamPlaceholder(store, input.first, outputName),
|
||||
store.printStorePath(actualPath));
|
||||
resolved.inputSrcs.insert(std::move(actualPath));
|
||||
}
|
||||
}
|
||||
|
||||
rewriteDerivation(store, resolved, inputRewrites);
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "types.hh"
|
||||
#include "hash.hh"
|
||||
#include "content-address.hh"
|
||||
#include "sync.hh"
|
||||
|
||||
#include <map>
|
||||
#include <variant>
|
||||
|
|
@ -60,8 +61,6 @@ typedef std::map<string, DerivationOutput> DerivationOutputs;
|
|||
also contains, for each output, the (optional) store path in which it would
|
||||
be written. To calculate values of these types, see the corresponding
|
||||
functions in BasicDerivation */
|
||||
typedef std::map<string, std::pair<DerivationOutput, StorePath>>
|
||||
DerivationOutputsAndPaths;
|
||||
typedef std::map<string, std::pair<DerivationOutput, std::optional<StorePath>>>
|
||||
DerivationOutputsAndOptPaths;
|
||||
|
||||
|
|
@ -100,7 +99,7 @@ struct BasicDerivation
|
|||
StringPairs env;
|
||||
std::string name;
|
||||
|
||||
BasicDerivation() { }
|
||||
BasicDerivation() = default;
|
||||
virtual ~BasicDerivation() { };
|
||||
|
||||
bool isBuiltin() const;
|
||||
|
|
@ -127,7 +126,17 @@ struct Derivation : BasicDerivation
|
|||
std::string unparse(const Store & store, bool maskOutputs,
|
||||
std::map<std::string, StringSet> * actualInputs = nullptr) const;
|
||||
|
||||
Derivation() { }
|
||||
/* Return the underlying basic derivation but with these changes:
|
||||
|
||||
1. Input drvs are emptied, but the outputs of them that were used are
|
||||
added directly to input sources.
|
||||
|
||||
2. Input placeholders are replaced with realized input store paths. */
|
||||
std::optional<BasicDerivation> tryResolve(Store & store);
|
||||
|
||||
Derivation() = default;
|
||||
Derivation(const BasicDerivation & bd) : BasicDerivation(bd) { }
|
||||
Derivation(BasicDerivation && bd) : BasicDerivation(std::move(bd)) { }
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -137,7 +146,9 @@ enum RepairFlag : bool { NoRepair = false, Repair = true };
|
|||
|
||||
/* Write a derivation to the Nix store, and return its path. */
|
||||
StorePath writeDerivation(Store & store,
|
||||
const Derivation & drv, RepairFlag repair = NoRepair);
|
||||
const Derivation & drv,
|
||||
RepairFlag repair = NoRepair,
|
||||
bool readOnly = false);
|
||||
|
||||
/* Read a derivation from a file. */
|
||||
Derivation parseDerivation(const Store & store, std::string && s, std::string_view name);
|
||||
|
|
@ -191,6 +202,16 @@ typedef std::map<StorePath, DrvHashModulo> DrvHashes;
|
|||
|
||||
extern DrvHashes drvHashes; // FIXME: global, not thread-safe
|
||||
|
||||
/* Memoisation of `readDerivation(..).resove()`. */
|
||||
typedef std::map<
|
||||
StorePath,
|
||||
std::optional<StorePath>
|
||||
> DrvPathResolutions;
|
||||
|
||||
// FIXME: global, though at least thread-safe.
|
||||
// FIXME: arguably overlaps with hashDerivationModulo memo table.
|
||||
extern Sync<DrvPathResolutions> drvPathResolutions;
|
||||
|
||||
bool wantOutput(const string & output, const std::set<string> & wanted);
|
||||
|
||||
struct Source;
|
||||
|
|
|
|||
|
|
@ -18,8 +18,7 @@ struct DummyStore : public Store, public virtual DummyStoreConfig
|
|||
DummyStore(const Params & params)
|
||||
: StoreConfig(params)
|
||||
, Store(params)
|
||||
{
|
||||
}
|
||||
{ }
|
||||
|
||||
string getUri() override
|
||||
{
|
||||
|
|
@ -63,6 +62,6 @@ struct DummyStore : public Store, public virtual DummyStoreConfig
|
|||
{ unsupported("buildDerivation"); }
|
||||
};
|
||||
|
||||
static RegisterStoreImplementation<DummyStore, DummyStoreConfig> regStore;
|
||||
static RegisterStoreImplementation<DummyStore, DummyStoreConfig> regDummyStore;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ void Store::exportPath(const StorePath & path, Sink & sink)
|
|||
teeSink
|
||||
<< exportMagic
|
||||
<< printStorePath(path);
|
||||
writeStorePaths(*this, teeSink, info->references);
|
||||
worker_proto::write(*this, teeSink, info->references);
|
||||
teeSink
|
||||
<< (info->deriver ? printStorePath(*info->deriver) : "")
|
||||
<< 0;
|
||||
|
|
@ -73,7 +73,7 @@ StorePaths Store::importPaths(Source & source, CheckSigsFlag checkSigs)
|
|||
|
||||
//Activity act(*logger, lvlInfo, format("importing path '%s'") % info.path);
|
||||
|
||||
auto references = readStorePaths<StorePathSet>(*this, source);
|
||||
auto references = worker_proto::read(*this, source, Phantom<StorePathSet> {});
|
||||
auto deriver = readString(source);
|
||||
auto narHash = hashString(htSHA256, *saved.s);
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ namespace nix {
|
|||
|
||||
FileTransferSettings fileTransferSettings;
|
||||
|
||||
static GlobalConfig::Register r1(&fileTransferSettings);
|
||||
static GlobalConfig::Register rFileTransferSettings(&fileTransferSettings);
|
||||
|
||||
std::string resolveUri(const std::string & uri)
|
||||
{
|
||||
|
|
@ -113,6 +113,9 @@ struct curlFileTransfer : public FileTransfer
|
|||
requestHeaders = curl_slist_append(requestHeaders, ("If-None-Match: " + request.expectedETag).c_str());
|
||||
if (!request.mimeType.empty())
|
||||
requestHeaders = curl_slist_append(requestHeaders, ("Content-Type: " + request.mimeType).c_str());
|
||||
for (auto it = request.headers.begin(); it != request.headers.end(); ++it){
|
||||
requestHeaders = curl_slist_append(requestHeaders, fmt("%s: %s", it->first, it->second).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
~TransferItem()
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ extern FileTransferSettings fileTransferSettings;
|
|||
struct FileTransferRequest
|
||||
{
|
||||
std::string uri;
|
||||
Headers headers;
|
||||
std::string expectedETag;
|
||||
bool verifyTLS = true;
|
||||
bool head = false;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "derivations.hh"
|
||||
#include "globals.hh"
|
||||
#include "local-store.hh"
|
||||
#include "local-fs-store.hh"
|
||||
#include "finally.hh"
|
||||
|
||||
#include <functional>
|
||||
|
|
@ -682,7 +683,7 @@ void LocalStore::removeUnusedLinks(const GCState & state)
|
|||
struct stat st;
|
||||
if (stat(linksDir.c_str(), &st) == -1)
|
||||
throw SysError("statting '%1%'", linksDir);
|
||||
auto overhead = st.st_blocks * 512ULL;
|
||||
int64_t overhead = st.st_blocks * 512ULL;
|
||||
|
||||
printInfo("note: currently hard linking saves %.2f MiB",
|
||||
((unsharedSize - actualSize - overhead) / (1024.0 * 1024.0)));
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ namespace nix {
|
|||
|
||||
Settings settings;
|
||||
|
||||
static GlobalConfig::Register r1(&settings);
|
||||
static GlobalConfig::Register rSettings(&settings);
|
||||
|
||||
Settings::Settings()
|
||||
: nixPrefix(NIX_PREFIX)
|
||||
|
|
|
|||
|
|
@ -859,8 +859,54 @@ public:
|
|||
are loaded as plugins (non-recursively).
|
||||
)"};
|
||||
|
||||
Setting<std::string> githubAccessToken{this, "", "github-access-token",
|
||||
"GitHub access token to get access to GitHub data through the GitHub API for `github:<..>` flakes."};
|
||||
Setting<StringMap> accessTokens{this, {}, "access-tokens",
|
||||
R"(
|
||||
Access tokens used to access protected GitHub, GitLab, or
|
||||
other locations requiring token-based authentication.
|
||||
|
||||
Access tokens are specified as a string made up of
|
||||
space-separated `host=token` values. The specific token
|
||||
used is selected by matching the `host` portion against the
|
||||
"host" specification of the input. The actual use of the
|
||||
`token` value is determined by the type of resource being
|
||||
accessed:
|
||||
|
||||
* Github: the token value is the OAUTH-TOKEN string obtained
|
||||
as the Personal Access Token from the Github server (see
|
||||
https://docs.github.com/en/developers/apps/authorizing-oath-apps).
|
||||
|
||||
* Gitlab: the token value is either the OAuth2 token or the
|
||||
Personal Access Token (these are different types tokens
|
||||
for gitlab, see
|
||||
https://docs.gitlab.com/12.10/ee/api/README.html#authentication).
|
||||
The `token` value should be `type:tokenstring` where
|
||||
`type` is either `OAuth2` or `PAT` to indicate which type
|
||||
of token is being specified.
|
||||
|
||||
Example `~/.config/nix/nix.conf`:
|
||||
|
||||
```
|
||||
access-tokens = "github.com=23ac...b289 gitlab.mycompany.com=PAT:A123Bp_Cd..EfG gitlab.com=OAuth2:1jklw3jk"
|
||||
```
|
||||
|
||||
Example `~/code/flake.nix`:
|
||||
|
||||
```nix
|
||||
input.foo = {
|
||||
type = "gitlab";
|
||||
host = "gitlab.mycompany.com";
|
||||
owner = "mycompany";
|
||||
repo = "pro";
|
||||
};
|
||||
```
|
||||
|
||||
This example specifies three tokens, one each for accessing
|
||||
github.com, gitlab.mycompany.com, and sourceforge.net.
|
||||
|
||||
The `input.foo` uses the "gitlab" fetcher, which might
|
||||
requires specifying the token type along with the token
|
||||
value.
|
||||
)"};
|
||||
|
||||
Setting<Strings> experimentalFeatures{this, {}, "experimental-features",
|
||||
"Experimental Nix features to enable."};
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ public:
|
|||
if (forceHttp) ret.insert("file");
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void maybeDisable()
|
||||
|
|
@ -180,6 +181,6 @@ protected:
|
|||
|
||||
};
|
||||
|
||||
static RegisterStoreImplementation<HttpBinaryCacheStore, HttpBinaryCacheStoreConfig> regStore;
|
||||
static RegisterStoreImplementation<HttpBinaryCacheStore, HttpBinaryCacheStoreConfig> regHttpBinaryCacheStore;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ struct LegacySSHStore : public Store, public virtual LegacySSHStoreConfig
|
|||
auto deriver = readString(conn->from);
|
||||
if (deriver != "")
|
||||
info->deriver = parseStorePath(deriver);
|
||||
info->references = readStorePaths<StorePathSet>(*this, conn->from);
|
||||
info->references = worker_proto::read(*this, conn->from, Phantom<StorePathSet> {});
|
||||
readLongLong(conn->from); // download size
|
||||
info->narSize = readLongLong(conn->from);
|
||||
|
||||
|
|
@ -156,7 +156,7 @@ struct LegacySSHStore : public Store, public virtual LegacySSHStoreConfig
|
|||
<< printStorePath(info.path)
|
||||
<< (info.deriver ? printStorePath(*info.deriver) : "")
|
||||
<< info.narHash.to_string(Base16, false);
|
||||
writeStorePaths(*this, conn->to, info.references);
|
||||
worker_proto::write(*this, conn->to, info.references);
|
||||
conn->to
|
||||
<< info.registrationTime
|
||||
<< info.narSize
|
||||
|
|
@ -185,7 +185,7 @@ struct LegacySSHStore : public Store, public virtual LegacySSHStoreConfig
|
|||
conn->to
|
||||
<< exportMagic
|
||||
<< printStorePath(info.path);
|
||||
writeStorePaths(*this, conn->to, info.references);
|
||||
worker_proto::write(*this, conn->to, info.references);
|
||||
conn->to
|
||||
<< (info.deriver ? printStorePath(*info.deriver) : "")
|
||||
<< 0
|
||||
|
|
@ -301,10 +301,10 @@ public:
|
|||
conn->to
|
||||
<< cmdQueryClosure
|
||||
<< includeOutputs;
|
||||
writeStorePaths(*this, conn->to, paths);
|
||||
worker_proto::write(*this, conn->to, paths);
|
||||
conn->to.flush();
|
||||
|
||||
for (auto & i : readStorePaths<StorePathSet>(*this, conn->from))
|
||||
for (auto & i : worker_proto::read(*this, conn->from, Phantom<StorePathSet> {}))
|
||||
out.insert(i);
|
||||
}
|
||||
|
||||
|
|
@ -317,10 +317,10 @@ public:
|
|||
<< cmdQueryValidPaths
|
||||
<< false // lock
|
||||
<< maybeSubstitute;
|
||||
writeStorePaths(*this, conn->to, paths);
|
||||
worker_proto::write(*this, conn->to, paths);
|
||||
conn->to.flush();
|
||||
|
||||
return readStorePaths<StorePathSet>(*this, conn->from);
|
||||
return worker_proto::read(*this, conn->from, Phantom<StorePathSet> {});
|
||||
}
|
||||
|
||||
void connect() override
|
||||
|
|
@ -335,6 +335,6 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
static RegisterStoreImplementation<LegacySSHStore, LegacySSHStoreConfig> regStore;
|
||||
static RegisterStoreImplementation<LegacySSHStore, LegacySSHStoreConfig> regLegacySSHStore;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,6 +105,6 @@ std::set<std::string> LocalBinaryCacheStore::uriSchemes()
|
|||
return {"file"};
|
||||
}
|
||||
|
||||
static RegisterStoreImplementation<LocalBinaryCacheStore, LocalBinaryCacheStoreConfig> regStore;
|
||||
static RegisterStoreImplementation<LocalBinaryCacheStore, LocalBinaryCacheStoreConfig> regLocalBinaryCacheStore;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "archive.hh"
|
||||
#include "fs-accessor.hh"
|
||||
#include "store-api.hh"
|
||||
#include "local-fs-store.hh"
|
||||
#include "globals.hh"
|
||||
#include "compression.hh"
|
||||
#include "derivations.hh"
|
||||
|
|
|
|||
48
src/libstore/local-fs-store.hh
Normal file
48
src/libstore/local-fs-store.hh
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include "store-api.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct LocalFSStoreConfig : virtual StoreConfig
|
||||
{
|
||||
using StoreConfig::StoreConfig;
|
||||
// FIXME: the (StoreConfig*) cast works around a bug in gcc that causes
|
||||
// it to omit the call to the Setting constructor. Clang works fine
|
||||
// either way.
|
||||
const PathSetting rootDir{(StoreConfig*) this, true, "",
|
||||
"root", "directory prefixed to all other paths"};
|
||||
const PathSetting stateDir{(StoreConfig*) this, false,
|
||||
rootDir != "" ? rootDir + "/nix/var/nix" : settings.nixStateDir,
|
||||
"state", "directory where Nix will store state"};
|
||||
const PathSetting logDir{(StoreConfig*) this, false,
|
||||
rootDir != "" ? rootDir + "/nix/var/log/nix" : settings.nixLogDir,
|
||||
"log", "directory where Nix will store state"};
|
||||
};
|
||||
|
||||
class LocalFSStore : public virtual Store, public virtual LocalFSStoreConfig
|
||||
{
|
||||
public:
|
||||
|
||||
const static string drvsLogDir;
|
||||
|
||||
LocalFSStore(const Params & params);
|
||||
|
||||
void narFromPath(const StorePath & path, Sink & sink) override;
|
||||
ref<FSAccessor> getFSAccessor() override;
|
||||
|
||||
/* Register a permanent GC root. */
|
||||
Path addPermRoot(const StorePath & storePath, const Path & gcRoot);
|
||||
|
||||
virtual Path getRealStoreDir() { return storeDir; }
|
||||
|
||||
Path toRealPath(const Path & storePath) override
|
||||
{
|
||||
assert(isInStore(storePath));
|
||||
return getRealStoreDir() + "/" + std::string(storePath, storeDir.size() + 1);
|
||||
}
|
||||
|
||||
std::shared_ptr<std::string> getBuildLog(const StorePath & path) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -721,7 +721,7 @@ uint64_t LocalStore::queryValidPathId(State & state, const StorePath & path)
|
|||
{
|
||||
auto use(state.stmtQueryPathInfo.use()(printStorePath(path)));
|
||||
if (!use.next())
|
||||
throw Error("path '%s' is not valid", printStorePath(path));
|
||||
throw InvalidPath("path '%s' is not valid", printStorePath(path));
|
||||
return use.getInt(0);
|
||||
}
|
||||
|
||||
|
|
@ -796,18 +796,58 @@ StorePathSet LocalStore::queryValidDerivers(const StorePath & path)
|
|||
}
|
||||
|
||||
|
||||
std::map<std::string, std::optional<StorePath>> LocalStore::queryPartialDerivationOutputMap(const StorePath & path)
|
||||
std::map<std::string, std::optional<StorePath>> LocalStore::queryPartialDerivationOutputMap(const StorePath & path_)
|
||||
{
|
||||
auto path = path_;
|
||||
std::map<std::string, std::optional<StorePath>> outputs;
|
||||
BasicDerivation drv = readDerivation(path);
|
||||
Derivation drv = readDerivation(path);
|
||||
for (auto & [outName, _] : drv.outputs) {
|
||||
outputs.insert_or_assign(outName, std::nullopt);
|
||||
}
|
||||
bool haveCached = false;
|
||||
{
|
||||
auto resolutions = drvPathResolutions.lock();
|
||||
auto resolvedPathOptIter = resolutions->find(path);
|
||||
if (resolvedPathOptIter != resolutions->end()) {
|
||||
auto & [_, resolvedPathOpt] = *resolvedPathOptIter;
|
||||
if (resolvedPathOpt)
|
||||
path = *resolvedPathOpt;
|
||||
haveCached = true;
|
||||
}
|
||||
}
|
||||
/* can't just use else-if instead of `!haveCached` because we need to unlock
|
||||
`drvPathResolutions` before it is locked in `Derivation::resolve`. */
|
||||
if (!haveCached && drv.type() == DerivationType::CAFloating) {
|
||||
/* Try resolve drv and use that path instead. */
|
||||
auto attempt = drv.tryResolve(*this);
|
||||
if (!attempt)
|
||||
/* If we cannot resolve the derivation, we cannot have any path
|
||||
assigned so we return the map of all std::nullopts. */
|
||||
return outputs;
|
||||
/* Just compute store path */
|
||||
auto pathResolved = writeDerivation(*this, *std::move(attempt), NoRepair, true);
|
||||
/* Store in memo table. */
|
||||
/* FIXME: memo logic should not be local-store specific, should have
|
||||
wrapper-method instead. */
|
||||
drvPathResolutions.lock()->insert_or_assign(path, pathResolved);
|
||||
path = std::move(pathResolved);
|
||||
}
|
||||
return retrySQLite<std::map<std::string, std::optional<StorePath>>>([&]() {
|
||||
auto state(_state.lock());
|
||||
|
||||
auto useQueryDerivationOutputs(state->stmtQueryDerivationOutputs.use()
|
||||
(queryValidPathId(*state, path)));
|
||||
uint64_t drvId;
|
||||
try {
|
||||
drvId = queryValidPathId(*state, path);
|
||||
} catch (InvalidPath &) {
|
||||
/* FIXME? if the derivation doesn't exist, we cannot have a mapping
|
||||
for it. */
|
||||
return outputs;
|
||||
}
|
||||
|
||||
auto useQueryDerivationOutputs {
|
||||
state->stmtQueryDerivationOutputs.use()
|
||||
(drvId)
|
||||
};
|
||||
|
||||
while (useQueryDerivationOutputs.next())
|
||||
outputs.insert_or_assign(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "pathlocks.hh"
|
||||
#include "store-api.hh"
|
||||
#include "local-fs-store.hh"
|
||||
#include "sync.hh"
|
||||
#include "util.hh"
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ libstore_NAME = libnixstore
|
|||
|
||||
libstore_DIR := $(d)
|
||||
|
||||
libstore_SOURCES := $(wildcard $(d)/*.cc $(d)/builtins/*.cc)
|
||||
libstore_SOURCES := $(wildcard $(d)/*.cc $(d)/builtins/*.cc $(d)/build/*.cc)
|
||||
|
||||
libstore_LIBS = libutil
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ ifeq ($(HAVE_SECCOMP), 1)
|
|||
endif
|
||||
|
||||
libstore_CXXFLAGS += \
|
||||
-I src/libutil -I src/libstore \
|
||||
-I src/libutil -I src/libstore -I src/libstore/build \
|
||||
-DNIX_PREFIX=\"$(prefix)\" \
|
||||
-DNIX_STORE_DIR=\"$(storedir)\" \
|
||||
-DNIX_DATA_DIR=\"$(datadir)\" \
|
||||
|
|
@ -64,3 +64,6 @@ $(eval $(call install-file-in, $(d)/nix-store.pc, $(prefix)/lib/pkgconfig, 0644)
|
|||
|
||||
$(foreach i, $(wildcard src/libstore/builtins/*.hh), \
|
||||
$(eval $(call install-file-in, $(i), $(includedir)/nix/builtins, 0644)))
|
||||
|
||||
$(foreach i, $(wildcard src/libstore/build/*.hh), \
|
||||
$(eval $(call install-file-in, $(i), $(includedir)/nix/build, 0644)))
|
||||
|
|
|
|||
93
src/libstore/lock.cc
Normal file
93
src/libstore/lock.cc
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
#include "lock.hh"
|
||||
#include "globals.hh"
|
||||
#include "pathlocks.hh"
|
||||
|
||||
#include <grp.h>
|
||||
#include <pwd.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
UserLock::UserLock()
|
||||
{
|
||||
assert(settings.buildUsersGroup != "");
|
||||
createDirs(settings.nixStateDir + "/userpool");
|
||||
}
|
||||
|
||||
bool UserLock::findFreeUser() {
|
||||
if (enabled()) return true;
|
||||
|
||||
/* Get the members of the build-users-group. */
|
||||
struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str());
|
||||
if (!gr)
|
||||
throw Error("the group '%1%' specified in 'build-users-group' does not exist",
|
||||
settings.buildUsersGroup);
|
||||
gid = gr->gr_gid;
|
||||
|
||||
/* Copy the result of getgrnam. */
|
||||
Strings users;
|
||||
for (char * * p = gr->gr_mem; *p; ++p) {
|
||||
debug("found build user '%1%'", *p);
|
||||
users.push_back(*p);
|
||||
}
|
||||
|
||||
if (users.empty())
|
||||
throw Error("the build users group '%1%' has no members",
|
||||
settings.buildUsersGroup);
|
||||
|
||||
/* Find a user account that isn't currently in use for another
|
||||
build. */
|
||||
for (auto & i : users) {
|
||||
debug("trying user '%1%'", i);
|
||||
|
||||
struct passwd * pw = getpwnam(i.c_str());
|
||||
if (!pw)
|
||||
throw Error("the user '%1%' in the group '%2%' does not exist",
|
||||
i, settings.buildUsersGroup);
|
||||
|
||||
|
||||
fnUserLock = (format("%1%/userpool/%2%") % settings.nixStateDir % pw->pw_uid).str();
|
||||
|
||||
AutoCloseFD fd = open(fnUserLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600);
|
||||
if (!fd)
|
||||
throw SysError("opening user lock '%1%'", fnUserLock);
|
||||
|
||||
if (lockFile(fd.get(), ltWrite, false)) {
|
||||
fdUserLock = std::move(fd);
|
||||
user = i;
|
||||
uid = pw->pw_uid;
|
||||
|
||||
/* Sanity check... */
|
||||
if (uid == getuid() || uid == geteuid())
|
||||
throw Error("the Nix user should not be a member of '%1%'",
|
||||
settings.buildUsersGroup);
|
||||
|
||||
#if __linux__
|
||||
/* Get the list of supplementary groups of this build user. This
|
||||
is usually either empty or contains a group such as "kvm". */
|
||||
supplementaryGIDs.resize(10);
|
||||
int ngroups = supplementaryGIDs.size();
|
||||
int err = getgrouplist(pw->pw_name, pw->pw_gid,
|
||||
supplementaryGIDs.data(), &ngroups);
|
||||
if (err == -1)
|
||||
throw Error("failed to get list of supplementary groups for '%1%'", pw->pw_name);
|
||||
|
||||
supplementaryGIDs.resize(ngroups);
|
||||
#endif
|
||||
|
||||
isEnabled = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void UserLock::kill()
|
||||
{
|
||||
killUser(uid);
|
||||
}
|
||||
|
||||
}
|
||||
37
src/libstore/lock.hh
Normal file
37
src/libstore/lock.hh
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include "sync.hh"
|
||||
#include "types.hh"
|
||||
#include "util.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
class UserLock
|
||||
{
|
||||
private:
|
||||
Path fnUserLock;
|
||||
AutoCloseFD fdUserLock;
|
||||
|
||||
bool isEnabled = false;
|
||||
string user;
|
||||
uid_t uid = 0;
|
||||
gid_t gid = 0;
|
||||
std::vector<gid_t> supplementaryGIDs;
|
||||
|
||||
public:
|
||||
UserLock();
|
||||
|
||||
void kill();
|
||||
|
||||
string getUser() { return user; }
|
||||
uid_t getUID() { assert(uid); return uid; }
|
||||
uid_t getGID() { assert(gid); return gid; }
|
||||
std::vector<gid_t> getSupplementaryGIDs() { return supplementaryGIDs; }
|
||||
|
||||
bool findFreeUser();
|
||||
|
||||
bool enabled() { return isEnabled; }
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -276,21 +276,15 @@ void LocalStore::optimiseStore(OptimiseStats & stats)
|
|||
}
|
||||
}
|
||||
|
||||
static string showBytes(uint64_t bytes)
|
||||
{
|
||||
return (format("%.2f MiB") % (bytes / (1024.0 * 1024.0))).str();
|
||||
}
|
||||
|
||||
void LocalStore::optimiseStore()
|
||||
{
|
||||
OptimiseStats stats;
|
||||
|
||||
optimiseStore(stats);
|
||||
|
||||
printInfo(
|
||||
format("%1% freed by hard-linking %2% files")
|
||||
% showBytes(stats.bytesFreed)
|
||||
% stats.filesLinked);
|
||||
printInfo("%s freed by hard-linking %d files",
|
||||
showBytes(stats.bytesFreed),
|
||||
stats.filesLinked);
|
||||
}
|
||||
|
||||
void LocalStore::optimisePath(const Path & path)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include "store-api.hh"
|
||||
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "profiles.hh"
|
||||
#include "store-api.hh"
|
||||
#include "local-fs-store.hh"
|
||||
#include "util.hh"
|
||||
|
||||
#include <sys/types.h>
|
||||
|
|
|
|||
|
|
@ -12,59 +12,21 @@
|
|||
#include "logging.hh"
|
||||
#include "callback.hh"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
template<> StorePathSet readStorePaths(const Store & store, Source & from)
|
||||
{
|
||||
StorePathSet paths;
|
||||
for (auto & i : readStrings<Strings>(from))
|
||||
paths.insert(store.parseStorePath(i));
|
||||
return paths;
|
||||
}
|
||||
|
||||
void writeStorePaths(const Store & store, Sink & out, const StorePathSet & paths)
|
||||
{
|
||||
out << paths.size();
|
||||
for (auto & i : paths)
|
||||
out << store.printStorePath(i);
|
||||
}
|
||||
|
||||
|
||||
StorePathCAMap readStorePathCAMap(const Store & store, Source & from)
|
||||
{
|
||||
StorePathCAMap paths;
|
||||
auto count = readNum<size_t>(from);
|
||||
while (count--) {
|
||||
auto path = store.parseStorePath(readString(from));
|
||||
auto ca = parseContentAddressOpt(readString(from));
|
||||
paths.insert_or_assign(path, ca);
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
void writeStorePathCAMap(const Store & store, Sink & out, const StorePathCAMap & paths)
|
||||
{
|
||||
out << paths.size();
|
||||
for (auto & i : paths) {
|
||||
out << store.printStorePath(i.first);
|
||||
out << renderContentAddress(i.second);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace worker_proto {
|
||||
|
||||
std::string read(const Store & store, Source & from, Phantom<std::string> _)
|
||||
{
|
||||
return readString(from);
|
||||
}
|
||||
|
||||
void write(const Store & store, Sink & out, const std::string & str)
|
||||
{
|
||||
out << str;
|
||||
}
|
||||
|
||||
|
||||
StorePath read(const Store & store, Source & from, Phantom<StorePath> _)
|
||||
{
|
||||
return store.parseStorePath(readString(from));
|
||||
|
|
@ -76,19 +38,39 @@ void write(const Store & store, Sink & out, const StorePath & storePath)
|
|||
}
|
||||
|
||||
|
||||
template<>
|
||||
ContentAddress read(const Store & store, Source & from, Phantom<ContentAddress> _)
|
||||
{
|
||||
return parseContentAddress(readString(from));
|
||||
}
|
||||
|
||||
void write(const Store & store, Sink & out, const ContentAddress & ca)
|
||||
{
|
||||
out << renderContentAddress(ca);
|
||||
}
|
||||
|
||||
|
||||
std::optional<StorePath> read(const Store & store, Source & from, Phantom<std::optional<StorePath>> _)
|
||||
{
|
||||
auto s = readString(from);
|
||||
return s == "" ? std::optional<StorePath> {} : store.parseStorePath(s);
|
||||
}
|
||||
|
||||
template<>
|
||||
void write(const Store & store, Sink & out, const std::optional<StorePath> & storePathOpt)
|
||||
{
|
||||
out << (storePathOpt ? store.printStorePath(*storePathOpt) : "");
|
||||
}
|
||||
|
||||
|
||||
std::optional<ContentAddress> read(const Store & store, Source & from, Phantom<std::optional<ContentAddress>> _)
|
||||
{
|
||||
return parseContentAddressOpt(readString(from));
|
||||
}
|
||||
|
||||
void write(const Store & store, Sink & out, const std::optional<ContentAddress> & caOpt)
|
||||
{
|
||||
out << (caOpt ? renderContentAddress(*caOpt) : "");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -133,69 +115,6 @@ ref<RemoteStore::Connection> RemoteStore::openConnectionWrapper()
|
|||
}
|
||||
|
||||
|
||||
UDSRemoteStore::UDSRemoteStore(const Params & params)
|
||||
: StoreConfig(params)
|
||||
, Store(params)
|
||||
, LocalFSStore(params)
|
||||
, RemoteStore(params)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
UDSRemoteStore::UDSRemoteStore(
|
||||
const std::string scheme,
|
||||
std::string socket_path,
|
||||
const Params & params)
|
||||
: UDSRemoteStore(params)
|
||||
{
|
||||
path.emplace(socket_path);
|
||||
}
|
||||
|
||||
|
||||
std::string UDSRemoteStore::getUri()
|
||||
{
|
||||
if (path) {
|
||||
return std::string("unix://") + *path;
|
||||
} else {
|
||||
return "daemon";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ref<RemoteStore::Connection> UDSRemoteStore::openConnection()
|
||||
{
|
||||
auto conn = make_ref<Connection>();
|
||||
|
||||
/* Connect to a daemon that does the privileged work for us. */
|
||||
conn->fd = socket(PF_UNIX, SOCK_STREAM
|
||||
#ifdef SOCK_CLOEXEC
|
||||
| SOCK_CLOEXEC
|
||||
#endif
|
||||
, 0);
|
||||
if (!conn->fd)
|
||||
throw SysError("cannot create Unix domain socket");
|
||||
closeOnExec(conn->fd.get());
|
||||
|
||||
string socketPath = path ? *path : settings.nixDaemonSocketFile;
|
||||
|
||||
struct sockaddr_un addr;
|
||||
addr.sun_family = AF_UNIX;
|
||||
if (socketPath.size() + 1 >= sizeof(addr.sun_path))
|
||||
throw Error("socket path '%1%' is too long", socketPath);
|
||||
strcpy(addr.sun_path, socketPath.c_str());
|
||||
|
||||
if (::connect(conn->fd.get(), (struct sockaddr *) &addr, sizeof(addr)) == -1)
|
||||
throw SysError("cannot connect to daemon at '%1%'", socketPath);
|
||||
|
||||
conn->from.fd = conn->fd.get();
|
||||
conn->to.fd = conn->fd.get();
|
||||
|
||||
conn->startTime = std::chrono::steady_clock::now();
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
|
||||
void RemoteStore::initConnection(Connection & conn)
|
||||
{
|
||||
/* Send the magic greeting, check for the reply. */
|
||||
|
|
@ -337,9 +256,9 @@ StorePathSet RemoteStore::queryValidPaths(const StorePathSet & paths, Substitute
|
|||
return res;
|
||||
} else {
|
||||
conn->to << wopQueryValidPaths;
|
||||
writeStorePaths(*this, conn->to, paths);
|
||||
worker_proto::write(*this, conn->to, paths);
|
||||
conn.processStderr();
|
||||
return readStorePaths<StorePathSet>(*this, conn->from);
|
||||
return worker_proto::read(*this, conn->from, Phantom<StorePathSet> {});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -349,7 +268,7 @@ StorePathSet RemoteStore::queryAllValidPaths()
|
|||
auto conn(getConnection());
|
||||
conn->to << wopQueryAllValidPaths;
|
||||
conn.processStderr();
|
||||
return readStorePaths<StorePathSet>(*this, conn->from);
|
||||
return worker_proto::read(*this, conn->from, Phantom<StorePathSet> {});
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -366,9 +285,9 @@ StorePathSet RemoteStore::querySubstitutablePaths(const StorePathSet & paths)
|
|||
return res;
|
||||
} else {
|
||||
conn->to << wopQuerySubstitutablePaths;
|
||||
writeStorePaths(*this, conn->to, paths);
|
||||
worker_proto::write(*this, conn->to, paths);
|
||||
conn.processStderr();
|
||||
return readStorePaths<StorePathSet>(*this, conn->from);
|
||||
return worker_proto::read(*this, conn->from, Phantom<StorePathSet> {});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -390,7 +309,7 @@ void RemoteStore::querySubstitutablePathInfos(const StorePathCAMap & pathsMap, S
|
|||
auto deriver = readString(conn->from);
|
||||
if (deriver != "")
|
||||
info.deriver = parseStorePath(deriver);
|
||||
info.references = readStorePaths<StorePathSet>(*this, conn->from);
|
||||
info.references = worker_proto::read(*this, conn->from, Phantom<StorePathSet> {});
|
||||
info.downloadSize = readLongLong(conn->from);
|
||||
info.narSize = readLongLong(conn->from);
|
||||
infos.insert_or_assign(i.first, std::move(info));
|
||||
|
|
@ -403,9 +322,9 @@ void RemoteStore::querySubstitutablePathInfos(const StorePathCAMap & pathsMap, S
|
|||
StorePathSet paths;
|
||||
for (auto & path : pathsMap)
|
||||
paths.insert(path.first);
|
||||
writeStorePaths(*this, conn->to, paths);
|
||||
worker_proto::write(*this, conn->to, paths);
|
||||
} else
|
||||
writeStorePathCAMap(*this, conn->to, pathsMap);
|
||||
worker_proto::write(*this, conn->to, pathsMap);
|
||||
conn.processStderr();
|
||||
size_t count = readNum<size_t>(conn->from);
|
||||
for (size_t n = 0; n < count; n++) {
|
||||
|
|
@ -413,7 +332,7 @@ void RemoteStore::querySubstitutablePathInfos(const StorePathCAMap & pathsMap, S
|
|||
auto deriver = readString(conn->from);
|
||||
if (deriver != "")
|
||||
info.deriver = parseStorePath(deriver);
|
||||
info.references = readStorePaths<StorePathSet>(*this, conn->from);
|
||||
info.references = worker_proto::read(*this, conn->from, Phantom<StorePathSet> {});
|
||||
info.downloadSize = readLongLong(conn->from);
|
||||
info.narSize = readLongLong(conn->from);
|
||||
}
|
||||
|
|
@ -428,7 +347,7 @@ ref<const ValidPathInfo> RemoteStore::readValidPathInfo(ConnectionHandle & conn,
|
|||
auto narHash = Hash::parseAny(readString(conn->from), htSHA256);
|
||||
auto info = make_ref<ValidPathInfo>(path, narHash);
|
||||
if (deriver != "") info->deriver = parseStorePath(deriver);
|
||||
info->references = readStorePaths<StorePathSet>(*this, conn->from);
|
||||
info->references = worker_proto::read(*this, conn->from, Phantom<StorePathSet> {});
|
||||
conn->from >> info->registrationTime >> info->narSize;
|
||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 16) {
|
||||
conn->from >> info->ultimate;
|
||||
|
|
@ -472,7 +391,7 @@ void RemoteStore::queryReferrers(const StorePath & path,
|
|||
auto conn(getConnection());
|
||||
conn->to << wopQueryReferrers << printStorePath(path);
|
||||
conn.processStderr();
|
||||
for (auto & i : readStorePaths<StorePathSet>(*this, conn->from))
|
||||
for (auto & i : worker_proto::read(*this, conn->from, Phantom<StorePathSet> {}))
|
||||
referrers.insert(i);
|
||||
}
|
||||
|
||||
|
|
@ -482,7 +401,7 @@ StorePathSet RemoteStore::queryValidDerivers(const StorePath & path)
|
|||
auto conn(getConnection());
|
||||
conn->to << wopQueryValidDerivers << printStorePath(path);
|
||||
conn.processStderr();
|
||||
return readStorePaths<StorePathSet>(*this, conn->from);
|
||||
return worker_proto::read(*this, conn->from, Phantom<StorePathSet> {});
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -494,7 +413,7 @@ StorePathSet RemoteStore::queryDerivationOutputs(const StorePath & path)
|
|||
}
|
||||
conn->to << wopQueryDerivationOutputs << printStorePath(path);
|
||||
conn.processStderr();
|
||||
return readStorePaths<StorePathSet>(*this, conn->from);
|
||||
return worker_proto::read(*this, conn->from, Phantom<StorePathSet> {});
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -520,7 +439,6 @@ std::map<std::string, std::optional<StorePath>> RemoteStore::queryPartialDerivat
|
|||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::optional<StorePath> RemoteStore::queryPathFromHashPart(const std::string & hashPart)
|
||||
|
|
@ -550,7 +468,7 @@ ref<const ValidPathInfo> RemoteStore::addCAToStore(
|
|||
<< wopAddToStore
|
||||
<< name
|
||||
<< renderContentAddressMethod(caMethod);
|
||||
writeStorePaths(*this, conn->to, references);
|
||||
worker_proto::write(*this, conn->to, references);
|
||||
conn->to << repair;
|
||||
|
||||
conn.withFramedSink([&](Sink & sink) {
|
||||
|
|
@ -567,7 +485,7 @@ ref<const ValidPathInfo> RemoteStore::addCAToStore(
|
|||
[&](TextHashMethod thm) -> void {
|
||||
std::string s = dump.drain();
|
||||
conn->to << wopAddTextToStore << name << s;
|
||||
writeStorePaths(*this, conn->to, references);
|
||||
worker_proto::write(*this, conn->to, references);
|
||||
conn.processStderr();
|
||||
},
|
||||
[&](FixedOutputHashMethod fohm) -> void {
|
||||
|
|
@ -636,7 +554,7 @@ void RemoteStore::addToStore(const ValidPathInfo & info, Source & source,
|
|||
sink
|
||||
<< exportMagic
|
||||
<< printStorePath(info.path);
|
||||
writeStorePaths(*this, sink, info.references);
|
||||
worker_proto::write(*this, sink, info.references);
|
||||
sink
|
||||
<< (info.deriver ? printStorePath(*info.deriver) : "")
|
||||
<< 0 // == no legacy signature
|
||||
|
|
@ -646,7 +564,7 @@ void RemoteStore::addToStore(const ValidPathInfo & info, Source & source,
|
|||
|
||||
conn.processStderr(0, source2.get());
|
||||
|
||||
auto importedPaths = readStorePaths<StorePathSet>(*this, conn->from);
|
||||
auto importedPaths = worker_proto::read(*this, conn->from, Phantom<StorePathSet> {});
|
||||
assert(importedPaths.size() <= 1);
|
||||
}
|
||||
|
||||
|
|
@ -655,7 +573,7 @@ void RemoteStore::addToStore(const ValidPathInfo & info, Source & source,
|
|||
<< printStorePath(info.path)
|
||||
<< (info.deriver ? printStorePath(*info.deriver) : "")
|
||||
<< info.narHash.to_string(Base16, false);
|
||||
writeStorePaths(*this, conn->to, info.references);
|
||||
worker_proto::write(*this, conn->to, info.references);
|
||||
conn->to << info.registrationTime << info.narSize
|
||||
<< info.ultimate << info.sigs << renderContentAddress(info.ca)
|
||||
<< repair << !checkSigs;
|
||||
|
|
@ -777,7 +695,7 @@ void RemoteStore::collectGarbage(const GCOptions & options, GCResults & results)
|
|||
|
||||
conn->to
|
||||
<< wopCollectGarbage << options.action;
|
||||
writeStorePaths(*this, conn->to, options.pathsToDelete);
|
||||
worker_proto::write(*this, conn->to, options.pathsToDelete);
|
||||
conn->to << options.ignoreLiveness
|
||||
<< options.maxFreed
|
||||
/* removed options */
|
||||
|
|
@ -839,9 +757,9 @@ void RemoteStore::queryMissing(const std::vector<StorePathWithOutputs> & targets
|
|||
ss.push_back(p.to_string(*this));
|
||||
conn->to << ss;
|
||||
conn.processStderr();
|
||||
willBuild = readStorePaths<StorePathSet>(*this, conn->from);
|
||||
willSubstitute = readStorePaths<StorePathSet>(*this, conn->from);
|
||||
unknown = readStorePaths<StorePathSet>(*this, conn->from);
|
||||
willBuild = worker_proto::read(*this, conn->from, Phantom<StorePathSet> {});
|
||||
willSubstitute = worker_proto::read(*this, conn->from, Phantom<StorePathSet> {});
|
||||
unknown = worker_proto::read(*this, conn->from, Phantom<StorePathSet> {});
|
||||
conn->from >> downloadSize >> narSize;
|
||||
return;
|
||||
}
|
||||
|
|
@ -934,10 +852,14 @@ std::exception_ptr RemoteStore::Connection::processStderr(Sink * sink, Source *
|
|||
}
|
||||
|
||||
else if (msg == STDERR_ERROR) {
|
||||
if (GET_PROTOCOL_MINOR(daemonVersion) >= 26) {
|
||||
return std::make_exception_ptr(readError(from));
|
||||
} else {
|
||||
string error = readString(from);
|
||||
unsigned int status = readInt(from);
|
||||
return std::make_exception_ptr(Error(status, error));
|
||||
}
|
||||
}
|
||||
|
||||
else if (msg == STDERR_NEXT)
|
||||
printError(chomp(readString(from)));
|
||||
|
|
@ -1017,6 +939,4 @@ void ConnectionHandle::withFramedSink(std::function<void(Sink &sink)> fun)
|
|||
|
||||
}
|
||||
|
||||
static RegisterStoreImplementation<UDSRemoteStore, UDSRemoteStoreConfig> regStore;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,49 +155,5 @@ private:
|
|||
|
||||
};
|
||||
|
||||
struct UDSRemoteStoreConfig : virtual LocalFSStoreConfig, virtual RemoteStoreConfig
|
||||
{
|
||||
UDSRemoteStoreConfig(const Store::Params & params)
|
||||
: StoreConfig(params)
|
||||
, LocalFSStoreConfig(params)
|
||||
, RemoteStoreConfig(params)
|
||||
{
|
||||
}
|
||||
|
||||
UDSRemoteStoreConfig()
|
||||
: UDSRemoteStoreConfig(Store::Params({}))
|
||||
{
|
||||
}
|
||||
|
||||
const std::string name() override { return "Local Daemon Store"; }
|
||||
};
|
||||
|
||||
class UDSRemoteStore : public LocalFSStore, public RemoteStore, public virtual UDSRemoteStoreConfig
|
||||
{
|
||||
public:
|
||||
|
||||
UDSRemoteStore(const Params & params);
|
||||
UDSRemoteStore(const std::string scheme, std::string path, const Params & params);
|
||||
|
||||
std::string getUri() override;
|
||||
|
||||
static std::set<std::string> uriSchemes()
|
||||
{ return {"unix"}; }
|
||||
|
||||
bool sameMachine() override
|
||||
{ return true; }
|
||||
|
||||
ref<FSAccessor> getFSAccessor() override
|
||||
{ return LocalFSStore::getFSAccessor(); }
|
||||
|
||||
void narFromPath(const StorePath & path, Sink & sink) override
|
||||
{ LocalFSStore::narFromPath(path, sink); }
|
||||
|
||||
private:
|
||||
|
||||
ref<RemoteStore::Connection> openConnection() override;
|
||||
std::optional<std::string> path;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -439,7 +439,7 @@ struct S3BinaryCacheStoreImpl : public S3BinaryCacheStore, virtual S3BinaryCache
|
|||
|
||||
};
|
||||
|
||||
static RegisterStoreImplementation<S3BinaryCacheStoreImpl, S3BinaryCacheStoreConfig> regStore;
|
||||
static RegisterStoreImplementation<S3BinaryCacheStoreImpl, S3BinaryCacheStoreConfig> regS3BinaryCacheStore;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,6 @@ ref<RemoteStore::Connection> SSHStore::openConnection()
|
|||
return conn;
|
||||
}
|
||||
|
||||
static RegisterStoreImplementation<SSHStore, SSHStoreConfig> regStore;
|
||||
static RegisterStoreImplementation<SSHStore, SSHStoreConfig> regSSHStore;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1011,7 +1011,7 @@ Derivation Store::readDerivation(const StorePath & drvPath)
|
|||
|
||||
|
||||
#include "local-store.hh"
|
||||
#include "remote-store.hh"
|
||||
#include "uds-remote-store.hh"
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
|
|
|||
|
|
@ -194,6 +194,8 @@ struct StoreConfig : public Config
|
|||
*/
|
||||
StoreConfig() { assert(false); }
|
||||
|
||||
virtual ~StoreConfig() { }
|
||||
|
||||
virtual const std::string name() = 0;
|
||||
|
||||
const PathSetting storeDir_{this, false, settings.nixStore,
|
||||
|
|
@ -454,9 +456,7 @@ public:
|
|||
// FIXME: remove?
|
||||
virtual StorePath addToStoreFromDump(Source & dump, const string & name,
|
||||
FileIngestionMethod method = FileIngestionMethod::Recursive, HashType hashAlgo = htSHA256, RepairFlag repair = NoRepair)
|
||||
{
|
||||
throw Error("addToStoreFromDump() is not supported by this store");
|
||||
}
|
||||
{ unsupported("addToStoreFromDump"); }
|
||||
|
||||
/* Like addToStore, but the contents written to the output path is
|
||||
a regular file containing the given string. */
|
||||
|
|
@ -479,8 +479,38 @@ public:
|
|||
BuildMode buildMode = bmNormal);
|
||||
|
||||
/* Build a single non-materialized derivation (i.e. not from an
|
||||
on-disk .drv file). Note that ‘drvPath’ is only used for
|
||||
informational purposes. */
|
||||
on-disk .drv file).
|
||||
|
||||
‘drvPath’ is used to deduplicate worker goals so it is imperative that
|
||||
is correct. That said, it doesn't literally need to be store path that
|
||||
would be calculated from writing this derivation to the store: it is OK
|
||||
if it instead is that of a Derivation which would resolve to this (by
|
||||
taking the outputs of it's input derivations and adding them as input
|
||||
sources) such that the build time referenceable-paths are the same.
|
||||
|
||||
In the input-addressed case, we usually *do* use an "original"
|
||||
unresolved derivations's path, as that is what will be used in the
|
||||
`buildPaths` case. Also, the input-addressed output paths are verified
|
||||
only by that contents of that specific unresolved derivation, so it is
|
||||
nice to keep that information around so if the original derivation is
|
||||
ever obtained later, it can be verified whether the trusted user in fact
|
||||
used the proper output path.
|
||||
|
||||
In the content-addressed case, we want to always use the
|
||||
resolved drv path calculated from the provided derivation. This serves
|
||||
two purposes:
|
||||
|
||||
- It keeps the operation trustless, by ruling out a maliciously
|
||||
invalid drv path corresponding to a non-resolution-equivalent
|
||||
derivation.
|
||||
|
||||
- For the floating case in particular, it ensures that the derivation
|
||||
to output mapping respects the resolution equivalence relation, so
|
||||
one cannot choose different resolution-equivalent derivations to
|
||||
subvert dependency coherence (i.e. the property that one doesn't end
|
||||
up with multiple different versions of dependencies without
|
||||
explicitly choosing to allow it).
|
||||
*/
|
||||
virtual BuildResult buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
||||
BuildMode buildMode = bmNormal) = 0;
|
||||
|
||||
|
|
@ -517,7 +547,7 @@ public:
|
|||
- The collector isn't running, or it's just started but hasn't
|
||||
acquired the GC lock yet. In that case we get and release
|
||||
the lock right away, then exit. The collector scans the
|
||||
permanent root and sees our's.
|
||||
permanent root and sees ours.
|
||||
|
||||
In either case the permanent root is seen by the collector. */
|
||||
virtual void syncWithGC() { };
|
||||
|
|
@ -685,47 +715,6 @@ protected:
|
|||
|
||||
};
|
||||
|
||||
struct LocalFSStoreConfig : virtual StoreConfig
|
||||
{
|
||||
using StoreConfig::StoreConfig;
|
||||
// FIXME: the (StoreConfig*) cast works around a bug in gcc that causes
|
||||
// it to omit the call to the Setting constructor. Clang works fine
|
||||
// either way.
|
||||
const PathSetting rootDir{(StoreConfig*) this, true, "",
|
||||
"root", "directory prefixed to all other paths"};
|
||||
const PathSetting stateDir{(StoreConfig*) this, false,
|
||||
rootDir != "" ? rootDir + "/nix/var/nix" : settings.nixStateDir,
|
||||
"state", "directory where Nix will store state"};
|
||||
const PathSetting logDir{(StoreConfig*) this, false,
|
||||
rootDir != "" ? rootDir + "/nix/var/log/nix" : settings.nixLogDir,
|
||||
"log", "directory where Nix will store state"};
|
||||
};
|
||||
|
||||
class LocalFSStore : public virtual Store, public virtual LocalFSStoreConfig
|
||||
{
|
||||
public:
|
||||
|
||||
const static string drvsLogDir;
|
||||
|
||||
LocalFSStore(const Params & params);
|
||||
|
||||
void narFromPath(const StorePath & path, Sink & sink) override;
|
||||
ref<FSAccessor> getFSAccessor() override;
|
||||
|
||||
/* Register a permanent GC root. */
|
||||
Path addPermRoot(const StorePath & storePath, const Path & gcRoot);
|
||||
|
||||
virtual Path getRealStoreDir() { return storeDir; }
|
||||
|
||||
Path toRealPath(const Path & storePath) override
|
||||
{
|
||||
assert(isInStore(storePath));
|
||||
return getRealStoreDir() + "/" + std::string(storePath, storeDir.size() + 1);
|
||||
}
|
||||
|
||||
std::shared_ptr<std::string> getBuildLog(const StorePath & path) override;
|
||||
};
|
||||
|
||||
|
||||
/* Copy a path from one store to another. */
|
||||
void copyStorePath(ref<Store> srcStore, ref<Store> dstStore,
|
||||
|
|
@ -802,6 +791,7 @@ struct StoreFactory
|
|||
std::function<std::shared_ptr<Store> (const std::string & scheme, const std::string & uri, const Store::Params & params)> create;
|
||||
std::function<std::shared_ptr<StoreConfig> ()> getConfig;
|
||||
};
|
||||
|
||||
struct Implementations
|
||||
{
|
||||
static std::vector<StoreFactory> * registered;
|
||||
|
|
|
|||
81
src/libstore/uds-remote-store.cc
Normal file
81
src/libstore/uds-remote-store.cc
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#include "uds-remote-store.hh"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
UDSRemoteStore::UDSRemoteStore(const Params & params)
|
||||
: StoreConfig(params)
|
||||
, Store(params)
|
||||
, LocalFSStore(params)
|
||||
, RemoteStore(params)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
UDSRemoteStore::UDSRemoteStore(
|
||||
const std::string scheme,
|
||||
std::string socket_path,
|
||||
const Params & params)
|
||||
: UDSRemoteStore(params)
|
||||
{
|
||||
path.emplace(socket_path);
|
||||
}
|
||||
|
||||
|
||||
std::string UDSRemoteStore::getUri()
|
||||
{
|
||||
if (path) {
|
||||
return std::string("unix://") + *path;
|
||||
} else {
|
||||
return "daemon";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ref<RemoteStore::Connection> UDSRemoteStore::openConnection()
|
||||
{
|
||||
auto conn = make_ref<Connection>();
|
||||
|
||||
/* Connect to a daemon that does the privileged work for us. */
|
||||
conn->fd = socket(PF_UNIX, SOCK_STREAM
|
||||
#ifdef SOCK_CLOEXEC
|
||||
| SOCK_CLOEXEC
|
||||
#endif
|
||||
, 0);
|
||||
if (!conn->fd)
|
||||
throw SysError("cannot create Unix domain socket");
|
||||
closeOnExec(conn->fd.get());
|
||||
|
||||
string socketPath = path ? *path : settings.nixDaemonSocketFile;
|
||||
|
||||
struct sockaddr_un addr;
|
||||
addr.sun_family = AF_UNIX;
|
||||
if (socketPath.size() + 1 >= sizeof(addr.sun_path))
|
||||
throw Error("socket path '%1%' is too long", socketPath);
|
||||
strcpy(addr.sun_path, socketPath.c_str());
|
||||
|
||||
if (::connect(conn->fd.get(), (struct sockaddr *) &addr, sizeof(addr)) == -1)
|
||||
throw SysError("cannot connect to daemon at '%1%'", socketPath);
|
||||
|
||||
conn->from.fd = conn->fd.get();
|
||||
conn->to.fd = conn->fd.get();
|
||||
|
||||
conn->startTime = std::chrono::steady_clock::now();
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
|
||||
static RegisterStoreImplementation<UDSRemoteStore, UDSRemoteStoreConfig> regUDSRemoteStore;
|
||||
|
||||
}
|
||||
52
src/libstore/uds-remote-store.hh
Normal file
52
src/libstore/uds-remote-store.hh
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#pragma once
|
||||
|
||||
#include "remote-store.hh"
|
||||
#include "local-fs-store.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct UDSRemoteStoreConfig : virtual LocalFSStoreConfig, virtual RemoteStoreConfig
|
||||
{
|
||||
UDSRemoteStoreConfig(const Store::Params & params)
|
||||
: StoreConfig(params)
|
||||
, LocalFSStoreConfig(params)
|
||||
, RemoteStoreConfig(params)
|
||||
{
|
||||
}
|
||||
|
||||
UDSRemoteStoreConfig()
|
||||
: UDSRemoteStoreConfig(Store::Params({}))
|
||||
{
|
||||
}
|
||||
|
||||
const std::string name() override { return "Local Daemon Store"; }
|
||||
};
|
||||
|
||||
class UDSRemoteStore : public LocalFSStore, public RemoteStore, public virtual UDSRemoteStoreConfig
|
||||
{
|
||||
public:
|
||||
|
||||
UDSRemoteStore(const Params & params);
|
||||
UDSRemoteStore(const std::string scheme, std::string path, const Params & params);
|
||||
|
||||
std::string getUri() override;
|
||||
|
||||
static std::set<std::string> uriSchemes()
|
||||
{ return {"unix"}; }
|
||||
|
||||
bool sameMachine() override
|
||||
{ return true; }
|
||||
|
||||
ref<FSAccessor> getFSAccessor() override
|
||||
{ return LocalFSStore::getFSAccessor(); }
|
||||
|
||||
void narFromPath(const StorePath & path, Sink & sink) override
|
||||
{ LocalFSStore::narFromPath(path, sink); }
|
||||
|
||||
private:
|
||||
|
||||
ref<RemoteStore::Connection> openConnection() override;
|
||||
std::optional<std::string> path;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ namespace nix {
|
|||
#define WORKER_MAGIC_1 0x6e697863
|
||||
#define WORKER_MAGIC_2 0x6478696f
|
||||
|
||||
#define PROTOCOL_VERSION 0x119
|
||||
#define PROTOCOL_VERSION 0x11a
|
||||
#define GET_PROTOCOL_MAJOR(x) ((x) & 0xff00)
|
||||
#define GET_PROTOCOL_MINOR(x) ((x) & 0x00ff)
|
||||
|
||||
|
|
@ -66,10 +66,6 @@ typedef enum {
|
|||
class Store;
|
||||
struct Source;
|
||||
|
||||
template<class T> T readStorePaths(const Store & store, Source & from);
|
||||
|
||||
void writeStorePaths(const Store & store, Sink & out, const StorePathSet & paths);
|
||||
|
||||
/* To guide overloading */
|
||||
template<typename T>
|
||||
struct Phantom {};
|
||||
|
|
@ -78,76 +74,81 @@ struct Phantom {};
|
|||
namespace worker_proto {
|
||||
/* FIXME maybe move more stuff inside here */
|
||||
|
||||
StorePath read(const Store & store, Source & from, Phantom<StorePath> _);
|
||||
void write(const Store & store, Sink & out, const StorePath & storePath);
|
||||
#define MAKE_WORKER_PROTO(TEMPLATE, T) \
|
||||
TEMPLATE T read(const Store & store, Source & from, Phantom< T > _); \
|
||||
TEMPLATE void write(const Store & store, Sink & out, const T & str)
|
||||
|
||||
template<typename T>
|
||||
std::map<std::string, T> read(const Store & store, Source & from, Phantom<std::map<std::string, T>> _);
|
||||
template<typename T>
|
||||
void write(const Store & store, Sink & out, const std::map<string, T> & resMap);
|
||||
template<typename T>
|
||||
std::optional<T> read(const Store & store, Source & from, Phantom<std::optional<T>> _);
|
||||
template<typename T>
|
||||
void write(const Store & store, Sink & out, const std::optional<T> & optVal);
|
||||
MAKE_WORKER_PROTO(, std::string);
|
||||
MAKE_WORKER_PROTO(, StorePath);
|
||||
MAKE_WORKER_PROTO(, ContentAddress);
|
||||
|
||||
/* Specialization which uses and empty string for the empty case, taking
|
||||
advantage of the fact StorePaths always serialize to a non-empty string.
|
||||
This is done primarily for backwards compatability, so that StorePath <=
|
||||
std::optional<StorePath>, where <= is the compatability partial order.
|
||||
MAKE_WORKER_PROTO(template<typename T>, std::set<T>);
|
||||
|
||||
#define X_ template<typename K, typename V>
|
||||
#define Y_ std::map<K, V>
|
||||
MAKE_WORKER_PROTO(X_, Y_);
|
||||
#undef X_
|
||||
#undef Y_
|
||||
|
||||
/* These use the empty string for the null case, relying on the fact
|
||||
that the underlying types never serialize to the empty string.
|
||||
|
||||
We do this instead of a generic std::optional<T> instance because
|
||||
ordinal tags (0 or 1, here) are a bit of a compatability hazard. For
|
||||
the same reason, we don't have a std::variant<T..> instances (ordinal
|
||||
tags 0...n).
|
||||
|
||||
We could the generic instances and then these as specializations for
|
||||
compatability, but that's proven a bit finnicky, and also makes the
|
||||
worker protocol harder to implement in other languages where such
|
||||
specializations may not be allowed.
|
||||
*/
|
||||
template<>
|
||||
void write(const Store & store, Sink & out, const std::optional<StorePath> & optVal);
|
||||
MAKE_WORKER_PROTO(, std::optional<StorePath>);
|
||||
MAKE_WORKER_PROTO(, std::optional<ContentAddress>);
|
||||
|
||||
template<typename T>
|
||||
std::map<std::string, T> read(const Store & store, Source & from, Phantom<std::map<std::string, T>> _)
|
||||
std::set<T> read(const Store & store, Source & from, Phantom<std::set<T>> _)
|
||||
{
|
||||
std::map<string, T> resMap;
|
||||
auto size = (size_t)readInt(from);
|
||||
std::set<T> resSet;
|
||||
auto size = readNum<size_t>(from);
|
||||
while (size--) {
|
||||
auto thisKey = readString(from);
|
||||
resMap.insert_or_assign(std::move(thisKey), nix::worker_proto::read(store, from, Phantom<T> {}));
|
||||
resSet.insert(read(store, from, Phantom<T> {}));
|
||||
}
|
||||
return resSet;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void write(const Store & store, Sink & out, const std::set<T> & resSet)
|
||||
{
|
||||
out << resSet.size();
|
||||
for (auto & key : resSet) {
|
||||
write(store, out, key);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename K, typename V>
|
||||
std::map<K, V> read(const Store & store, Source & from, Phantom<std::map<K, V>> _)
|
||||
{
|
||||
std::map<K, V> resMap;
|
||||
auto size = readNum<size_t>(from);
|
||||
while (size--) {
|
||||
auto k = read(store, from, Phantom<K> {});
|
||||
auto v = read(store, from, Phantom<V> {});
|
||||
resMap.insert_or_assign(std::move(k), std::move(v));
|
||||
}
|
||||
return resMap;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void write(const Store & store, Sink & out, const std::map<string, T> & resMap)
|
||||
template<typename K, typename V>
|
||||
void write(const Store & store, Sink & out, const std::map<K, V> & resMap)
|
||||
{
|
||||
out << resMap.size();
|
||||
for (auto & i : resMap) {
|
||||
out << i.first;
|
||||
nix::worker_proto::write(store, out, i.second);
|
||||
write(store, out, i.first);
|
||||
write(store, out, i.second);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::optional<T> read(const Store & store, Source & from, Phantom<std::optional<T>> _)
|
||||
{
|
||||
auto tag = readNum<uint8_t>(from);
|
||||
switch (tag) {
|
||||
case 0:
|
||||
return std::nullopt;
|
||||
case 1:
|
||||
return nix::worker_proto::read(store, from, Phantom<T> {});
|
||||
default:
|
||||
throw Error("got an invalid tag bit for std::optional: %#04x", tag);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void write(const Store & store, Sink & out, const std::optional<T> & optVal)
|
||||
{
|
||||
out << (optVal ? 1 : 0);
|
||||
if (optVal)
|
||||
nix::worker_proto::write(store, out, *optVal);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
StorePathCAMap readStorePathCAMap(const Store & store, Source & from);
|
||||
|
||||
void writeStorePathCAMap(const Store & store, Sink & out, const StorePathCAMap & paths);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ struct ArchiveSettings : Config
|
|||
|
||||
static ArchiveSettings archiveSettings;
|
||||
|
||||
static GlobalConfig::Register r1(&archiveSettings);
|
||||
static GlobalConfig::Register rArchiveSettings(&archiveSettings);
|
||||
|
||||
const std::string narVersionMagic1 = "nix-archive-1";
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue