forked from lix-project/lix
Merge remote-tracking branch 'upstream/master' into misc-ca
This commit is contained in:
commit
03f4fafc27
|
@ -370,34 +370,6 @@ false</literal>.</para>
|
||||||
|
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry xml:id="conf-hashed-mirrors"><term><literal>hashed-mirrors</literal></term>
|
|
||||||
|
|
||||||
<listitem><para>A list of web servers used by
|
|
||||||
<function>builtins.fetchurl</function> to obtain files by
|
|
||||||
hash. The default is
|
|
||||||
<literal>http://tarballs.nixos.org/</literal>. Given a hash type
|
|
||||||
<replaceable>ht</replaceable> and a base-16 hash
|
|
||||||
<replaceable>h</replaceable>, Nix will try to download the file
|
|
||||||
from
|
|
||||||
<literal>hashed-mirror/<replaceable>ht</replaceable>/<replaceable>h</replaceable></literal>.
|
|
||||||
This allows files to be downloaded even if they have disappeared
|
|
||||||
from their original URI. For example, given the default mirror
|
|
||||||
<literal>http://tarballs.nixos.org/</literal>, when building the derivation
|
|
||||||
|
|
||||||
<programlisting>
|
|
||||||
builtins.fetchurl {
|
|
||||||
url = "https://example.org/foo-1.2.3.tar.xz";
|
|
||||||
sha256 = "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae";
|
|
||||||
}
|
|
||||||
</programlisting>
|
|
||||||
|
|
||||||
Nix will attempt to download this file from
|
|
||||||
<literal>http://tarballs.nixos.org/sha256/2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae</literal>
|
|
||||||
first. If it is not available there, if will try the original URI.</para></listitem>
|
|
||||||
|
|
||||||
</varlistentry>
|
|
||||||
|
|
||||||
|
|
||||||
<varlistentry xml:id="conf-http-connections"><term><literal>http-connections</literal></term>
|
<varlistentry xml:id="conf-http-connections"><term><literal>http-connections</literal></term>
|
||||||
|
|
||||||
<listitem><para>The maximum number of parallel TCP connections
|
<listitem><para>The maximum number of parallel TCP connections
|
||||||
|
|
|
@ -37,6 +37,8 @@ readonly PROFILE_NIX_FILE="$NIX_ROOT/var/nix/profiles/default/etc/profile.d/nix-
|
||||||
|
|
||||||
readonly NIX_INSTALLED_NIX="@nix@"
|
readonly NIX_INSTALLED_NIX="@nix@"
|
||||||
readonly NIX_INSTALLED_CACERT="@cacert@"
|
readonly NIX_INSTALLED_CACERT="@cacert@"
|
||||||
|
#readonly NIX_INSTALLED_NIX="/nix/store/j8dbv5w6jl34caywh2ygdy88knx1mdf7-nix-2.3.6"
|
||||||
|
#readonly NIX_INSTALLED_CACERT="/nix/store/7dxhzymvy330i28ii676fl1pqwcahv2f-nss-cacert-3.49.2"
|
||||||
readonly EXTRACTED_NIX_PATH="$(dirname "$0")"
|
readonly EXTRACTED_NIX_PATH="$(dirname "$0")"
|
||||||
|
|
||||||
readonly ROOT_HOME=$(echo ~root)
|
readonly ROOT_HOME=$(echo ~root)
|
||||||
|
@ -69,9 +71,11 @@ uninstall_directions() {
|
||||||
subheader "Uninstalling nix:"
|
subheader "Uninstalling nix:"
|
||||||
local step=0
|
local step=0
|
||||||
|
|
||||||
if poly_service_installed_check; then
|
if [ -e /run/systemd/system ] && poly_service_installed_check; then
|
||||||
step=$((step + 1))
|
step=$((step + 1))
|
||||||
poly_service_uninstall_directions "$step"
|
poly_service_uninstall_directions "$step"
|
||||||
|
else
|
||||||
|
step=$((step + 1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for profile_target in "${PROFILE_TARGETS[@]}"; do
|
for profile_target in "${PROFILE_TARGETS[@]}"; do
|
||||||
|
@ -250,6 +254,8 @@ function finish_success {
|
||||||
echo "But fetching the nixpkgs channel failed. (Are you offline?)"
|
echo "But fetching the nixpkgs channel failed. (Are you offline?)"
|
||||||
echo "To try again later, run \"sudo -i nix-channel --update nixpkgs\"."
|
echo "To try again later, run \"sudo -i nix-channel --update nixpkgs\"."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -e /run/systemd/system ]; then
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
|
|
||||||
Before Nix will work in your existing shells, you'll need to close
|
Before Nix will work in your existing shells, you'll need to close
|
||||||
|
@ -264,6 +270,26 @@ hesitate:
|
||||||
|
|
||||||
$(contactme)
|
$(contactme)
|
||||||
EOF
|
EOF
|
||||||
|
else
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
Before Nix will work in your existing shells, you'll need to close
|
||||||
|
them and open them again. Other than that, you should be ready to go.
|
||||||
|
|
||||||
|
Try it! Open a new terminal, and type:
|
||||||
|
|
||||||
|
$ sudo nix-daemon
|
||||||
|
$ nix-shell -p nix-info --run "nix-info -m"
|
||||||
|
|
||||||
|
Additionally, you may want to add nix-daemon to your init-system.
|
||||||
|
|
||||||
|
Thank you for using this installer. If you have any feedback, don't
|
||||||
|
hesitate:
|
||||||
|
|
||||||
|
$(contactme)
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -664,12 +690,8 @@ main() {
|
||||||
# shellcheck source=./install-darwin-multi-user.sh
|
# shellcheck source=./install-darwin-multi-user.sh
|
||||||
. "$EXTRACTED_NIX_PATH/install-darwin-multi-user.sh"
|
. "$EXTRACTED_NIX_PATH/install-darwin-multi-user.sh"
|
||||||
elif [ "$(uname -s)" = "Linux" ]; then
|
elif [ "$(uname -s)" = "Linux" ]; then
|
||||||
if [ -e /run/systemd/system ]; then
|
|
||||||
# shellcheck source=./install-systemd-multi-user.sh
|
# shellcheck source=./install-systemd-multi-user.sh
|
||||||
. "$EXTRACTED_NIX_PATH/install-systemd-multi-user.sh"
|
. "$EXTRACTED_NIX_PATH/install-systemd-multi-user.sh" # most of this works on non-systemd distros also
|
||||||
else
|
|
||||||
failure "Sorry, the multi-user installation requires systemd on Linux (detected using /run/systemd/system)"
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
failure "Sorry, I don't know what to do on $(uname)"
|
failure "Sorry, I don't know what to do on $(uname)"
|
||||||
fi
|
fi
|
||||||
|
@ -702,7 +724,10 @@ main() {
|
||||||
|
|
||||||
setup_default_profile
|
setup_default_profile
|
||||||
place_nix_configuration
|
place_nix_configuration
|
||||||
|
|
||||||
|
if [ -e /run/systemd/system ]; then
|
||||||
poly_configure_nix_daemon_service
|
poly_configure_nix_daemon_service
|
||||||
|
fi
|
||||||
|
|
||||||
trap finish_success EXIT
|
trap finish_success EXIT
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ fi
|
||||||
# Determine if we could use the multi-user installer or not
|
# Determine if we could use the multi-user installer or not
|
||||||
if [ "$(uname -s)" = "Darwin" ]; then
|
if [ "$(uname -s)" = "Darwin" ]; then
|
||||||
echo "Note: a multi-user installation is possible. See https://nixos.org/nix/manual/#sect-multi-user-installation" >&2
|
echo "Note: a multi-user installation is possible. See https://nixos.org/nix/manual/#sect-multi-user-installation" >&2
|
||||||
elif [ "$(uname -s)" = "Linux" ] && [ -e /run/systemd/system ]; then
|
elif [ "$(uname -s)" = "Linux" ]; then
|
||||||
echo "Note: a multi-user installation is possible. See https://nixos.org/nix/manual/#sect-multi-user-installation" >&2
|
echo "Note: a multi-user installation is possible. See https://nixos.org/nix/manual/#sect-multi-user-installation" >&2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ if [ "$(uname -s)" = "Darwin" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$INSTALL_MODE" = "daemon" ]; then
|
if [ "$INSTALL_MODE" = "daemon" ]; then
|
||||||
printf '\e[1;31mSwitching to the Daemon-based Installer\e[0m\n'
|
printf '\e[1;31mSwitching to the Multi-user Installer\e[0m\n'
|
||||||
exec "$self/install-multi-user"
|
exec "$self/install-multi-user"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -33,7 +33,7 @@ std::string escapeUri(std::string uri)
|
||||||
|
|
||||||
static string currentLoad;
|
static string currentLoad;
|
||||||
|
|
||||||
static AutoCloseFD openSlotLock(const Machine & m, unsigned long long slot)
|
static AutoCloseFD openSlotLock(const Machine & m, uint64_t slot)
|
||||||
{
|
{
|
||||||
return openLockFile(fmt("%s/%s-%d", currentLoad, escapeUri(m.storeUri), slot), true);
|
return openLockFile(fmt("%s/%s-%d", currentLoad, escapeUri(m.storeUri), slot), true);
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ static int _main(int argc, char * * argv)
|
||||||
bool rightType = false;
|
bool rightType = false;
|
||||||
|
|
||||||
Machine * bestMachine = nullptr;
|
Machine * bestMachine = nullptr;
|
||||||
unsigned long long bestLoad = 0;
|
uint64_t bestLoad = 0;
|
||||||
for (auto & m : machines) {
|
for (auto & m : machines) {
|
||||||
debug("considering building on remote machine '%s'", m.storeUri);
|
debug("considering building on remote machine '%s'", m.storeUri);
|
||||||
|
|
||||||
|
@ -130,8 +130,8 @@ static int _main(int argc, char * * argv)
|
||||||
m.mandatoryMet(requiredFeatures)) {
|
m.mandatoryMet(requiredFeatures)) {
|
||||||
rightType = true;
|
rightType = true;
|
||||||
AutoCloseFD free;
|
AutoCloseFD free;
|
||||||
unsigned long long load = 0;
|
uint64_t load = 0;
|
||||||
for (unsigned long long slot = 0; slot < m.maxJobs; ++slot) {
|
for (uint64_t slot = 0; slot < m.maxJobs; ++slot) {
|
||||||
auto slotLock = openSlotLock(m, slot);
|
auto slotLock = openSlotLock(m, slot);
|
||||||
if (lockFile(slotLock.get(), ltWrite, false)) {
|
if (lockFile(slotLock.get(), ltWrite, false)) {
|
||||||
if (!free) {
|
if (!free) {
|
||||||
|
|
|
@ -1259,7 +1259,7 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po
|
||||||
addErrorTrace(e, lambda.pos, "while evaluating %s",
|
addErrorTrace(e, lambda.pos, "while evaluating %s",
|
||||||
(lambda.name.set()
|
(lambda.name.set()
|
||||||
? "'" + (string) lambda.name + "'"
|
? "'" + (string) lambda.name + "'"
|
||||||
: "anonymous lambdaction"));
|
: "anonymous lambda"));
|
||||||
addErrorTrace(e, pos, "from call site%s", "");
|
addErrorTrace(e, pos, "from call site%s", "");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,6 @@ void emitTreeAttrs(
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
const fetchers::Tree & tree,
|
const fetchers::Tree & tree,
|
||||||
const fetchers::Input & input,
|
const fetchers::Input & input,
|
||||||
Value & v);
|
Value & v, bool emptyRevFallback = false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ void EvalState::realiseContext(const PathSet & context)
|
||||||
|
|
||||||
/* For performance, prefetch all substitute info. */
|
/* For performance, prefetch all substitute info. */
|
||||||
StorePathSet willBuild, willSubstitute, unknown;
|
StorePathSet willBuild, willSubstitute, unknown;
|
||||||
unsigned long long downloadSize, narSize;
|
uint64_t downloadSize, narSize;
|
||||||
store->queryMissing(drvs, willBuild, willSubstitute, unknown, downloadSize, narSize);
|
store->queryMissing(drvs, willBuild, willSubstitute, unknown, downloadSize, narSize);
|
||||||
|
|
||||||
store->buildPaths(drvs);
|
store->buildPaths(drvs);
|
||||||
|
@ -1201,7 +1201,7 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value
|
||||||
string name;
|
string name;
|
||||||
Value * filterFun = nullptr;
|
Value * filterFun = nullptr;
|
||||||
auto method = FileIngestionMethod::Recursive;
|
auto method = FileIngestionMethod::Recursive;
|
||||||
Hash expectedHash(htSHA256);
|
std::optional<Hash> expectedHash;
|
||||||
|
|
||||||
for (auto & attr : *args[0]->attrs) {
|
for (auto & attr : *args[0]->attrs) {
|
||||||
const string & n(attr.name);
|
const string & n(attr.name);
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
#include "primops.hh"
|
|
||||||
#include "eval-inline.hh"
|
|
||||||
#include "store-api.hh"
|
|
||||||
#include "hash.hh"
|
|
||||||
#include "fetchers.hh"
|
|
||||||
#include "url.hh"
|
|
||||||
|
|
||||||
namespace nix {
|
|
||||||
|
|
||||||
static void prim_fetchGit(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
|
||||||
{
|
|
||||||
std::string url;
|
|
||||||
std::optional<std::string> ref;
|
|
||||||
std::optional<Hash> rev;
|
|
||||||
std::string name = "source";
|
|
||||||
bool fetchSubmodules = false;
|
|
||||||
PathSet context;
|
|
||||||
|
|
||||||
state.forceValue(*args[0]);
|
|
||||||
|
|
||||||
if (args[0]->type == tAttrs) {
|
|
||||||
|
|
||||||
state.forceAttrs(*args[0], pos);
|
|
||||||
|
|
||||||
for (auto & attr : *args[0]->attrs) {
|
|
||||||
string n(attr.name);
|
|
||||||
if (n == "url")
|
|
||||||
url = state.coerceToString(*attr.pos, *attr.value, context, false, false);
|
|
||||||
else if (n == "ref")
|
|
||||||
ref = state.forceStringNoCtx(*attr.value, *attr.pos);
|
|
||||||
else if (n == "rev")
|
|
||||||
rev = Hash(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA1);
|
|
||||||
else if (n == "name")
|
|
||||||
name = state.forceStringNoCtx(*attr.value, *attr.pos);
|
|
||||||
else if (n == "submodules")
|
|
||||||
fetchSubmodules = state.forceBool(*attr.value, *attr.pos);
|
|
||||||
else
|
|
||||||
throw EvalError({
|
|
||||||
.hint = hintfmt("unsupported argument '%s' to 'fetchGit'", attr.name),
|
|
||||||
.errPos = *attr.pos
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url.empty())
|
|
||||||
throw EvalError({
|
|
||||||
.hint = hintfmt("'url' argument required"),
|
|
||||||
.errPos = pos
|
|
||||||
});
|
|
||||||
|
|
||||||
} else
|
|
||||||
url = state.coerceToString(pos, *args[0], context, false, false);
|
|
||||||
|
|
||||||
// FIXME: git externals probably can be used to bypass the URI
|
|
||||||
// whitelist. Ah well.
|
|
||||||
state.checkURI(url);
|
|
||||||
|
|
||||||
if (evalSettings.pureEval && !rev)
|
|
||||||
throw Error("in pure evaluation mode, 'fetchGit' requires a Git revision");
|
|
||||||
|
|
||||||
fetchers::Attrs attrs;
|
|
||||||
attrs.insert_or_assign("type", "git");
|
|
||||||
attrs.insert_or_assign("url", url.find("://") != std::string::npos ? url : "file://" + url);
|
|
||||||
if (ref) attrs.insert_or_assign("ref", *ref);
|
|
||||||
if (rev) attrs.insert_or_assign("rev", rev->gitRev());
|
|
||||||
if (fetchSubmodules) attrs.insert_or_assign("submodules", fetchers::Explicit<bool>{true});
|
|
||||||
auto input = fetchers::Input::fromAttrs(std::move(attrs));
|
|
||||||
|
|
||||||
// FIXME: use name?
|
|
||||||
auto [tree, input2] = input.fetch(state.store);
|
|
||||||
|
|
||||||
state.mkAttrs(v, 8);
|
|
||||||
auto storePath = state.store->printStorePath(tree.storePath);
|
|
||||||
mkString(*state.allocAttr(v, state.sOutPath), storePath, PathSet({storePath}));
|
|
||||||
// Backward compatibility: set 'rev' to
|
|
||||||
// 0000000000000000000000000000000000000000 for a dirty tree.
|
|
||||||
auto rev2 = input2.getRev().value_or(Hash(htSHA1));
|
|
||||||
mkString(*state.allocAttr(v, state.symbols.create("rev")), rev2.gitRev());
|
|
||||||
mkString(*state.allocAttr(v, state.symbols.create("shortRev")), rev2.gitShortRev());
|
|
||||||
// Backward compatibility: set 'revCount' to 0 for a dirty tree.
|
|
||||||
mkInt(*state.allocAttr(v, state.symbols.create("revCount")),
|
|
||||||
input2.getRevCount().value_or(0));
|
|
||||||
mkBool(*state.allocAttr(v, state.symbols.create("submodules")), fetchSubmodules);
|
|
||||||
v.attrs->sort();
|
|
||||||
|
|
||||||
if (state.allowedPaths)
|
|
||||||
state.allowedPaths->insert(tree.actualPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
static RegisterPrimOp r("fetchGit", 1, prim_fetchGit);
|
|
||||||
|
|
||||||
}
|
|
|
@ -14,7 +14,8 @@ void emitTreeAttrs(
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
const fetchers::Tree & tree,
|
const fetchers::Tree & tree,
|
||||||
const fetchers::Input & input,
|
const fetchers::Input & input,
|
||||||
Value & v)
|
Value & v,
|
||||||
|
bool emptyRevFallback)
|
||||||
{
|
{
|
||||||
assert(input.isImmutable());
|
assert(input.isImmutable());
|
||||||
|
|
||||||
|
@ -34,10 +35,20 @@ void emitTreeAttrs(
|
||||||
if (auto rev = input.getRev()) {
|
if (auto rev = input.getRev()) {
|
||||||
mkString(*state.allocAttr(v, state.symbols.create("rev")), rev->gitRev());
|
mkString(*state.allocAttr(v, state.symbols.create("rev")), rev->gitRev());
|
||||||
mkString(*state.allocAttr(v, state.symbols.create("shortRev")), rev->gitShortRev());
|
mkString(*state.allocAttr(v, state.symbols.create("shortRev")), rev->gitShortRev());
|
||||||
|
} else if (emptyRevFallback) {
|
||||||
|
// Backwards compat for `builtins.fetchGit`: dirty repos return an empty sha1 as rev
|
||||||
|
auto emptyHash = Hash(htSHA1);
|
||||||
|
mkString(*state.allocAttr(v, state.symbols.create("rev")), emptyHash.gitRev());
|
||||||
|
mkString(*state.allocAttr(v, state.symbols.create("shortRev")), emptyHash.gitRev());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.getType() == "git")
|
||||||
|
mkBool(*state.allocAttr(v, state.symbols.create("submodules")), maybeGetBoolAttr(input.attrs, "submodules").value_or(false));
|
||||||
|
|
||||||
if (auto revCount = input.getRevCount())
|
if (auto revCount = input.getRevCount())
|
||||||
mkInt(*state.allocAttr(v, state.symbols.create("revCount")), *revCount);
|
mkInt(*state.allocAttr(v, state.symbols.create("revCount")), *revCount);
|
||||||
|
else if (emptyRevFallback)
|
||||||
|
mkInt(*state.allocAttr(v, state.symbols.create("revCount")), 0);
|
||||||
|
|
||||||
if (auto lastModified = input.getLastModified()) {
|
if (auto lastModified = input.getLastModified()) {
|
||||||
mkInt(*state.allocAttr(v, state.symbols.create("lastModified")), *lastModified);
|
mkInt(*state.allocAttr(v, state.symbols.create("lastModified")), *lastModified);
|
||||||
|
@ -48,10 +59,26 @@ void emitTreeAttrs(
|
||||||
v.attrs->sort();
|
v.attrs->sort();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
std::string fixURI(std::string uri, EvalState &state)
|
||||||
{
|
{
|
||||||
settings.requireExperimentalFeature("flakes");
|
state.checkURI(uri);
|
||||||
|
return uri.find("://") != std::string::npos ? uri : "file://" + uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addURI(EvalState &state, fetchers::Attrs &attrs, Symbol name, std::string v)
|
||||||
|
{
|
||||||
|
string n(name);
|
||||||
|
attrs.emplace(name, n == "url" ? fixURI(v, state) : v);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fetchTree(
|
||||||
|
EvalState &state,
|
||||||
|
const Pos &pos,
|
||||||
|
Value **args,
|
||||||
|
Value &v,
|
||||||
|
const std::optional<std::string> type,
|
||||||
|
bool emptyRevFallback = false
|
||||||
|
) {
|
||||||
fetchers::Input input;
|
fetchers::Input input;
|
||||||
PathSet context;
|
PathSet context;
|
||||||
|
|
||||||
|
@ -64,8 +91,15 @@ static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, V
|
||||||
|
|
||||||
for (auto & attr : *args[0]->attrs) {
|
for (auto & attr : *args[0]->attrs) {
|
||||||
state.forceValue(*attr.value);
|
state.forceValue(*attr.value);
|
||||||
if (attr.value->type == tString)
|
if (attr.value->type == tPath || attr.value->type == tString)
|
||||||
attrs.emplace(attr.name, attr.value->string.s);
|
addURI(
|
||||||
|
state,
|
||||||
|
attrs,
|
||||||
|
attr.name,
|
||||||
|
state.coerceToString(*attr.pos, *attr.value, context, false, false)
|
||||||
|
);
|
||||||
|
else if (attr.value->type == tString)
|
||||||
|
addURI(state, attrs, attr.name, attr.value->string.s);
|
||||||
else if (attr.value->type == tBool)
|
else if (attr.value->type == tBool)
|
||||||
attrs.emplace(attr.name, fetchers::Explicit<bool>{attr.value->boolean});
|
attrs.emplace(attr.name, fetchers::Explicit<bool>{attr.value->boolean});
|
||||||
else if (attr.value->type == tInt)
|
else if (attr.value->type == tInt)
|
||||||
|
@ -75,6 +109,9 @@ static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, V
|
||||||
attr.name, showType(*attr.value));
|
attr.name, showType(*attr.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type)
|
||||||
|
attrs.emplace("type", type.value());
|
||||||
|
|
||||||
if (!attrs.count("type"))
|
if (!attrs.count("type"))
|
||||||
throw Error({
|
throw Error({
|
||||||
.hint = hintfmt("attribute 'type' is missing in call to 'fetchTree'"),
|
.hint = hintfmt("attribute 'type' is missing in call to 'fetchTree'"),
|
||||||
|
@ -82,8 +119,18 @@ static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, V
|
||||||
});
|
});
|
||||||
|
|
||||||
input = fetchers::Input::fromAttrs(std::move(attrs));
|
input = fetchers::Input::fromAttrs(std::move(attrs));
|
||||||
} else
|
} else {
|
||||||
input = fetchers::Input::fromURL(state.coerceToString(pos, *args[0], context, false, false));
|
auto url = fixURI(state.coerceToString(pos, *args[0], context, false, false), state);
|
||||||
|
|
||||||
|
if (type == "git") {
|
||||||
|
fetchers::Attrs attrs;
|
||||||
|
attrs.emplace("type", "git");
|
||||||
|
attrs.emplace("url", url);
|
||||||
|
input = fetchers::Input::fromAttrs(std::move(attrs));
|
||||||
|
} else {
|
||||||
|
input = fetchers::Input::fromURL(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!evalSettings.pureEval && !input.isDirect())
|
if (!evalSettings.pureEval && !input.isDirect())
|
||||||
input = lookupInRegistries(state.store, input).first;
|
input = lookupInRegistries(state.store, input).first;
|
||||||
|
@ -96,7 +143,13 @@ static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, V
|
||||||
if (state.allowedPaths)
|
if (state.allowedPaths)
|
||||||
state.allowedPaths->insert(tree.actualPath);
|
state.allowedPaths->insert(tree.actualPath);
|
||||||
|
|
||||||
emitTreeAttrs(state, tree, input2, v);
|
emitTreeAttrs(state, tree, input2, v, emptyRevFallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
|
{
|
||||||
|
settings.requireExperimentalFeature("flakes");
|
||||||
|
fetchTree(state, pos, args, v, std::nullopt);
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp r("fetchTree", 1, prim_fetchTree);
|
static RegisterPrimOp r("fetchTree", 1, prim_fetchTree);
|
||||||
|
@ -178,7 +231,13 @@ static void prim_fetchTarball(EvalState & state, const Pos & pos, Value * * args
|
||||||
fetch(state, pos, args, v, "fetchTarball", true, "source");
|
fetch(state, pos, args, v, "fetchTarball", true, "source");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void prim_fetchGit(EvalState &state, const Pos &pos, Value **args, Value &v)
|
||||||
|
{
|
||||||
|
fetchTree(state, pos, args, v, "git", true);
|
||||||
|
}
|
||||||
|
|
||||||
static RegisterPrimOp r2("__fetchurl", 1, prim_fetchurl);
|
static RegisterPrimOp r2("__fetchurl", 1, prim_fetchurl);
|
||||||
static RegisterPrimOp r3("fetchTarball", 1, prim_fetchTarball);
|
static RegisterPrimOp r3("fetchTarball", 1, prim_fetchTarball);
|
||||||
|
static RegisterPrimOp r4("fetchGit", 1, prim_fetchGit);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,7 +134,7 @@ std::pair<Tree, Input> Input::fetch(ref<Store> store) const
|
||||||
|
|
||||||
if (auto prevNarHash = getNarHash()) {
|
if (auto prevNarHash = getNarHash()) {
|
||||||
if (narHash != *prevNarHash)
|
if (narHash != *prevNarHash)
|
||||||
throw Error("NAR hash mismatch in input '%s' (%s), expected '%s', got '%s'",
|
throw Error((unsigned int) 102, "NAR hash mismatch in input '%s' (%s), expected '%s', got '%s'",
|
||||||
to_string(), tree.actualPath, prevNarHash->to_string(SRI, true), narHash->to_string(SRI, true));
|
to_string(), tree.actualPath, prevNarHash->to_string(SRI, true), narHash->to_string(SRI, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ void printGCWarning()
|
||||||
|
|
||||||
void printMissing(ref<Store> store, const std::vector<StorePathWithOutputs> & paths, Verbosity lvl)
|
void printMissing(ref<Store> store, const std::vector<StorePathWithOutputs> & paths, Verbosity lvl)
|
||||||
{
|
{
|
||||||
unsigned long long downloadSize, narSize;
|
uint64_t downloadSize, narSize;
|
||||||
StorePathSet willBuild, willSubstitute, unknown;
|
StorePathSet willBuild, willSubstitute, unknown;
|
||||||
store->queryMissing(paths, willBuild, willSubstitute, unknown, downloadSize, narSize);
|
store->queryMissing(paths, willBuild, willSubstitute, unknown, downloadSize, narSize);
|
||||||
printMissing(store, willBuild, willSubstitute, unknown, downloadSize, narSize, lvl);
|
printMissing(store, willBuild, willSubstitute, unknown, downloadSize, narSize, lvl);
|
||||||
|
@ -45,7 +45,7 @@ void printMissing(ref<Store> store, const std::vector<StorePathWithOutputs> & pa
|
||||||
|
|
||||||
void printMissing(ref<Store> store, const StorePathSet & willBuild,
|
void printMissing(ref<Store> store, const StorePathSet & willBuild,
|
||||||
const StorePathSet & willSubstitute, const StorePathSet & unknown,
|
const StorePathSet & willSubstitute, const StorePathSet & unknown,
|
||||||
unsigned long long downloadSize, unsigned long long narSize, Verbosity lvl)
|
uint64_t downloadSize, uint64_t narSize, Verbosity lvl)
|
||||||
{
|
{
|
||||||
if (!willBuild.empty()) {
|
if (!willBuild.empty()) {
|
||||||
if (willBuild.size() == 1)
|
if (willBuild.size() == 1)
|
||||||
|
@ -384,7 +384,7 @@ RunPager::~RunPager()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
string showBytes(unsigned long long bytes)
|
string showBytes(uint64_t bytes)
|
||||||
{
|
{
|
||||||
return (format("%.2f MiB") % (bytes / (1024.0 * 1024.0))).str();
|
return (format("%.2f MiB") % (bytes / (1024.0 * 1024.0))).str();
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ void printMissing(
|
||||||
|
|
||||||
void printMissing(ref<Store> store, const StorePathSet & willBuild,
|
void printMissing(ref<Store> store, const StorePathSet & willBuild,
|
||||||
const StorePathSet & willSubstitute, const StorePathSet & unknown,
|
const StorePathSet & willSubstitute, const StorePathSet & unknown,
|
||||||
unsigned long long downloadSize, unsigned long long narSize, Verbosity lvl = lvlInfo);
|
uint64_t downloadSize, uint64_t narSize, Verbosity lvl = lvlInfo);
|
||||||
|
|
||||||
string getArg(const string & opt,
|
string getArg(const string & opt,
|
||||||
Strings::iterator & i, const Strings::iterator & end);
|
Strings::iterator & i, const Strings::iterator & end);
|
||||||
|
@ -110,7 +110,7 @@ extern volatile ::sig_atomic_t blockInt;
|
||||||
|
|
||||||
/* GC helpers. */
|
/* GC helpers. */
|
||||||
|
|
||||||
string showBytes(unsigned long long bytes);
|
string showBytes(uint64_t bytes);
|
||||||
|
|
||||||
struct GCResults;
|
struct GCResults;
|
||||||
|
|
||||||
|
|
|
@ -153,6 +153,8 @@ void BinaryCacheStore::addToStore(const ValidPathInfo & info, Source & narSource
|
||||||
|
|
||||||
auto [fdTemp, fnTemp] = createTempFile();
|
auto [fdTemp, fnTemp] = createTempFile();
|
||||||
|
|
||||||
|
AutoDelete autoDelete(fnTemp);
|
||||||
|
|
||||||
auto now1 = std::chrono::steady_clock::now();
|
auto now1 = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
/* Read the NAR simultaneously into a CompressionSink+FileSink (to
|
/* Read the NAR simultaneously into a CompressionSink+FileSink (to
|
||||||
|
@ -167,6 +169,7 @@ void BinaryCacheStore::addToStore(const ValidPathInfo & info, Source & narSource
|
||||||
TeeSource teeSource(narSource, *compressionSink);
|
TeeSource teeSource(narSource, *compressionSink);
|
||||||
narAccessor = makeNarAccessor(teeSource);
|
narAccessor = makeNarAccessor(teeSource);
|
||||||
compressionSink->finish();
|
compressionSink->finish();
|
||||||
|
fileSink.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto now2 = std::chrono::steady_clock::now();
|
auto now2 = std::chrono::steady_clock::now();
|
||||||
|
@ -280,7 +283,7 @@ void BinaryCacheStore::addToStore(const ValidPathInfo & info, Source & narSource
|
||||||
if (repair || !fileExists(narInfo->url)) {
|
if (repair || !fileExists(narInfo->url)) {
|
||||||
stats.narWrite++;
|
stats.narWrite++;
|
||||||
upsertFile(narInfo->url,
|
upsertFile(narInfo->url,
|
||||||
std::make_shared<std::fstream>(fnTemp, std::ios_base::in),
|
std::make_shared<std::fstream>(fnTemp, std::ios_base::in | std::ios_base::binary),
|
||||||
"application/x-nix-nar");
|
"application/x-nix-nar");
|
||||||
} else
|
} else
|
||||||
stats.narWriteAverted++;
|
stats.narWriteAverted++;
|
||||||
|
|
|
@ -297,7 +297,7 @@ public:
|
||||||
GoalPtr makeDerivationGoal(const StorePath & drvPath, const StringSet & wantedOutputs, BuildMode buildMode = bmNormal);
|
GoalPtr makeDerivationGoal(const StorePath & drvPath, const StringSet & wantedOutputs, BuildMode buildMode = bmNormal);
|
||||||
std::shared_ptr<DerivationGoal> makeBasicDerivationGoal(const StorePath & drvPath,
|
std::shared_ptr<DerivationGoal> makeBasicDerivationGoal(const StorePath & drvPath,
|
||||||
const BasicDerivation & drv, BuildMode buildMode = bmNormal);
|
const BasicDerivation & drv, BuildMode buildMode = bmNormal);
|
||||||
GoalPtr makeSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair);
|
GoalPtr makeSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
||||||
|
|
||||||
/* Remove a dead goal. */
|
/* Remove a dead goal. */
|
||||||
void removeGoal(GoalPtr goal);
|
void removeGoal(GoalPtr goal);
|
||||||
|
@ -1206,7 +1206,7 @@ void DerivationGoal::haveDerivation()
|
||||||
them. */
|
them. */
|
||||||
if (settings.useSubstitutes && parsedDrv->substitutesAllowed())
|
if (settings.useSubstitutes && parsedDrv->substitutesAllowed())
|
||||||
for (auto & i : invalidOutputs)
|
for (auto & i : invalidOutputs)
|
||||||
addWaitee(worker.makeSubstitutionGoal(i, buildMode == bmRepair ? Repair : NoRepair));
|
addWaitee(worker.makeSubstitutionGoal(i, buildMode == bmRepair ? Repair : NoRepair, getDerivationCA(*drv)));
|
||||||
|
|
||||||
if (waitees.empty()) /* to prevent hang (no wake-up event) */
|
if (waitees.empty()) /* to prevent hang (no wake-up event) */
|
||||||
outputsSubstituted();
|
outputsSubstituted();
|
||||||
|
@ -1646,13 +1646,13 @@ void DerivationGoal::buildDone()
|
||||||
So instead, check if the disk is (nearly) full now. If
|
So instead, check if the disk is (nearly) full now. If
|
||||||
so, we don't mark this build as a permanent failure. */
|
so, we don't mark this build as a permanent failure. */
|
||||||
#if HAVE_STATVFS
|
#if HAVE_STATVFS
|
||||||
unsigned long long required = 8ULL * 1024 * 1024; // FIXME: make configurable
|
uint64_t required = 8ULL * 1024 * 1024; // FIXME: make configurable
|
||||||
struct statvfs st;
|
struct statvfs st;
|
||||||
if (statvfs(worker.store.realStoreDir.c_str(), &st) == 0 &&
|
if (statvfs(worker.store.realStoreDir.c_str(), &st) == 0 &&
|
||||||
(unsigned long long) st.f_bavail * st.f_bsize < required)
|
(uint64_t) st.f_bavail * st.f_bsize < required)
|
||||||
diskFull = true;
|
diskFull = true;
|
||||||
if (statvfs(tmpDir.c_str(), &st) == 0 &&
|
if (statvfs(tmpDir.c_str(), &st) == 0 &&
|
||||||
(unsigned long long) st.f_bavail * st.f_bsize < required)
|
(uint64_t) st.f_bavail * st.f_bsize < required)
|
||||||
diskFull = true;
|
diskFull = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -2851,7 +2851,7 @@ struct RestrictedStore : public LocalFSStore
|
||||||
|
|
||||||
void queryMissing(const std::vector<StorePathWithOutputs> & targets,
|
void queryMissing(const std::vector<StorePathWithOutputs> & targets,
|
||||||
StorePathSet & willBuild, StorePathSet & willSubstitute, StorePathSet & unknown,
|
StorePathSet & willBuild, StorePathSet & willSubstitute, StorePathSet & unknown,
|
||||||
unsigned long long & downloadSize, unsigned long long & narSize) override
|
uint64_t & downloadSize, uint64_t & narSize) override
|
||||||
{
|
{
|
||||||
/* This is slightly impure since it leaks information to the
|
/* This is slightly impure since it leaks information to the
|
||||||
client about what paths will be built/substituted or are
|
client about what paths will be built/substituted or are
|
||||||
|
@ -4300,6 +4300,10 @@ private:
|
||||||
/* The store path that should be realised through a substitute. */
|
/* The store path that should be realised through a substitute. */
|
||||||
StorePath storePath;
|
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. */
|
/* The remaining substituters. */
|
||||||
std::list<ref<Store>> subs;
|
std::list<ref<Store>> subs;
|
||||||
|
|
||||||
|
@ -4333,8 +4337,11 @@ private:
|
||||||
typedef void (SubstitutionGoal::*GoalState)();
|
typedef void (SubstitutionGoal::*GoalState)();
|
||||||
GoalState state;
|
GoalState state;
|
||||||
|
|
||||||
|
/* Content address for recomputing store path */
|
||||||
|
std::optional<ContentAddress> ca;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair = NoRepair);
|
SubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
||||||
~SubstitutionGoal();
|
~SubstitutionGoal();
|
||||||
|
|
||||||
void timedOut(Error && ex) override { abort(); };
|
void timedOut(Error && ex) override { abort(); };
|
||||||
|
@ -4364,10 +4371,11 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
SubstitutionGoal::SubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair)
|
SubstitutionGoal::SubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair, std::optional<ContentAddress> ca)
|
||||||
: Goal(worker)
|
: Goal(worker)
|
||||||
, storePath(storePath)
|
, storePath(storePath)
|
||||||
, repair(repair)
|
, repair(repair)
|
||||||
|
, ca(ca)
|
||||||
{
|
{
|
||||||
state = &SubstitutionGoal::init;
|
state = &SubstitutionGoal::init;
|
||||||
name = fmt("substitution of '%s'", worker.store.printStorePath(this->storePath));
|
name = fmt("substitution of '%s'", worker.store.printStorePath(this->storePath));
|
||||||
|
@ -4442,14 +4450,18 @@ void SubstitutionGoal::tryNext()
|
||||||
sub = subs.front();
|
sub = subs.front();
|
||||||
subs.pop_front();
|
subs.pop_front();
|
||||||
|
|
||||||
if (sub->storeDir != worker.store.storeDir) {
|
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();
|
tryNext();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// FIXME: make async
|
// FIXME: make async
|
||||||
info = sub->queryPathInfo(storePath);
|
info = sub->queryPathInfo(subPath ? *subPath : storePath);
|
||||||
} catch (InvalidPath &) {
|
} catch (InvalidPath &) {
|
||||||
tryNext();
|
tryNext();
|
||||||
return;
|
return;
|
||||||
|
@ -4468,6 +4480,19 @@ void SubstitutionGoal::tryNext()
|
||||||
throw;
|
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. */
|
/* Update the total expected download size. */
|
||||||
auto narInfo = std::dynamic_pointer_cast<const NarInfo>(info);
|
auto narInfo = std::dynamic_pointer_cast<const NarInfo>(info);
|
||||||
|
|
||||||
|
@ -4557,7 +4582,7 @@ void SubstitutionGoal::tryToRun()
|
||||||
PushActivity pact(act.id);
|
PushActivity pact(act.id);
|
||||||
|
|
||||||
copyStorePath(ref<Store>(sub), ref<Store>(worker.store.shared_from_this()),
|
copyStorePath(ref<Store>(sub), ref<Store>(worker.store.shared_from_this()),
|
||||||
storePath, repair, sub->isTrusted ? NoCheckSigs : CheckSigs);
|
subPath ? *subPath : storePath, repair, sub->isTrusted ? NoCheckSigs : CheckSigs);
|
||||||
|
|
||||||
promise.set_value();
|
promise.set_value();
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
|
@ -4690,11 +4715,11 @@ std::shared_ptr<DerivationGoal> Worker::makeBasicDerivationGoal(const StorePath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
GoalPtr Worker::makeSubstitutionGoal(const StorePath & path, RepairFlag repair)
|
GoalPtr Worker::makeSubstitutionGoal(const StorePath & path, RepairFlag repair, std::optional<ContentAddress> ca)
|
||||||
{
|
{
|
||||||
GoalPtr goal = substitutionGoals[path].lock(); // FIXME
|
GoalPtr goal = substitutionGoals[path].lock(); // FIXME
|
||||||
if (!goal) {
|
if (!goal) {
|
||||||
goal = std::make_shared<SubstitutionGoal>(path, *this, repair);
|
goal = std::make_shared<SubstitutionGoal>(path, *this, repair, ca);
|
||||||
substitutionGoals.insert_or_assign(path, goal);
|
substitutionGoals.insert_or_assign(path, goal);
|
||||||
wakeUp(goal);
|
wakeUp(goal);
|
||||||
}
|
}
|
||||||
|
@ -5062,7 +5087,7 @@ void Worker::markContentsGood(const StorePath & path)
|
||||||
static void primeCache(Store & store, const std::vector<StorePathWithOutputs> & paths)
|
static void primeCache(Store & store, const std::vector<StorePathWithOutputs> & paths)
|
||||||
{
|
{
|
||||||
StorePathSet willBuild, willSubstitute, unknown;
|
StorePathSet willBuild, willSubstitute, unknown;
|
||||||
unsigned long long downloadSize, narSize;
|
uint64_t downloadSize, narSize;
|
||||||
store.queryMissing(paths, willBuild, willSubstitute, unknown, downloadSize, narSize);
|
store.queryMissing(paths, willBuild, willSubstitute, unknown, downloadSize, narSize);
|
||||||
|
|
||||||
if (!willBuild.empty() && 0 == settings.maxBuildJobs && getMachines().empty())
|
if (!willBuild.empty() && 0 == settings.maxBuildJobs && getMachines().empty())
|
||||||
|
|
|
@ -58,26 +58,6 @@ void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* We always have one output, and if it's a fixed-output derivation (as
|
|
||||||
checked below) it must be the only output */
|
|
||||||
auto & output = drv.outputs.begin()->second;
|
|
||||||
|
|
||||||
/* Try the hashed mirrors first. */
|
|
||||||
if (auto hash = std::get_if<DerivationOutputFixed>(&output.output)) {
|
|
||||||
if (hash->hash.method == FileIngestionMethod::Flat) {
|
|
||||||
for (auto hashedMirror : settings.hashedMirrors.get()) {
|
|
||||||
try {
|
|
||||||
if (!hasSuffix(hashedMirror, "/")) hashedMirror += '/';
|
|
||||||
fetch(hashedMirror + printHashType(hash->hash.hash.type) + "/" + hash->hash.hash.to_string(Base16, false));
|
|
||||||
return;
|
|
||||||
} catch (Error & e) {
|
|
||||||
debug(e.what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Otherwise try the specified URL. */
|
|
||||||
fetch(mainUrl);
|
fetch(mainUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -579,7 +579,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
auto path = store->parseStorePath(readString(from));
|
auto path = store->parseStorePath(readString(from));
|
||||||
logger->startWork();
|
logger->startWork();
|
||||||
SubstitutablePathInfos infos;
|
SubstitutablePathInfos infos;
|
||||||
store->querySubstitutablePathInfos({path}, infos);
|
store->querySubstitutablePathInfos({{path, std::nullopt}}, infos);
|
||||||
logger->stopWork();
|
logger->stopWork();
|
||||||
auto i = infos.find(path);
|
auto i = infos.find(path);
|
||||||
if (i == infos.end())
|
if (i == infos.end())
|
||||||
|
@ -595,10 +595,16 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
}
|
}
|
||||||
|
|
||||||
case wopQuerySubstitutablePathInfos: {
|
case wopQuerySubstitutablePathInfos: {
|
||||||
auto paths = readStorePaths<StorePathSet>(*store, from);
|
|
||||||
logger->startWork();
|
|
||||||
SubstitutablePathInfos infos;
|
SubstitutablePathInfos infos;
|
||||||
store->querySubstitutablePathInfos(paths, infos);
|
StorePathCAMap pathsMap = {};
|
||||||
|
if (GET_PROTOCOL_MINOR(clientVersion) < 22) {
|
||||||
|
auto paths = readStorePaths<StorePathSet>(*store, from);
|
||||||
|
for (auto & path : paths)
|
||||||
|
pathsMap.emplace(path, std::nullopt);
|
||||||
|
} else
|
||||||
|
pathsMap = readStorePathCAMap(*store, from);
|
||||||
|
logger->startWork();
|
||||||
|
store->querySubstitutablePathInfos(pathsMap, infos);
|
||||||
logger->stopWork();
|
logger->stopWork();
|
||||||
to << infos.size();
|
to << infos.size();
|
||||||
for (auto & i : infos) {
|
for (auto & i : infos) {
|
||||||
|
@ -703,6 +709,64 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
if (!trusted)
|
if (!trusted)
|
||||||
info.ultimate = false;
|
info.ultimate = false;
|
||||||
|
|
||||||
|
if (GET_PROTOCOL_MINOR(clientVersion) >= 23) {
|
||||||
|
|
||||||
|
struct FramedSource : Source
|
||||||
|
{
|
||||||
|
Source & from;
|
||||||
|
bool eof = false;
|
||||||
|
std::vector<unsigned char> pending;
|
||||||
|
size_t pos = 0;
|
||||||
|
|
||||||
|
FramedSource(Source & from) : from(from)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
~FramedSource()
|
||||||
|
{
|
||||||
|
if (!eof) {
|
||||||
|
while (true) {
|
||||||
|
auto n = readInt(from);
|
||||||
|
if (!n) break;
|
||||||
|
std::vector<unsigned char> data(n);
|
||||||
|
from(data.data(), n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t read(unsigned char * data, size_t len) override
|
||||||
|
{
|
||||||
|
if (eof) throw EndOfFile("reached end of FramedSource");
|
||||||
|
|
||||||
|
if (pos >= pending.size()) {
|
||||||
|
size_t len = readInt(from);
|
||||||
|
if (!len) {
|
||||||
|
eof = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
pending = std::vector<unsigned char>(len);
|
||||||
|
pos = 0;
|
||||||
|
from(pending.data(), len);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto n = std::min(len, pending.size() - pos);
|
||||||
|
memcpy(data, pending.data() + pos, n);
|
||||||
|
pos += n;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
logger->startWork();
|
||||||
|
|
||||||
|
{
|
||||||
|
FramedSource source(from);
|
||||||
|
store->addToStore(info, source, (RepairFlag) repair,
|
||||||
|
dontCheckSigs ? NoCheckSigs : CheckSigs);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger->stopWork();
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
std::unique_ptr<Source> source;
|
std::unique_ptr<Source> source;
|
||||||
if (GET_PROTOCOL_MINOR(clientVersion) >= 21)
|
if (GET_PROTOCOL_MINOR(clientVersion) >= 21)
|
||||||
source = std::make_unique<TunnelSource>(from, to);
|
source = std::make_unique<TunnelSource>(from, to);
|
||||||
|
@ -721,6 +785,8 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
dontCheckSigs ? NoCheckSigs : CheckSigs);
|
dontCheckSigs ? NoCheckSigs : CheckSigs);
|
||||||
|
|
||||||
logger->stopWork();
|
logger->stopWork();
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -730,7 +796,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
targets.push_back(store->parsePathWithOutputs(s));
|
targets.push_back(store->parsePathWithOutputs(s));
|
||||||
logger->startWork();
|
logger->startWork();
|
||||||
StorePathSet willBuild, willSubstitute, unknown;
|
StorePathSet willBuild, willSubstitute, unknown;
|
||||||
unsigned long long downloadSize, narSize;
|
uint64_t downloadSize, narSize;
|
||||||
store->queryMissing(targets, willBuild, willSubstitute, unknown, downloadSize, narSize);
|
store->queryMissing(targets, willBuild, willSubstitute, unknown, downloadSize, narSize);
|
||||||
logger->stopWork();
|
logger->stopWork();
|
||||||
writeStorePaths(*store, to, willBuild);
|
writeStorePaths(*store, to, willBuild);
|
||||||
|
|
|
@ -500,7 +500,7 @@ struct LocalStore::GCState
|
||||||
StorePathSet alive;
|
StorePathSet alive;
|
||||||
bool gcKeepOutputs;
|
bool gcKeepOutputs;
|
||||||
bool gcKeepDerivations;
|
bool gcKeepDerivations;
|
||||||
unsigned long long bytesInvalidated;
|
uint64_t bytesInvalidated;
|
||||||
bool moveToTrash = true;
|
bool moveToTrash = true;
|
||||||
bool shouldDelete;
|
bool shouldDelete;
|
||||||
GCState(const GCOptions & options, GCResults & results)
|
GCState(const GCOptions & options, GCResults & results)
|
||||||
|
@ -518,7 +518,7 @@ bool LocalStore::isActiveTempFile(const GCState & state,
|
||||||
|
|
||||||
void LocalStore::deleteGarbage(GCState & state, const Path & path)
|
void LocalStore::deleteGarbage(GCState & state, const Path & path)
|
||||||
{
|
{
|
||||||
unsigned long long bytesFreed;
|
uint64_t bytesFreed;
|
||||||
deletePath(path, bytesFreed);
|
deletePath(path, bytesFreed);
|
||||||
state.results.bytesFreed += bytesFreed;
|
state.results.bytesFreed += bytesFreed;
|
||||||
}
|
}
|
||||||
|
@ -528,7 +528,7 @@ void LocalStore::deletePathRecursive(GCState & state, const Path & path)
|
||||||
{
|
{
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
|
|
||||||
unsigned long long size = 0;
|
uint64_t size = 0;
|
||||||
|
|
||||||
auto storePath = maybeParseStorePath(path);
|
auto storePath = maybeParseStorePath(path);
|
||||||
if (storePath && isValidPath(*storePath)) {
|
if (storePath && isValidPath(*storePath)) {
|
||||||
|
@ -687,7 +687,7 @@ void LocalStore::removeUnusedLinks(const GCState & state)
|
||||||
AutoCloseDir dir(opendir(linksDir.c_str()));
|
AutoCloseDir dir(opendir(linksDir.c_str()));
|
||||||
if (!dir) throw SysError("opening directory '%1%'", linksDir);
|
if (!dir) throw SysError("opening directory '%1%'", linksDir);
|
||||||
|
|
||||||
long long actualSize = 0, unsharedSize = 0;
|
int64_t actualSize = 0, unsharedSize = 0;
|
||||||
|
|
||||||
struct dirent * dirent;
|
struct dirent * dirent;
|
||||||
while (errno = 0, dirent = readdir(dir.get())) {
|
while (errno = 0, dirent = readdir(dir.get())) {
|
||||||
|
@ -717,10 +717,10 @@ void LocalStore::removeUnusedLinks(const GCState & state)
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (stat(linksDir.c_str(), &st) == -1)
|
if (stat(linksDir.c_str(), &st) == -1)
|
||||||
throw SysError("statting '%1%'", linksDir);
|
throw SysError("statting '%1%'", linksDir);
|
||||||
long long overhead = st.st_blocks * 512ULL;
|
auto overhead = st.st_blocks * 512ULL;
|
||||||
|
|
||||||
printInfo(format("note: currently hard linking saves %.2f MiB")
|
printInfo("note: currently hard linking saves %.2f MiB",
|
||||||
% ((unsharedSize - actualSize - overhead) / (1024.0 * 1024.0)));
|
((unsharedSize - actualSize - overhead) / (1024.0 * 1024.0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -335,9 +335,6 @@ public:
|
||||||
"setuid/setgid bits or with file capabilities."};
|
"setuid/setgid bits or with file capabilities."};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Setting<Strings> hashedMirrors{this, {"http://tarballs.nixos.org/"}, "hashed-mirrors",
|
|
||||||
"A list of servers used by builtins.fetchurl to fetch files by hash."};
|
|
||||||
|
|
||||||
Setting<uint64_t> minFree{this, 0, "min-free",
|
Setting<uint64_t> minFree{this, 0, "min-free",
|
||||||
"Automatically run the garbage collector when free disk space drops below the specified amount."};
|
"Automatically run the garbage collector when free disk space drops below the specified amount."};
|
||||||
|
|
||||||
|
|
|
@ -854,20 +854,32 @@ StorePathSet LocalStore::querySubstitutablePaths(const StorePathSet & paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void LocalStore::querySubstitutablePathInfos(const StorePathSet & paths,
|
void LocalStore::querySubstitutablePathInfos(const StorePathCAMap & paths, SubstitutablePathInfos & infos)
|
||||||
SubstitutablePathInfos & infos)
|
|
||||||
{
|
{
|
||||||
if (!settings.useSubstitutes) return;
|
if (!settings.useSubstitutes) return;
|
||||||
for (auto & sub : getDefaultSubstituters()) {
|
for (auto & sub : getDefaultSubstituters()) {
|
||||||
if (sub->storeDir != storeDir) continue;
|
|
||||||
for (auto & path : paths) {
|
for (auto & path : paths) {
|
||||||
if (infos.count(path)) continue;
|
auto subPath(path.first);
|
||||||
debug("checking substituter '%s' for path '%s'", sub->getUri(), printStorePath(path));
|
|
||||||
|
// recompute store path so that we can use a different store root
|
||||||
|
if (path.second) {
|
||||||
|
subPath = makeFixedOutputPathFromCA(path.first.name(), *path.second);
|
||||||
|
if (sub->storeDir == storeDir)
|
||||||
|
assert(subPath == path.first);
|
||||||
|
if (subPath != path.first)
|
||||||
|
debug("replaced path '%s' with '%s' for substituter '%s'", printStorePath(path.first), sub->printStorePath(subPath), sub->getUri());
|
||||||
|
} else if (sub->storeDir != storeDir) continue;
|
||||||
|
|
||||||
|
debug("checking substituter '%s' for path '%s'", sub->getUri(), sub->printStorePath(subPath));
|
||||||
try {
|
try {
|
||||||
auto info = sub->queryPathInfo(path);
|
auto info = sub->queryPathInfo(subPath);
|
||||||
|
|
||||||
|
if (sub->storeDir != storeDir && !(info->isContentAddressed(*sub) && info->references.empty()))
|
||||||
|
continue;
|
||||||
|
|
||||||
auto narInfo = std::dynamic_pointer_cast<const NarInfo>(
|
auto narInfo = std::dynamic_pointer_cast<const NarInfo>(
|
||||||
std::shared_ptr<const ValidPathInfo>(info));
|
std::shared_ptr<const ValidPathInfo>(info));
|
||||||
infos.insert_or_assign(path, SubstitutablePathInfo{
|
infos.insert_or_assign(path.first, SubstitutablePathInfo{
|
||||||
info->deriver,
|
info->deriver,
|
||||||
info->references,
|
info->references,
|
||||||
narInfo ? narInfo->fileSize : 0,
|
narInfo ? narInfo->fileSize : 0,
|
||||||
|
@ -1041,20 +1053,6 @@ void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
StorePath LocalStore::addToStore(const string & name, const Path & _srcPath,
|
|
||||||
FileIngestionMethod method, HashType hashAlgo, PathFilter & filter, RepairFlag repair)
|
|
||||||
{
|
|
||||||
Path srcPath(absPath(_srcPath));
|
|
||||||
auto source = sinkToSource([&](Sink & sink) {
|
|
||||||
if (method == FileIngestionMethod::Recursive)
|
|
||||||
dumpPath(srcPath, sink, filter);
|
|
||||||
else
|
|
||||||
readFile(srcPath, sink);
|
|
||||||
});
|
|
||||||
return addToStoreFromDump(*source, name, method, hashAlgo, repair);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
StorePath LocalStore::addToStoreFromDump(Source & source0, const string & name,
|
StorePath LocalStore::addToStoreFromDump(Source & source0, const string & name,
|
||||||
FileIngestionMethod method, HashType hashAlgo, RepairFlag repair)
|
FileIngestionMethod method, HashType hashAlgo, RepairFlag repair)
|
||||||
{
|
{
|
||||||
|
|
|
@ -29,8 +29,8 @@ struct Derivation;
|
||||||
struct OptimiseStats
|
struct OptimiseStats
|
||||||
{
|
{
|
||||||
unsigned long filesLinked = 0;
|
unsigned long filesLinked = 0;
|
||||||
unsigned long long bytesFreed = 0;
|
uint64_t bytesFreed = 0;
|
||||||
unsigned long long blocksFreed = 0;
|
uint64_t blocksFreed = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -139,22 +139,14 @@ public:
|
||||||
|
|
||||||
StorePathSet querySubstitutablePaths(const StorePathSet & paths) override;
|
StorePathSet querySubstitutablePaths(const StorePathSet & paths) override;
|
||||||
|
|
||||||
void querySubstitutablePathInfos(const StorePathSet & paths,
|
void querySubstitutablePathInfos(const StorePathCAMap & paths,
|
||||||
SubstitutablePathInfos & infos) override;
|
SubstitutablePathInfos & infos) override;
|
||||||
|
|
||||||
void addToStore(const ValidPathInfo & info, Source & source,
|
void addToStore(const ValidPathInfo & info, Source & source,
|
||||||
RepairFlag repair, CheckSigsFlag checkSigs) override;
|
RepairFlag repair, CheckSigsFlag checkSigs) override;
|
||||||
|
|
||||||
StorePath addToStore(const string & name, const Path & srcPath,
|
|
||||||
FileIngestionMethod method, HashType hashAlgo,
|
|
||||||
PathFilter & filter, RepairFlag repair) override;
|
|
||||||
|
|
||||||
/* Like addToStore(), but the contents of the path are contained
|
|
||||||
in `dump', which is either a NAR serialisation (if recursive ==
|
|
||||||
true) or simply the contents of a regular file (if recursive ==
|
|
||||||
false). */
|
|
||||||
StorePath addToStoreFromDump(Source & dump, const string & name,
|
StorePath addToStoreFromDump(Source & dump, const string & name,
|
||||||
FileIngestionMethod method = FileIngestionMethod::Recursive, HashType hashAlgo = htSHA256, RepairFlag repair = NoRepair) override;
|
FileIngestionMethod method, HashType hashAlgo, RepairFlag repair) override;
|
||||||
|
|
||||||
StorePath addTextToStore(const string & name, const string & s,
|
StorePath addTextToStore(const string & name, const string & s,
|
||||||
const StorePathSet & references, RepairFlag repair) override;
|
const StorePathSet & references, RepairFlag repair) override;
|
||||||
|
|
|
@ -108,9 +108,19 @@ void Store::computeFSClosure(const StorePath & startPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::optional<ContentAddress> getDerivationCA(const BasicDerivation & drv)
|
||||||
|
{
|
||||||
|
auto out = drv.outputs.find("out");
|
||||||
|
if (out != drv.outputs.end()) {
|
||||||
|
if (auto v = std::get_if<DerivationOutputFixed>(&out->second.output))
|
||||||
|
return v->hash;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
void Store::queryMissing(const std::vector<StorePathWithOutputs> & targets,
|
void Store::queryMissing(const std::vector<StorePathWithOutputs> & targets,
|
||||||
StorePathSet & willBuild_, StorePathSet & willSubstitute_, StorePathSet & unknown_,
|
StorePathSet & willBuild_, StorePathSet & willSubstitute_, StorePathSet & unknown_,
|
||||||
unsigned long long & downloadSize_, unsigned long long & narSize_)
|
uint64_t & downloadSize_, uint64_t & narSize_)
|
||||||
{
|
{
|
||||||
Activity act(*logger, lvlDebug, actUnknown, "querying info about missing paths");
|
Activity act(*logger, lvlDebug, actUnknown, "querying info about missing paths");
|
||||||
|
|
||||||
|
@ -122,8 +132,8 @@ void Store::queryMissing(const std::vector<StorePathWithOutputs> & targets,
|
||||||
{
|
{
|
||||||
std::unordered_set<std::string> done;
|
std::unordered_set<std::string> done;
|
||||||
StorePathSet & unknown, & willSubstitute, & willBuild;
|
StorePathSet & unknown, & willSubstitute, & willBuild;
|
||||||
unsigned long long & downloadSize;
|
uint64_t & downloadSize;
|
||||||
unsigned long long & narSize;
|
uint64_t & narSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DrvState
|
struct DrvState
|
||||||
|
@ -157,7 +167,7 @@ void Store::queryMissing(const std::vector<StorePathWithOutputs> & targets,
|
||||||
auto outPath = parseStorePath(outPathS);
|
auto outPath = parseStorePath(outPathS);
|
||||||
|
|
||||||
SubstitutablePathInfos infos;
|
SubstitutablePathInfos infos;
|
||||||
querySubstitutablePathInfos({outPath}, infos);
|
querySubstitutablePathInfos({{outPath, getDerivationCA(*drv)}}, infos);
|
||||||
|
|
||||||
if (infos.empty()) {
|
if (infos.empty()) {
|
||||||
drvState_->lock()->done = true;
|
drvState_->lock()->done = true;
|
||||||
|
@ -214,7 +224,7 @@ void Store::queryMissing(const std::vector<StorePathWithOutputs> & targets,
|
||||||
if (isValidPath(path.path)) return;
|
if (isValidPath(path.path)) return;
|
||||||
|
|
||||||
SubstitutablePathInfos infos;
|
SubstitutablePathInfos infos;
|
||||||
querySubstitutablePathInfos({path.path}, infos);
|
querySubstitutablePathInfos({{path.path, std::nullopt}}, infos);
|
||||||
|
|
||||||
if (infos.empty()) {
|
if (infos.empty()) {
|
||||||
auto state(state_.lock());
|
auto state(state_.lock());
|
||||||
|
|
|
@ -79,14 +79,14 @@ struct NarAccessor : public FSAccessor
|
||||||
parents.top()->isExecutable = true;
|
parents.top()->isExecutable = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void preallocateContents(unsigned long long size) override
|
void preallocateContents(uint64_t size) override
|
||||||
{
|
{
|
||||||
assert(size <= std::numeric_limits<uint64_t>::max());
|
assert(size <= std::numeric_limits<uint64_t>::max());
|
||||||
parents.top()->size = (uint64_t) size;
|
parents.top()->size = (uint64_t) size;
|
||||||
parents.top()->start = pos;
|
parents.top()->start = pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
void receiveContents(unsigned char * data, unsigned int len) override
|
void receiveContents(unsigned char * data, size_t len) override
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
void createSymlink(const Path & path, const string & target) override
|
void createSymlink(const Path & path, const string & target) override
|
||||||
|
|
|
@ -282,7 +282,7 @@ void LocalStore::optimiseStore(OptimiseStats & stats)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static string showBytes(unsigned long long bytes)
|
static string showBytes(uint64_t bytes)
|
||||||
{
|
{
|
||||||
return (format("%.2f MiB") % (bytes / (1024.0 * 1024.0))).str();
|
return (format("%.2f MiB") % (bytes / (1024.0 * 1024.0))).str();
|
||||||
}
|
}
|
||||||
|
|
111
src/libstore/path-info.hh
Normal file
111
src/libstore/path-info.hh
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "path.hh"
|
||||||
|
#include "hash.hh"
|
||||||
|
#include "content-address.hh"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
|
class Store;
|
||||||
|
|
||||||
|
|
||||||
|
struct SubstitutablePathInfo
|
||||||
|
{
|
||||||
|
std::optional<StorePath> deriver;
|
||||||
|
StorePathSet references;
|
||||||
|
uint64_t downloadSize; /* 0 = unknown or inapplicable */
|
||||||
|
uint64_t narSize; /* 0 = unknown */
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::map<StorePath, SubstitutablePathInfo> SubstitutablePathInfos;
|
||||||
|
|
||||||
|
|
||||||
|
struct ValidPathInfo
|
||||||
|
{
|
||||||
|
StorePath path;
|
||||||
|
std::optional<StorePath> deriver;
|
||||||
|
// TODO document this
|
||||||
|
std::optional<Hash> narHash;
|
||||||
|
StorePathSet references;
|
||||||
|
time_t registrationTime = 0;
|
||||||
|
uint64_t narSize = 0; // 0 = unknown
|
||||||
|
uint64_t id; // internal use only
|
||||||
|
|
||||||
|
/* Whether the path is ultimately trusted, that is, it's a
|
||||||
|
derivation output that was built locally. */
|
||||||
|
bool ultimate = false;
|
||||||
|
|
||||||
|
StringSet sigs; // note: not necessarily verified
|
||||||
|
|
||||||
|
/* If non-empty, an assertion that the path is content-addressed,
|
||||||
|
i.e., that the store path is computed from a cryptographic hash
|
||||||
|
of the contents of the path, plus some other bits of data like
|
||||||
|
the "name" part of the path. Such a path doesn't need
|
||||||
|
signatures, since we don't have to trust anybody's claim that
|
||||||
|
the path is the output of a particular derivation. (In the
|
||||||
|
extensional store model, we have to trust that the *contents*
|
||||||
|
of an output path of a derivation were actually produced by
|
||||||
|
that derivation. In the intensional model, we have to trust
|
||||||
|
that a particular output path was produced by a derivation; the
|
||||||
|
path then implies the contents.)
|
||||||
|
|
||||||
|
Ideally, the content-addressability assertion would just be a Boolean,
|
||||||
|
and the store path would be computed from the name component, ‘narHash’
|
||||||
|
and ‘references’. However, we support many types of content addresses.
|
||||||
|
*/
|
||||||
|
std::optional<ContentAddress> ca;
|
||||||
|
|
||||||
|
bool operator == (const ValidPathInfo & i) const
|
||||||
|
{
|
||||||
|
return
|
||||||
|
path == i.path
|
||||||
|
&& narHash == i.narHash
|
||||||
|
&& references == i.references;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return a fingerprint of the store path to be used in binary
|
||||||
|
cache signatures. It contains the store path, the base-32
|
||||||
|
SHA-256 hash of the NAR serialisation of the path, the size of
|
||||||
|
the NAR, and the sorted references. The size field is strictly
|
||||||
|
speaking superfluous, but might prevent endless/excessive data
|
||||||
|
attacks. */
|
||||||
|
std::string fingerprint(const Store & store) const;
|
||||||
|
|
||||||
|
void sign(const Store & store, const SecretKey & secretKey);
|
||||||
|
|
||||||
|
/* Return true iff the path is verifiably content-addressed. */
|
||||||
|
bool isContentAddressed(const Store & store) const;
|
||||||
|
|
||||||
|
/* Functions to view references + hasSelfReference as one set, mainly for
|
||||||
|
compatibility's sake. */
|
||||||
|
StorePathSet referencesPossiblyToSelf() const;
|
||||||
|
void insertReferencePossiblyToSelf(StorePath && ref);
|
||||||
|
void setReferencesPossiblyToSelf(StorePathSet && refs);
|
||||||
|
|
||||||
|
static const size_t maxSigs = std::numeric_limits<size_t>::max();
|
||||||
|
|
||||||
|
/* Return the number of signatures on this .narinfo that were
|
||||||
|
produced by one of the specified keys, or maxSigs if the path
|
||||||
|
is content-addressed. */
|
||||||
|
size_t checkSignatures(const Store & store, const PublicKeys & publicKeys) const;
|
||||||
|
|
||||||
|
/* Verify a single signature. */
|
||||||
|
bool checkSignature(const Store & store, const PublicKeys & publicKeys, const std::string & sig) const;
|
||||||
|
|
||||||
|
Strings shortRefs() const;
|
||||||
|
|
||||||
|
ValidPathInfo(const ValidPathInfo & other) = default;
|
||||||
|
|
||||||
|
ValidPathInfo(StorePath && path) : path(std::move(path)) { };
|
||||||
|
ValidPathInfo(const StorePath & path) : path(path) { };
|
||||||
|
|
||||||
|
virtual ~ValidPathInfo() { }
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef list<ValidPathInfo> ValidPathInfos;
|
||||||
|
|
||||||
|
}
|
|
@ -64,6 +64,8 @@ typedef std::set<StorePath> StorePathSet;
|
||||||
typedef std::vector<StorePath> StorePaths;
|
typedef std::vector<StorePath> StorePaths;
|
||||||
typedef std::map<string, StorePath> OutputPathMap;
|
typedef std::map<string, StorePath> OutputPathMap;
|
||||||
|
|
||||||
|
typedef std::map<StorePath, std::optional<ContentAddress>> StorePathCAMap;
|
||||||
|
|
||||||
/* Extension of derivations in the Nix store. */
|
/* Extension of derivations in the Nix store. */
|
||||||
const std::string drvExtension = ".drv";
|
const std::string drvExtension = ".drv";
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,24 @@ void writeStorePaths(const Store & store, Sink & out, const StorePathSet & paths
|
||||||
out << store.printStorePath(i);
|
out << store.printStorePath(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StorePathCAMap readStorePathCAMap(const Store & store, Source & from)
|
||||||
|
{
|
||||||
|
StorePathCAMap paths;
|
||||||
|
auto count = readNum<size_t>(from);
|
||||||
|
while (count--)
|
||||||
|
paths.insert_or_assign(store.parseStorePath(readString(from)), parseContentAddressOpt(readString(from)));
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::map<string, StorePath> readOutputPathMap(const Store & store, Source & from)
|
std::map<string, StorePath> readOutputPathMap(const Store & store, Source & from)
|
||||||
{
|
{
|
||||||
std::map<string, StorePath> pathMap;
|
std::map<string, StorePath> pathMap;
|
||||||
|
@ -332,18 +350,17 @@ StorePathSet RemoteStore::querySubstitutablePaths(const StorePathSet & paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void RemoteStore::querySubstitutablePathInfos(const StorePathSet & paths,
|
void RemoteStore::querySubstitutablePathInfos(const StorePathCAMap & pathsMap, SubstitutablePathInfos & infos)
|
||||||
SubstitutablePathInfos & infos)
|
|
||||||
{
|
{
|
||||||
if (paths.empty()) return;
|
if (pathsMap.empty()) return;
|
||||||
|
|
||||||
auto conn(getConnection());
|
auto conn(getConnection());
|
||||||
|
|
||||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 12) {
|
if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 12) {
|
||||||
|
|
||||||
for (auto & i : paths) {
|
for (auto & i : pathsMap) {
|
||||||
SubstitutablePathInfo info;
|
SubstitutablePathInfo info;
|
||||||
conn->to << wopQuerySubstitutablePathInfo << printStorePath(i);
|
conn->to << wopQuerySubstitutablePathInfo << printStorePath(i.first);
|
||||||
conn.processStderr();
|
conn.processStderr();
|
||||||
unsigned int reply = readInt(conn->from);
|
unsigned int reply = readInt(conn->from);
|
||||||
if (reply == 0) continue;
|
if (reply == 0) continue;
|
||||||
|
@ -353,13 +370,19 @@ void RemoteStore::querySubstitutablePathInfos(const StorePathSet & paths,
|
||||||
info.references = readStorePaths<StorePathSet>(*this, conn->from);
|
info.references = readStorePaths<StorePathSet>(*this, conn->from);
|
||||||
info.downloadSize = readLongLong(conn->from);
|
info.downloadSize = readLongLong(conn->from);
|
||||||
info.narSize = readLongLong(conn->from);
|
info.narSize = readLongLong(conn->from);
|
||||||
infos.insert_or_assign(i, std::move(info));
|
infos.insert_or_assign(i.first, std::move(info));
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
conn->to << wopQuerySubstitutablePathInfos;
|
conn->to << wopQuerySubstitutablePathInfos;
|
||||||
|
if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 22) {
|
||||||
|
StorePathSet paths;
|
||||||
|
for (auto & path : pathsMap)
|
||||||
|
paths.insert(path.first);
|
||||||
writeStorePaths(*this, conn->to, paths);
|
writeStorePaths(*this, conn->to, paths);
|
||||||
|
} else
|
||||||
|
writeStorePathCAMap(*this, conn->to, pathsMap);
|
||||||
conn.processStderr();
|
conn.processStderr();
|
||||||
size_t count = readNum<size_t>(conn->from);
|
size_t count = readNum<size_t>(conn->from);
|
||||||
for (size_t n = 0; n < count; n++) {
|
for (size_t n = 0; n < count; n++) {
|
||||||
|
@ -503,9 +526,84 @@ void RemoteStore::addToStore(const ValidPathInfo & info, Source & source,
|
||||||
conn->to << info.registrationTime << info.narSize
|
conn->to << info.registrationTime << info.narSize
|
||||||
<< info.ultimate << info.sigs << renderContentAddress(info.ca)
|
<< info.ultimate << info.sigs << renderContentAddress(info.ca)
|
||||||
<< repair << !checkSigs;
|
<< repair << !checkSigs;
|
||||||
bool tunnel = GET_PROTOCOL_MINOR(conn->daemonVersion) >= 21;
|
|
||||||
if (!tunnel) copyNAR(source, conn->to);
|
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 23) {
|
||||||
conn.processStderr(0, tunnel ? &source : nullptr);
|
|
||||||
|
std::exception_ptr ex;
|
||||||
|
|
||||||
|
struct FramedSink : BufferedSink
|
||||||
|
{
|
||||||
|
ConnectionHandle & conn;
|
||||||
|
std::exception_ptr & ex;
|
||||||
|
|
||||||
|
FramedSink(ConnectionHandle & conn, std::exception_ptr & ex) : conn(conn), ex(ex)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
~FramedSink()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
conn->to << 0;
|
||||||
|
conn->to.flush();
|
||||||
|
} catch (...) {
|
||||||
|
ignoreException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(const unsigned char * data, size_t len) override
|
||||||
|
{
|
||||||
|
/* Don't send more data if the remote has
|
||||||
|
encountered an error. */
|
||||||
|
if (ex) {
|
||||||
|
auto ex2 = ex;
|
||||||
|
ex = nullptr;
|
||||||
|
std::rethrow_exception(ex2);
|
||||||
|
}
|
||||||
|
conn->to << len;
|
||||||
|
conn->to(data, len);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Handle log messages / exceptions from the remote on a
|
||||||
|
separate thread. */
|
||||||
|
std::thread stderrThread([&]()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
conn.processStderr();
|
||||||
|
} catch (...) {
|
||||||
|
ex = std::current_exception();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Finally joinStderrThread([&]()
|
||||||
|
{
|
||||||
|
if (stderrThread.joinable()) {
|
||||||
|
stderrThread.join();
|
||||||
|
if (ex) {
|
||||||
|
try {
|
||||||
|
std::rethrow_exception(ex);
|
||||||
|
} catch (...) {
|
||||||
|
ignoreException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
FramedSink sink(conn, ex);
|
||||||
|
copyNAR(source, sink);
|
||||||
|
sink.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
stderrThread.join();
|
||||||
|
if (ex)
|
||||||
|
std::rethrow_exception(ex);
|
||||||
|
|
||||||
|
} else if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 21) {
|
||||||
|
conn.processStderr(0, &source);
|
||||||
|
} else {
|
||||||
|
copyNAR(source, conn->to);
|
||||||
|
conn.processStderr(0, nullptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -707,7 +805,7 @@ void RemoteStore::addSignatures(const StorePath & storePath, const StringSet & s
|
||||||
|
|
||||||
void RemoteStore::queryMissing(const std::vector<StorePathWithOutputs> & targets,
|
void RemoteStore::queryMissing(const std::vector<StorePathWithOutputs> & targets,
|
||||||
StorePathSet & willBuild, StorePathSet & willSubstitute, StorePathSet & unknown,
|
StorePathSet & willBuild, StorePathSet & willSubstitute, StorePathSet & unknown,
|
||||||
unsigned long long & downloadSize, unsigned long long & narSize)
|
uint64_t & downloadSize, uint64_t & narSize)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
auto conn(getConnection());
|
auto conn(getConnection());
|
||||||
|
|
|
@ -56,7 +56,7 @@ public:
|
||||||
|
|
||||||
StorePathSet querySubstitutablePaths(const StorePathSet & paths) override;
|
StorePathSet querySubstitutablePaths(const StorePathSet & paths) override;
|
||||||
|
|
||||||
void querySubstitutablePathInfos(const StorePathSet & paths,
|
void querySubstitutablePathInfos(const StorePathCAMap & paths,
|
||||||
SubstitutablePathInfos & infos) override;
|
SubstitutablePathInfos & infos) override;
|
||||||
|
|
||||||
void addToStore(const ValidPathInfo & info, Source & nar,
|
void addToStore(const ValidPathInfo & info, Source & nar,
|
||||||
|
@ -94,7 +94,7 @@ public:
|
||||||
|
|
||||||
void queryMissing(const std::vector<StorePathWithOutputs> & targets,
|
void queryMissing(const std::vector<StorePathWithOutputs> & targets,
|
||||||
StorePathSet & willBuild, StorePathSet & willSubstitute, StorePathSet & unknown,
|
StorePathSet & willBuild, StorePathSet & willSubstitute, StorePathSet & unknown,
|
||||||
unsigned long long & downloadSize, unsigned long long & narSize) override;
|
uint64_t & downloadSize, uint64_t & narSize) override;
|
||||||
|
|
||||||
void connect() override;
|
void connect() override;
|
||||||
|
|
||||||
|
|
|
@ -266,6 +266,10 @@ struct S3BinaryCacheStoreImpl : public S3BinaryCacheStore
|
||||||
const std::string & mimeType,
|
const std::string & mimeType,
|
||||||
const std::string & contentEncoding)
|
const std::string & contentEncoding)
|
||||||
{
|
{
|
||||||
|
istream->seekg(0, istream->end);
|
||||||
|
auto size = istream->tellg();
|
||||||
|
istream->seekg(0, istream->beg);
|
||||||
|
|
||||||
auto maxThreads = std::thread::hardware_concurrency();
|
auto maxThreads = std::thread::hardware_concurrency();
|
||||||
|
|
||||||
static std::shared_ptr<Aws::Utils::Threading::PooledThreadExecutor>
|
static std::shared_ptr<Aws::Utils::Threading::PooledThreadExecutor>
|
||||||
|
@ -343,10 +347,11 @@ struct S3BinaryCacheStoreImpl : public S3BinaryCacheStore
|
||||||
std::chrono::duration_cast<std::chrono::milliseconds>(now2 - now1)
|
std::chrono::duration_cast<std::chrono::milliseconds>(now2 - now1)
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
printInfo("uploaded 's3://%s/%s' in %d ms",
|
printInfo("uploaded 's3://%s/%s' (%d bytes) in %d ms",
|
||||||
bucketName, path, duration);
|
bucketName, path, size, duration);
|
||||||
|
|
||||||
stats.putTimeMs += duration;
|
stats.putTimeMs += duration;
|
||||||
|
stats.putBytes += std::max(size, (decltype(size)) 0);
|
||||||
stats.put++;
|
stats.put++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ public:
|
||||||
struct Stats
|
struct Stats
|
||||||
{
|
{
|
||||||
std::atomic<uint64_t> put{0};
|
std::atomic<uint64_t> put{0};
|
||||||
|
std::atomic<uint64_t> putBytes{0};
|
||||||
std::atomic<uint64_t> putTimeMs{0};
|
std::atomic<uint64_t> putTimeMs{0};
|
||||||
std::atomic<uint64_t> get{0};
|
std::atomic<uint64_t> get{0};
|
||||||
std::atomic<uint64_t> getBytes{0};
|
std::atomic<uint64_t> getBytes{0};
|
||||||
|
|
|
@ -193,6 +193,23 @@ StorePath Store::makeFixedOutputPath(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME Put this somewhere?
|
||||||
|
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
|
||||||
|
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
|
||||||
|
|
||||||
|
StorePath Store::makeFixedOutputPathFromCA(std::string_view name, ContentAddress ca,
|
||||||
|
const StorePathSet & references, bool hasSelfReference) const
|
||||||
|
{
|
||||||
|
// New template
|
||||||
|
return std::visit(overloaded {
|
||||||
|
[&](TextHash th) {
|
||||||
|
return makeTextPath(name, th.hash, references);
|
||||||
|
},
|
||||||
|
[&](FixedOutputHash fsh) {
|
||||||
|
return makeFixedOutputPath(fsh.method, fsh.hash, name, references, hasSelfReference);
|
||||||
|
}
|
||||||
|
}, ca);
|
||||||
|
}
|
||||||
|
|
||||||
StorePath Store::makeTextPath(std::string_view name, const Hash & hash,
|
StorePath Store::makeTextPath(std::string_view name, const Hash & hash,
|
||||||
const StorePathSet & references) const
|
const StorePathSet & references) const
|
||||||
|
@ -222,6 +239,20 @@ StorePath Store::computeStorePathForText(const string & name, const string & s,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
StorePath Store::addToStore(const string & name, const Path & _srcPath,
|
||||||
|
FileIngestionMethod method, HashType hashAlgo, PathFilter & filter, RepairFlag repair)
|
||||||
|
{
|
||||||
|
Path srcPath(absPath(_srcPath));
|
||||||
|
auto source = sinkToSource([&](Sink & sink) {
|
||||||
|
if (method == FileIngestionMethod::Recursive)
|
||||||
|
dumpPath(srcPath, sink, filter);
|
||||||
|
else
|
||||||
|
readFile(srcPath, sink);
|
||||||
|
});
|
||||||
|
return addToStoreFromDump(*source, name, method, hashAlgo, repair);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The aim of this function is to compute in one pass the correct ValidPathInfo for
|
The aim of this function is to compute in one pass the correct ValidPathInfo for
|
||||||
the files that we are trying to add to the store. To accomplish that in one
|
the files that we are trying to add to the store. To accomplish that in one
|
||||||
|
@ -689,6 +720,15 @@ void copyStorePath(ref<Store> srcStore, ref<Store> dstStore,
|
||||||
|
|
||||||
uint64_t total = 0;
|
uint64_t total = 0;
|
||||||
|
|
||||||
|
// recompute store path on the chance dstStore does it differently
|
||||||
|
if (info->ca && info->references.empty()) {
|
||||||
|
auto info2 = make_ref<ValidPathInfo>(*info);
|
||||||
|
info2->path = dstStore->makeFixedOutputPathFromCA(info->path.name(), *info->ca);
|
||||||
|
if (dstStore->storeDir == srcStore->storeDir)
|
||||||
|
assert(info->path == info2->path);
|
||||||
|
info = info2;
|
||||||
|
}
|
||||||
|
|
||||||
if (!info->narHash) {
|
if (!info->narHash) {
|
||||||
StringSink sink;
|
StringSink sink;
|
||||||
srcStore->narFromPath({storePath}, sink);
|
srcStore->narFromPath({storePath}, sink);
|
||||||
|
@ -724,16 +764,20 @@ void copyStorePath(ref<Store> srcStore, ref<Store> dstStore,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void copyPaths(ref<Store> srcStore, ref<Store> dstStore, const StorePathSet & storePaths,
|
std::map<StorePath, StorePath> copyPaths(ref<Store> srcStore, ref<Store> dstStore, const StorePathSet & storePaths,
|
||||||
RepairFlag repair, CheckSigsFlag checkSigs, SubstituteFlag substitute)
|
RepairFlag repair, CheckSigsFlag checkSigs, SubstituteFlag substitute)
|
||||||
{
|
{
|
||||||
auto valid = dstStore->queryValidPaths(storePaths, substitute);
|
auto valid = dstStore->queryValidPaths(storePaths, substitute);
|
||||||
|
|
||||||
PathSet missing;
|
StorePathSet missing;
|
||||||
for (auto & path : storePaths)
|
for (auto & path : storePaths)
|
||||||
if (!valid.count(path)) missing.insert(srcStore->printStorePath(path));
|
if (!valid.count(path)) missing.insert(path);
|
||||||
|
|
||||||
if (missing.empty()) return;
|
std::map<StorePath, StorePath> pathsMap;
|
||||||
|
for (auto & path : storePaths)
|
||||||
|
pathsMap.insert_or_assign(path, path);
|
||||||
|
|
||||||
|
if (missing.empty()) return pathsMap;
|
||||||
|
|
||||||
Activity act(*logger, lvlInfo, actCopyPaths, fmt("copying %d paths", missing.size()));
|
Activity act(*logger, lvlInfo, actCopyPaths, fmt("copying %d paths", missing.size()));
|
||||||
|
|
||||||
|
@ -748,30 +792,49 @@ void copyPaths(ref<Store> srcStore, ref<Store> dstStore, const StorePathSet & st
|
||||||
|
|
||||||
ThreadPool pool;
|
ThreadPool pool;
|
||||||
|
|
||||||
processGraph<Path>(pool,
|
processGraph<StorePath>(pool,
|
||||||
PathSet(missing.begin(), missing.end()),
|
StorePathSet(missing.begin(), missing.end()),
|
||||||
|
|
||||||
[&](const Path & storePath) {
|
[&](const StorePath & storePath) {
|
||||||
if (dstStore->isValidPath(dstStore->parseStorePath(storePath))) {
|
auto info = srcStore->queryPathInfo(storePath);
|
||||||
|
auto storePathForDst = storePath;
|
||||||
|
if (info->ca && info->references.empty()) {
|
||||||
|
storePathForDst = dstStore->makeFixedOutputPathFromCA(storePath.name(), *info->ca);
|
||||||
|
if (dstStore->storeDir == srcStore->storeDir)
|
||||||
|
assert(storePathForDst == storePath);
|
||||||
|
if (storePathForDst != storePath)
|
||||||
|
debug("replaced path '%s' to '%s' for substituter '%s'", srcStore->printStorePath(storePath), dstStore->printStorePath(storePathForDst), dstStore->getUri());
|
||||||
|
}
|
||||||
|
pathsMap.insert_or_assign(storePath, storePathForDst);
|
||||||
|
|
||||||
|
if (dstStore->isValidPath(storePath)) {
|
||||||
nrDone++;
|
nrDone++;
|
||||||
showProgress();
|
showProgress();
|
||||||
return PathSet();
|
return StorePathSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto info = srcStore->queryPathInfo(srcStore->parseStorePath(storePath));
|
|
||||||
|
|
||||||
bytesExpected += info->narSize;
|
bytesExpected += info->narSize;
|
||||||
act.setExpected(actCopyPath, bytesExpected);
|
act.setExpected(actCopyPath, bytesExpected);
|
||||||
|
|
||||||
return srcStore->printStorePathSet(info->references);
|
return info->references;
|
||||||
},
|
},
|
||||||
|
|
||||||
[&](const Path & storePathS) {
|
[&](const StorePath & storePath) {
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
|
|
||||||
auto storePath = dstStore->parseStorePath(storePathS);
|
auto info = srcStore->queryPathInfo(storePath);
|
||||||
|
|
||||||
if (!dstStore->isValidPath(storePath)) {
|
auto storePathForDst = storePath;
|
||||||
|
if (info->ca && info->references.empty()) {
|
||||||
|
storePathForDst = dstStore->makeFixedOutputPathFromCA(storePath.name(), *info->ca);
|
||||||
|
if (dstStore->storeDir == srcStore->storeDir)
|
||||||
|
assert(storePathForDst == storePath);
|
||||||
|
if (storePathForDst != storePath)
|
||||||
|
debug("replaced path '%s' to '%s' for substituter '%s'", srcStore->printStorePath(storePath), dstStore->printStorePath(storePathForDst), dstStore->getUri());
|
||||||
|
}
|
||||||
|
pathsMap.insert_or_assign(storePath, storePathForDst);
|
||||||
|
|
||||||
|
if (!dstStore->isValidPath(storePathForDst)) {
|
||||||
MaintainCount<decltype(nrRunning)> mc(nrRunning);
|
MaintainCount<decltype(nrRunning)> mc(nrRunning);
|
||||||
showProgress();
|
showProgress();
|
||||||
try {
|
try {
|
||||||
|
@ -780,7 +843,7 @@ void copyPaths(ref<Store> srcStore, ref<Store> dstStore, const StorePathSet & st
|
||||||
nrFailed++;
|
nrFailed++;
|
||||||
if (!settings.keepGoing)
|
if (!settings.keepGoing)
|
||||||
throw e;
|
throw e;
|
||||||
logger->log(lvlError, fmt("could not copy %s: %s", storePathS, e.what()));
|
logger->log(lvlError, fmt("could not copy %s: %s", dstStore->printStorePath(storePath), e.what()));
|
||||||
showProgress();
|
showProgress();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -789,6 +852,8 @@ void copyPaths(ref<Store> srcStore, ref<Store> dstStore, const StorePathSet & st
|
||||||
nrDone++;
|
nrDone++;
|
||||||
showProgress();
|
showProgress();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return pathsMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
#include "config.hh"
|
#include "config.hh"
|
||||||
#include "derivations.hh"
|
#include "derivations.hh"
|
||||||
|
#include "path-info.hh"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
@ -85,7 +86,7 @@ struct GCOptions
|
||||||
StorePathSet pathsToDelete;
|
StorePathSet pathsToDelete;
|
||||||
|
|
||||||
/* Stop after at least `maxFreed' bytes have been freed. */
|
/* Stop after at least `maxFreed' bytes have been freed. */
|
||||||
unsigned long long maxFreed{std::numeric_limits<unsigned long long>::max()};
|
uint64_t maxFreed{std::numeric_limits<uint64_t>::max()};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,99 +98,10 @@ struct GCResults
|
||||||
|
|
||||||
/* For `gcReturnDead', `gcDeleteDead' and `gcDeleteSpecific', the
|
/* For `gcReturnDead', `gcDeleteDead' and `gcDeleteSpecific', the
|
||||||
number of bytes that would be or was freed. */
|
number of bytes that would be or was freed. */
|
||||||
unsigned long long bytesFreed = 0;
|
uint64_t bytesFreed = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
struct SubstitutablePathInfo
|
|
||||||
{
|
|
||||||
std::optional<StorePath> deriver;
|
|
||||||
StorePathSet references;
|
|
||||||
unsigned long long downloadSize; /* 0 = unknown or inapplicable */
|
|
||||||
unsigned long long narSize; /* 0 = unknown */
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::map<StorePath, SubstitutablePathInfo> SubstitutablePathInfos;
|
|
||||||
|
|
||||||
struct ValidPathInfo
|
|
||||||
{
|
|
||||||
StorePath path;
|
|
||||||
std::optional<StorePath> deriver;
|
|
||||||
// TODO document this
|
|
||||||
std::optional<Hash> narHash;
|
|
||||||
StorePathSet references;
|
|
||||||
time_t registrationTime = 0;
|
|
||||||
uint64_t narSize = 0; // 0 = unknown
|
|
||||||
uint64_t id; // internal use only
|
|
||||||
|
|
||||||
/* Whether the path is ultimately trusted, that is, it's a
|
|
||||||
derivation output that was built locally. */
|
|
||||||
bool ultimate = false;
|
|
||||||
|
|
||||||
StringSet sigs; // note: not necessarily verified
|
|
||||||
|
|
||||||
/* If non-empty, an assertion that the path is content-addressed,
|
|
||||||
i.e., that the store path is computed from a cryptographic hash
|
|
||||||
of the contents of the path, plus some other bits of data like
|
|
||||||
the "name" part of the path. Such a path doesn't need
|
|
||||||
signatures, since we don't have to trust anybody's claim that
|
|
||||||
the path is the output of a particular derivation. (In the
|
|
||||||
extensional store model, we have to trust that the *contents*
|
|
||||||
of an output path of a derivation were actually produced by
|
|
||||||
that derivation. In the intensional model, we have to trust
|
|
||||||
that a particular output path was produced by a derivation; the
|
|
||||||
path then implies the contents.)
|
|
||||||
|
|
||||||
Ideally, the content-addressability assertion would just be a Boolean,
|
|
||||||
and the store path would be computed from the name component, ‘narHash’
|
|
||||||
and ‘references’. However, we support many types of content addresses.
|
|
||||||
*/
|
|
||||||
std::optional<ContentAddress> ca;
|
|
||||||
|
|
||||||
bool operator == (const ValidPathInfo & i) const
|
|
||||||
{
|
|
||||||
return
|
|
||||||
path == i.path
|
|
||||||
&& narHash == i.narHash
|
|
||||||
&& references == i.references;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Return a fingerprint of the store path to be used in binary
|
|
||||||
cache signatures. It contains the store path, the base-32
|
|
||||||
SHA-256 hash of the NAR serialisation of the path, the size of
|
|
||||||
the NAR, and the sorted references. The size field is strictly
|
|
||||||
speaking superfluous, but might prevent endless/excessive data
|
|
||||||
attacks. */
|
|
||||||
std::string fingerprint(const Store & store) const;
|
|
||||||
|
|
||||||
void sign(const Store & store, const SecretKey & secretKey);
|
|
||||||
|
|
||||||
/* Return true iff the path is verifiably content-addressed. */
|
|
||||||
bool isContentAddressed(const Store & store) const;
|
|
||||||
|
|
||||||
static const size_t maxSigs = std::numeric_limits<size_t>::max();
|
|
||||||
|
|
||||||
/* Return the number of signatures on this .narinfo that were
|
|
||||||
produced by one of the specified keys, or maxSigs if the path
|
|
||||||
is content-addressed. */
|
|
||||||
size_t checkSignatures(const Store & store, const PublicKeys & publicKeys) const;
|
|
||||||
|
|
||||||
/* Verify a single signature. */
|
|
||||||
bool checkSignature(const Store & store, const PublicKeys & publicKeys, const std::string & sig) const;
|
|
||||||
|
|
||||||
Strings shortRefs() const;
|
|
||||||
|
|
||||||
ValidPathInfo(const ValidPathInfo & other) = default;
|
|
||||||
|
|
||||||
ValidPathInfo(StorePath && path) : path(std::move(path)) { };
|
|
||||||
ValidPathInfo(const StorePath & path) : path(path) { };
|
|
||||||
|
|
||||||
virtual ~ValidPathInfo() { }
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef list<ValidPathInfo> ValidPathInfos;
|
|
||||||
|
|
||||||
|
|
||||||
enum BuildMode { bmNormal, bmRepair, bmCheck };
|
enum BuildMode { bmNormal, bmRepair, bmCheck };
|
||||||
|
|
||||||
|
|
||||||
|
@ -344,7 +256,11 @@ public:
|
||||||
bool hasSelfReference = false) const;
|
bool hasSelfReference = false) const;
|
||||||
|
|
||||||
StorePath makeTextPath(std::string_view name, const Hash & hash,
|
StorePath makeTextPath(std::string_view name, const Hash & hash,
|
||||||
const StorePathSet & references) const;
|
const StorePathSet & references = {}) const;
|
||||||
|
|
||||||
|
StorePath makeFixedOutputPathFromCA(std::string_view name, ContentAddress ca,
|
||||||
|
const StorePathSet & references = {},
|
||||||
|
bool hasSelfReference = false) const;
|
||||||
|
|
||||||
/* This is the preparatory part of addToStore(); it computes the
|
/* This is the preparatory part of addToStore(); it computes the
|
||||||
store path to which srcPath is to be copied. Returns the store
|
store path to which srcPath is to be copied. Returns the store
|
||||||
|
@ -436,9 +352,10 @@ public:
|
||||||
virtual StorePathSet querySubstitutablePaths(const StorePathSet & paths) { return {}; };
|
virtual StorePathSet querySubstitutablePaths(const StorePathSet & paths) { return {}; };
|
||||||
|
|
||||||
/* Query substitute info (i.e. references, derivers and download
|
/* Query substitute info (i.e. references, derivers and download
|
||||||
sizes) of a set of paths. If a path does not have substitute
|
sizes) of a map of paths to their optional ca values. If a path
|
||||||
info, it's omitted from the resulting ‘infos’ map. */
|
does not have substitute info, it's omitted from the resulting
|
||||||
virtual void querySubstitutablePathInfos(const StorePathSet & paths,
|
‘infos’ map. */
|
||||||
|
virtual void querySubstitutablePathInfos(const StorePathCAMap & paths,
|
||||||
SubstitutablePathInfos & infos) { return; };
|
SubstitutablePathInfos & infos) { return; };
|
||||||
|
|
||||||
/* Import a path into the store. */
|
/* Import a path into the store. */
|
||||||
|
@ -451,7 +368,7 @@ public:
|
||||||
libutil/archive.hh). */
|
libutil/archive.hh). */
|
||||||
virtual StorePath addToStore(const string & name, const Path & srcPath,
|
virtual StorePath addToStore(const string & name, const Path & srcPath,
|
||||||
FileIngestionMethod method = FileIngestionMethod::Recursive, HashType hashAlgo = htSHA256,
|
FileIngestionMethod method = FileIngestionMethod::Recursive, HashType hashAlgo = htSHA256,
|
||||||
PathFilter & filter = defaultPathFilter, RepairFlag repair = NoRepair) = 0;
|
PathFilter & filter = defaultPathFilter, RepairFlag repair = NoRepair);
|
||||||
|
|
||||||
/* Copy the contents of a path to the store and register the
|
/* Copy the contents of a path to the store and register the
|
||||||
validity the resulting path, using a constant amount of
|
validity the resulting path, using a constant amount of
|
||||||
|
@ -460,6 +377,10 @@ public:
|
||||||
FileIngestionMethod method = FileIngestionMethod::Recursive, HashType hashAlgo = htSHA256,
|
FileIngestionMethod method = FileIngestionMethod::Recursive, HashType hashAlgo = htSHA256,
|
||||||
std::optional<Hash> expectedCAHash = {});
|
std::optional<Hash> expectedCAHash = {});
|
||||||
|
|
||||||
|
/* Like addToStore(), but the contents of the path are contained
|
||||||
|
in `dump', which is either a NAR serialisation (if recursive ==
|
||||||
|
true) or simply the contents of a regular file (if recursive ==
|
||||||
|
false). */
|
||||||
// FIXME: remove?
|
// FIXME: remove?
|
||||||
virtual StorePath addToStoreFromDump(Source & dump, const string & name,
|
virtual StorePath addToStoreFromDump(Source & dump, const string & name,
|
||||||
FileIngestionMethod method = FileIngestionMethod::Recursive, HashType hashAlgo = htSHA256, RepairFlag repair = NoRepair)
|
FileIngestionMethod method = FileIngestionMethod::Recursive, HashType hashAlgo = htSHA256, RepairFlag repair = NoRepair)
|
||||||
|
@ -610,7 +531,7 @@ public:
|
||||||
that will be substituted. */
|
that will be substituted. */
|
||||||
virtual void queryMissing(const std::vector<StorePathWithOutputs> & targets,
|
virtual void queryMissing(const std::vector<StorePathWithOutputs> & targets,
|
||||||
StorePathSet & willBuild, StorePathSet & willSubstitute, StorePathSet & unknown,
|
StorePathSet & willBuild, StorePathSet & willSubstitute, StorePathSet & unknown,
|
||||||
unsigned long long & downloadSize, unsigned long long & narSize);
|
uint64_t & downloadSize, uint64_t & narSize);
|
||||||
|
|
||||||
/* Sort a set of paths topologically under the references
|
/* Sort a set of paths topologically under the references
|
||||||
relation. If p refers to q, then p precedes q in this list. */
|
relation. If p refers to q, then p precedes q in this list. */
|
||||||
|
@ -740,11 +661,13 @@ void copyStorePath(ref<Store> srcStore, ref<Store> dstStore,
|
||||||
|
|
||||||
|
|
||||||
/* Copy store paths from one store to another. The paths may be copied
|
/* Copy store paths from one store to another. The paths may be copied
|
||||||
in parallel. They are copied in a topologically sorted order
|
in parallel. They are copied in a topologically sorted order (i.e.
|
||||||
(i.e. if A is a reference of B, then A is copied before B), but
|
if A is a reference of B, then A is copied before B), but the set
|
||||||
the set of store paths is not automatically closed; use
|
of store paths is not automatically closed; use copyClosure() for
|
||||||
copyClosure() for that. */
|
that. Returns a map of what each path was copied to the dstStore
|
||||||
void copyPaths(ref<Store> srcStore, ref<Store> dstStore, const StorePathSet & storePaths,
|
as. */
|
||||||
|
std::map<StorePath, StorePath> copyPaths(ref<Store> srcStore, ref<Store> dstStore,
|
||||||
|
const StorePathSet & storePaths,
|
||||||
RepairFlag repair = NoRepair,
|
RepairFlag repair = NoRepair,
|
||||||
CheckSigsFlag checkSigs = CheckSigs,
|
CheckSigsFlag checkSigs = CheckSigs,
|
||||||
SubstituteFlag substitute = NoSubstitute);
|
SubstituteFlag substitute = NoSubstitute);
|
||||||
|
@ -843,4 +766,6 @@ std::optional<ValidPathInfo> decodeValidPathInfo(
|
||||||
/* Split URI into protocol+hierarchy part and its parameter set. */
|
/* Split URI into protocol+hierarchy part and its parameter set. */
|
||||||
std::pair<std::string, Store::Params> splitUriAndParams(const std::string & uri);
|
std::pair<std::string, Store::Params> splitUriAndParams(const std::string & uri);
|
||||||
|
|
||||||
|
std::optional<ContentAddress> getDerivationCA(const BasicDerivation & drv);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ namespace nix {
|
||||||
#define WORKER_MAGIC_1 0x6e697863
|
#define WORKER_MAGIC_1 0x6e697863
|
||||||
#define WORKER_MAGIC_2 0x6478696f
|
#define WORKER_MAGIC_2 0x6478696f
|
||||||
|
|
||||||
#define PROTOCOL_VERSION 0x116
|
#define PROTOCOL_VERSION 0x117
|
||||||
#define GET_PROTOCOL_MAJOR(x) ((x) & 0xff00)
|
#define GET_PROTOCOL_MAJOR(x) ((x) & 0xff00)
|
||||||
#define GET_PROTOCOL_MINOR(x) ((x) & 0x00ff)
|
#define GET_PROTOCOL_MINOR(x) ((x) & 0x00ff)
|
||||||
|
|
||||||
|
@ -70,6 +70,10 @@ template<class T> T readStorePaths(const Store & store, Source & from);
|
||||||
|
|
||||||
void writeStorePaths(const Store & store, Sink & out, const StorePathSet & paths);
|
void writeStorePaths(const Store & store, Sink & out, const StorePathSet & paths);
|
||||||
|
|
||||||
|
StorePathCAMap readStorePathCAMap(const Store & store, Source & from);
|
||||||
|
|
||||||
|
void writeStorePathCAMap(const Store & store, Sink & out, const StorePathCAMap & paths);
|
||||||
|
|
||||||
void writeOutputPathMap(const Store & store, Sink & out, const OutputPathMap & paths);
|
void writeOutputPathMap(const Store & store, Sink & out, const OutputPathMap & paths);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,17 +150,17 @@ static void skipGeneric(Source & source)
|
||||||
|
|
||||||
static void parseContents(ParseSink & sink, Source & source, const Path & path)
|
static void parseContents(ParseSink & sink, Source & source, const Path & path)
|
||||||
{
|
{
|
||||||
unsigned long long size = readLongLong(source);
|
uint64_t size = readLongLong(source);
|
||||||
|
|
||||||
sink.preallocateContents(size);
|
sink.preallocateContents(size);
|
||||||
|
|
||||||
unsigned long long left = size;
|
uint64_t left = size;
|
||||||
std::vector<unsigned char> buf(65536);
|
std::vector<unsigned char> buf(65536);
|
||||||
|
|
||||||
while (left) {
|
while (left) {
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
auto n = buf.size();
|
auto n = buf.size();
|
||||||
if ((unsigned long long)n > left) n = left;
|
if ((uint64_t)n > left) n = left;
|
||||||
source(buf.data(), n);
|
source(buf.data(), n);
|
||||||
sink.receiveContents(buf.data(), n);
|
sink.receiveContents(buf.data(), n);
|
||||||
left -= n;
|
left -= n;
|
||||||
|
@ -323,7 +323,7 @@ struct RestoreSink : ParseSink
|
||||||
throw SysError("fchmod");
|
throw SysError("fchmod");
|
||||||
}
|
}
|
||||||
|
|
||||||
void preallocateContents(unsigned long long len)
|
void preallocateContents(uint64_t len)
|
||||||
{
|
{
|
||||||
#if HAVE_POSIX_FALLOCATE
|
#if HAVE_POSIX_FALLOCATE
|
||||||
if (len) {
|
if (len) {
|
||||||
|
@ -338,7 +338,7 @@ struct RestoreSink : ParseSink
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void receiveContents(unsigned char * data, unsigned int len)
|
void receiveContents(unsigned char * data, size_t len)
|
||||||
{
|
{
|
||||||
writeFull(fd.get(), data, len);
|
writeFull(fd.get(), data, len);
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,8 +57,8 @@ struct ParseSink
|
||||||
|
|
||||||
virtual void createRegularFile(const Path & path) { };
|
virtual void createRegularFile(const Path & path) { };
|
||||||
virtual void isExecutable() { };
|
virtual void isExecutable() { };
|
||||||
virtual void preallocateContents(unsigned long long size) { };
|
virtual void preallocateContents(uint64_t size) { };
|
||||||
virtual void receiveContents(unsigned char * data, unsigned int len) { };
|
virtual void receiveContents(unsigned char * data, size_t len) { };
|
||||||
|
|
||||||
virtual void createSymlink(const Path & path, const string & target) { };
|
virtual void createSymlink(const Path & path, const string & target) { };
|
||||||
};
|
};
|
||||||
|
@ -77,7 +77,7 @@ struct RetrieveRegularNARSink : ParseSink
|
||||||
regular = false;
|
regular = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void receiveContents(unsigned char * data, unsigned int len)
|
void receiveContents(unsigned char * data, size_t len)
|
||||||
{
|
{
|
||||||
sink(data, len);
|
sink(data, len);
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,7 +111,7 @@ Hash hashFile(HashType ht, const Path & path);
|
||||||
|
|
||||||
/* Compute the hash of the given path. The hash is defined as
|
/* Compute the hash of the given path. The hash is defined as
|
||||||
(essentially) hashString(ht, dumpPath(path)). */
|
(essentially) hashString(ht, dumpPath(path)). */
|
||||||
typedef std::pair<Hash, unsigned long long> HashResult;
|
typedef std::pair<Hash, uint64_t> HashResult;
|
||||||
HashResult hashPath(HashType ht, const Path & path,
|
HashResult hashPath(HashType ht, const Path & path,
|
||||||
PathFilter & filter = defaultPathFilter);
|
PathFilter & filter = defaultPathFilter);
|
||||||
|
|
||||||
|
@ -141,7 +141,7 @@ class HashSink : public BufferedSink, public AbstractHashSink
|
||||||
private:
|
private:
|
||||||
HashType ht;
|
HashType ht;
|
||||||
Ctx * ctx;
|
Ctx * ctx;
|
||||||
unsigned long long bytes;
|
uint64_t bytes;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
HashSink(HashType ht);
|
HashSink(HashType ht);
|
||||||
|
|
|
@ -312,14 +312,14 @@ T readNum(Source & source)
|
||||||
source(buf, sizeof(buf));
|
source(buf, sizeof(buf));
|
||||||
|
|
||||||
uint64_t n =
|
uint64_t n =
|
||||||
((unsigned long long) buf[0]) |
|
((uint64_t) buf[0]) |
|
||||||
((unsigned long long) buf[1] << 8) |
|
((uint64_t) buf[1] << 8) |
|
||||||
((unsigned long long) buf[2] << 16) |
|
((uint64_t) buf[2] << 16) |
|
||||||
((unsigned long long) buf[3] << 24) |
|
((uint64_t) buf[3] << 24) |
|
||||||
((unsigned long long) buf[4] << 32) |
|
((uint64_t) buf[4] << 32) |
|
||||||
((unsigned long long) buf[5] << 40) |
|
((uint64_t) buf[5] << 40) |
|
||||||
((unsigned long long) buf[6] << 48) |
|
((uint64_t) buf[6] << 48) |
|
||||||
((unsigned long long) buf[7] << 56);
|
((uint64_t) buf[7] << 56);
|
||||||
|
|
||||||
if (n > std::numeric_limits<T>::max())
|
if (n > std::numeric_limits<T>::max())
|
||||||
throw SerialisationError("serialised integer %d is too large for type '%s'", n, typeid(T).name());
|
throw SerialisationError("serialised integer %d is too large for type '%s'", n, typeid(T).name());
|
||||||
|
|
|
@ -374,7 +374,7 @@ void writeLine(int fd, string s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void _deletePath(int parentfd, const Path & path, unsigned long long & bytesFreed)
|
static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed)
|
||||||
{
|
{
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
|
|
||||||
|
@ -414,7 +414,7 @@ static void _deletePath(int parentfd, const Path & path, unsigned long long & by
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _deletePath(const Path & path, unsigned long long & bytesFreed)
|
static void _deletePath(const Path & path, uint64_t & bytesFreed)
|
||||||
{
|
{
|
||||||
Path dir = dirOf(path);
|
Path dir = dirOf(path);
|
||||||
if (dir == "")
|
if (dir == "")
|
||||||
|
@ -435,12 +435,12 @@ static void _deletePath(const Path & path, unsigned long long & bytesFreed)
|
||||||
|
|
||||||
void deletePath(const Path & path)
|
void deletePath(const Path & path)
|
||||||
{
|
{
|
||||||
unsigned long long dummy;
|
uint64_t dummy;
|
||||||
deletePath(path, dummy);
|
deletePath(path, dummy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void deletePath(const Path & path, unsigned long long & bytesFreed)
|
void deletePath(const Path & path, uint64_t & bytesFreed)
|
||||||
{
|
{
|
||||||
//Activity act(*logger, lvlDebug, format("recursively deleting path '%1%'") % path);
|
//Activity act(*logger, lvlDebug, format("recursively deleting path '%1%'") % path);
|
||||||
bytesFreed = 0;
|
bytesFreed = 0;
|
||||||
|
@ -494,6 +494,7 @@ std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix)
|
||||||
{
|
{
|
||||||
Path tmpl(getEnv("TMPDIR").value_or("/tmp") + "/" + prefix + ".XXXXXX");
|
Path tmpl(getEnv("TMPDIR").value_or("/tmp") + "/" + prefix + ".XXXXXX");
|
||||||
// Strictly speaking, this is UB, but who cares...
|
// Strictly speaking, this is UB, but who cares...
|
||||||
|
// FIXME: use O_TMPFILE.
|
||||||
AutoCloseFD fd(mkstemp((char *) tmpl.c_str()));
|
AutoCloseFD fd(mkstemp((char *) tmpl.c_str()));
|
||||||
if (!fd)
|
if (!fd)
|
||||||
throw SysError("creating temporary file '%s'", tmpl);
|
throw SysError("creating temporary file '%s'", tmpl);
|
||||||
|
|
|
@ -125,7 +125,7 @@ void writeLine(int fd, string s);
|
||||||
second variant returns the number of bytes and blocks freed. */
|
second variant returns the number of bytes and blocks freed. */
|
||||||
void deletePath(const Path & path);
|
void deletePath(const Path & path);
|
||||||
|
|
||||||
void deletePath(const Path & path, unsigned long long & bytesFreed);
|
void deletePath(const Path & path, uint64_t & bytesFreed);
|
||||||
|
|
||||||
std::string getUserName();
|
std::string getUserName();
|
||||||
|
|
||||||
|
|
|
@ -174,7 +174,7 @@ static void _main(int argc, char * * argv)
|
||||||
else if (*arg == "--run-env") // obsolete
|
else if (*arg == "--run-env") // obsolete
|
||||||
runEnv = true;
|
runEnv = true;
|
||||||
|
|
||||||
else if (*arg == "--command" || *arg == "--run") {
|
else if (runEnv && (*arg == "--command" || *arg == "--run")) {
|
||||||
if (*arg == "--run")
|
if (*arg == "--run")
|
||||||
interactive = false;
|
interactive = false;
|
||||||
envCommand = getArg(*arg, arg, end) + "\nexit";
|
envCommand = getArg(*arg, arg, end) + "\nexit";
|
||||||
|
@ -192,7 +192,7 @@ static void _main(int argc, char * * argv)
|
||||||
else if (*arg == "--pure") pure = true;
|
else if (*arg == "--pure") pure = true;
|
||||||
else if (*arg == "--impure") pure = false;
|
else if (*arg == "--impure") pure = false;
|
||||||
|
|
||||||
else if (*arg == "--packages" || *arg == "-p")
|
else if (runEnv && (*arg == "--packages" || *arg == "-p"))
|
||||||
packages = true;
|
packages = true;
|
||||||
|
|
||||||
else if (inShebang && *arg == "-i") {
|
else if (inShebang && *arg == "-i") {
|
||||||
|
@ -325,7 +325,7 @@ static void _main(int argc, char * * argv)
|
||||||
auto buildPaths = [&](const std::vector<StorePathWithOutputs> & paths) {
|
auto buildPaths = [&](const std::vector<StorePathWithOutputs> & paths) {
|
||||||
/* Note: we do this even when !printMissing to efficiently
|
/* Note: we do this even when !printMissing to efficiently
|
||||||
fetch binary cache data. */
|
fetch binary cache data. */
|
||||||
unsigned long long downloadSize, narSize;
|
uint64_t downloadSize, narSize;
|
||||||
StorePathSet willBuild, willSubstitute, unknown;
|
StorePathSet willBuild, willSubstitute, unknown;
|
||||||
store->queryMissing(paths,
|
store->queryMissing(paths,
|
||||||
willBuild, willSubstitute, unknown, downloadSize, narSize);
|
willBuild, willSubstitute, unknown, downloadSize, narSize);
|
||||||
|
|
|
@ -67,10 +67,8 @@ static int _main(int argc, char * * argv)
|
||||||
deleteOlderThan = getArg(*arg, arg, end);
|
deleteOlderThan = getArg(*arg, arg, end);
|
||||||
}
|
}
|
||||||
else if (*arg == "--dry-run") dryRun = true;
|
else if (*arg == "--dry-run") dryRun = true;
|
||||||
else if (*arg == "--max-freed") {
|
else if (*arg == "--max-freed")
|
||||||
long long maxFreed = getIntArg<long long>(*arg, arg, end, true);
|
options.maxFreed = std::max(getIntArg<int64_t>(*arg, arg, end, true), (int64_t) 0);
|
||||||
options.maxFreed = maxFreed >= 0 ? maxFreed : 0;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -130,7 +130,7 @@ static void opRealise(Strings opFlags, Strings opArgs)
|
||||||
for (auto & i : opArgs)
|
for (auto & i : opArgs)
|
||||||
paths.push_back(store->followLinksToStorePathWithOutputs(i));
|
paths.push_back(store->followLinksToStorePathWithOutputs(i));
|
||||||
|
|
||||||
unsigned long long downloadSize, narSize;
|
uint64_t downloadSize, narSize;
|
||||||
StorePathSet willBuild, willSubstitute, unknown;
|
StorePathSet willBuild, willSubstitute, unknown;
|
||||||
store->queryMissing(paths, willBuild, willSubstitute, unknown, downloadSize, narSize);
|
store->queryMissing(paths, willBuild, willSubstitute, unknown, downloadSize, narSize);
|
||||||
|
|
||||||
|
@ -572,10 +572,8 @@ static void opGC(Strings opFlags, Strings opArgs)
|
||||||
if (*i == "--print-roots") printRoots = true;
|
if (*i == "--print-roots") printRoots = true;
|
||||||
else if (*i == "--print-live") options.action = GCOptions::gcReturnLive;
|
else if (*i == "--print-live") options.action = GCOptions::gcReturnLive;
|
||||||
else if (*i == "--print-dead") options.action = GCOptions::gcReturnDead;
|
else if (*i == "--print-dead") options.action = GCOptions::gcReturnDead;
|
||||||
else if (*i == "--max-freed") {
|
else if (*i == "--max-freed")
|
||||||
long long maxFreed = getIntArg<long long>(*i, i, opFlags.end(), true);
|
options.maxFreed = std::max(getIntArg<int64_t>(*i, i, opFlags.end(), true), (int64_t) 0);
|
||||||
options.maxFreed = maxFreed >= 0 ? maxFreed : 0;
|
|
||||||
}
|
|
||||||
else throw UsageError("bad sub-operation '%1%' in GC", *i);
|
else throw UsageError("bad sub-operation '%1%' in GC", *i);
|
||||||
|
|
||||||
if (!opArgs.empty()) throw UsageError("no arguments expected");
|
if (!opArgs.empty()) throw UsageError("no arguments expected");
|
||||||
|
@ -831,7 +829,7 @@ static void opServe(Strings opFlags, Strings opArgs)
|
||||||
for (auto & path : paths)
|
for (auto & path : paths)
|
||||||
if (!path.isDerivation())
|
if (!path.isDerivation())
|
||||||
paths2.push_back({path});
|
paths2.push_back({path});
|
||||||
unsigned long long downloadSize, narSize;
|
uint64_t downloadSize, narSize;
|
||||||
StorePathSet willBuild, willSubstitute, unknown;
|
StorePathSet willBuild, willSubstitute, unknown;
|
||||||
store->queryMissing(paths2,
|
store->queryMissing(paths2,
|
||||||
willBuild, willSubstitute, unknown, downloadSize, narSize);
|
willBuild, willSubstitute, unknown, downloadSize, narSize);
|
||||||
|
|
|
@ -9,6 +9,7 @@ struct CmdAddToStore : MixDryRun, StoreCommand
|
||||||
{
|
{
|
||||||
Path path;
|
Path path;
|
||||||
std::optional<std::string> namePart;
|
std::optional<std::string> namePart;
|
||||||
|
FileIngestionMethod ingestionMethod = FileIngestionMethod::Recursive;
|
||||||
|
|
||||||
CmdAddToStore()
|
CmdAddToStore()
|
||||||
{
|
{
|
||||||
|
@ -21,6 +22,13 @@ struct CmdAddToStore : MixDryRun, StoreCommand
|
||||||
.labels = {"name"},
|
.labels = {"name"},
|
||||||
.handler = {&namePart},
|
.handler = {&namePart},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addFlag({
|
||||||
|
.longName = "flat",
|
||||||
|
.shortName = 0,
|
||||||
|
.description = "add flat file to the Nix store",
|
||||||
|
.handler = {&ingestionMethod, FileIngestionMethod::Flat},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string description() override
|
std::string description() override
|
||||||
|
@ -45,12 +53,19 @@ struct CmdAddToStore : MixDryRun, StoreCommand
|
||||||
|
|
||||||
auto narHash = hashString(htSHA256, *sink.s);
|
auto narHash = hashString(htSHA256, *sink.s);
|
||||||
|
|
||||||
ValidPathInfo info(store->makeFixedOutputPath(FileIngestionMethod::Recursive, narHash, *namePart));
|
Hash hash = narHash;
|
||||||
|
if (ingestionMethod == FileIngestionMethod::Flat) {
|
||||||
|
HashSink hsink(htSHA256);
|
||||||
|
readFile(path, hsink);
|
||||||
|
hash = hsink.finish().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidPathInfo info(store->makeFixedOutputPath(ingestionMethod, hash, *namePart));
|
||||||
info.narHash = narHash;
|
info.narHash = narHash;
|
||||||
info.narSize = sink.s->size();
|
info.narSize = sink.s->size();
|
||||||
info.ca = std::optional { FixedOutputHash {
|
info.ca = std::optional { FixedOutputHash {
|
||||||
.method = FileIngestionMethod::Recursive,
|
.method = ingestionMethod,
|
||||||
.hash = *info.narHash,
|
.hash = hash,
|
||||||
} };
|
} };
|
||||||
|
|
||||||
if (!dryRun) {
|
if (!dryRun) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ using namespace nix;
|
||||||
struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile
|
struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile
|
||||||
{
|
{
|
||||||
Path outLink = "result";
|
Path outLink = "result";
|
||||||
|
BuildMode buildMode = bmNormal;
|
||||||
|
|
||||||
CmdBuild()
|
CmdBuild()
|
||||||
{
|
{
|
||||||
|
@ -26,6 +27,12 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile
|
||||||
.description = "do not create a symlink to the build result",
|
.description = "do not create a symlink to the build result",
|
||||||
.handler = {&outLink, Path("")},
|
.handler = {&outLink, Path("")},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addFlag({
|
||||||
|
.longName = "rebuild",
|
||||||
|
.description = "rebuild an already built package and compare the result to the existing store paths",
|
||||||
|
.handler = {&buildMode, bmCheck},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string description() override
|
std::string description() override
|
||||||
|
@ -53,7 +60,7 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile
|
||||||
|
|
||||||
void run(ref<Store> store) override
|
void run(ref<Store> store) override
|
||||||
{
|
{
|
||||||
auto buildables = build(store, dryRun ? Realise::Nothing : Realise::Outputs, installables);
|
auto buildables = build(store, dryRun ? Realise::Nothing : Realise::Outputs, installables, buildMode);
|
||||||
|
|
||||||
if (dryRun) return;
|
if (dryRun) return;
|
||||||
|
|
||||||
|
|
129
src/nix/bundle.cc
Normal file
129
src/nix/bundle.cc
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
#include "command.hh"
|
||||||
|
#include "common-args.hh"
|
||||||
|
#include "shared.hh"
|
||||||
|
#include "store-api.hh"
|
||||||
|
#include "fs-accessor.hh"
|
||||||
|
|
||||||
|
using namespace nix;
|
||||||
|
|
||||||
|
struct CmdBundle : InstallableCommand
|
||||||
|
{
|
||||||
|
std::string bundler = "github:matthewbauer/nix-bundle";
|
||||||
|
std::optional<Path> outLink;
|
||||||
|
|
||||||
|
CmdBundle()
|
||||||
|
{
|
||||||
|
addFlag({
|
||||||
|
.longName = "bundler",
|
||||||
|
.description = "use custom bundler",
|
||||||
|
.labels = {"flake-url"},
|
||||||
|
.handler = {&bundler},
|
||||||
|
.completer = {[&](size_t, std::string_view prefix) {
|
||||||
|
completeFlakeRef(getStore(), prefix);
|
||||||
|
}}
|
||||||
|
});
|
||||||
|
|
||||||
|
addFlag({
|
||||||
|
.longName = "out-link",
|
||||||
|
.shortName = 'o',
|
||||||
|
.description = "path of the symlink to the build result",
|
||||||
|
.labels = {"path"},
|
||||||
|
.handler = {&outLink},
|
||||||
|
.completer = completePath
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string description() override
|
||||||
|
{
|
||||||
|
return "bundle an application so that it works outside of the Nix store";
|
||||||
|
}
|
||||||
|
|
||||||
|
Examples examples() override
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
Example{
|
||||||
|
"To bundle Hello:",
|
||||||
|
"nix bundle hello"
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Category category() override { return catSecondary; }
|
||||||
|
|
||||||
|
Strings getDefaultFlakeAttrPaths() override
|
||||||
|
{
|
||||||
|
Strings res{"defaultApp." + settings.thisSystem.get()};
|
||||||
|
for (auto & s : SourceExprCommand::getDefaultFlakeAttrPaths())
|
||||||
|
res.push_back(s);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
Strings getDefaultFlakeAttrPathPrefixes() override
|
||||||
|
{
|
||||||
|
Strings res{"apps." + settings.thisSystem.get() + ".", "packages"};
|
||||||
|
for (auto & s : SourceExprCommand::getDefaultFlakeAttrPathPrefixes())
|
||||||
|
res.push_back(s);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void run(ref<Store> store) override
|
||||||
|
{
|
||||||
|
auto evalState = getEvalState();
|
||||||
|
|
||||||
|
auto app = installable->toApp(*evalState);
|
||||||
|
store->buildPaths(app.context);
|
||||||
|
|
||||||
|
auto [bundlerFlakeRef, bundlerName] = parseFlakeRefWithFragment(bundler, absPath("."));
|
||||||
|
const flake::LockFlags lockFlags{ .writeLockFile = false };
|
||||||
|
auto bundler = InstallableFlake(
|
||||||
|
evalState, std::move(bundlerFlakeRef),
|
||||||
|
Strings{bundlerName == "" ? "defaultBundler" : bundlerName},
|
||||||
|
Strings({"bundlers."}), lockFlags);
|
||||||
|
|
||||||
|
Value * arg = evalState->allocValue();
|
||||||
|
evalState->mkAttrs(*arg, 2);
|
||||||
|
|
||||||
|
PathSet context;
|
||||||
|
for (auto & i : app.context)
|
||||||
|
context.insert("=" + store->printStorePath(i.path));
|
||||||
|
mkString(*evalState->allocAttr(*arg, evalState->symbols.create("program")), app.program, context);
|
||||||
|
|
||||||
|
mkString(*evalState->allocAttr(*arg, evalState->symbols.create("system")), settings.thisSystem.get());
|
||||||
|
|
||||||
|
arg->attrs->sort();
|
||||||
|
|
||||||
|
auto vRes = evalState->allocValue();
|
||||||
|
evalState->callFunction(*bundler.toValue(*evalState).first, *arg, *vRes, noPos);
|
||||||
|
|
||||||
|
if (!evalState->isDerivation(*vRes))
|
||||||
|
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
|
||||||
|
|
||||||
|
auto attr1 = vRes->attrs->find(evalState->sDrvPath);
|
||||||
|
if (!attr1)
|
||||||
|
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
|
||||||
|
|
||||||
|
PathSet context2;
|
||||||
|
StorePath drvPath = store->parseStorePath(evalState->coerceToPath(*attr1->pos, *attr1->value, context2));
|
||||||
|
|
||||||
|
auto attr2 = vRes->attrs->find(evalState->sOutPath);
|
||||||
|
if (!attr2)
|
||||||
|
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
|
||||||
|
|
||||||
|
StorePath outPath = store->parseStorePath(evalState->coerceToPath(*attr2->pos, *attr2->value, context2));
|
||||||
|
|
||||||
|
store->buildPaths({{drvPath}});
|
||||||
|
|
||||||
|
auto outPathS = store->printStorePath(outPath);
|
||||||
|
|
||||||
|
auto info = store->queryPathInfo(outPath);
|
||||||
|
if (!info->references.empty())
|
||||||
|
throw Error("'%s' has references; a bundler must not leave any references", outPathS);
|
||||||
|
|
||||||
|
if (!outLink)
|
||||||
|
outLink = baseNameOf(app.program);
|
||||||
|
|
||||||
|
store.dynamic_pointer_cast<LocalFSStore>()->addPermRoot(outPath, absPath(*outLink), true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static auto r2 = registerCommand<CmdBundle>("bundle");
|
|
@ -5,6 +5,7 @@
|
||||||
#include "common-eval-args.hh"
|
#include "common-eval-args.hh"
|
||||||
#include "path.hh"
|
#include "path.hh"
|
||||||
#include "flake/lockfile.hh"
|
#include "flake/lockfile.hh"
|
||||||
|
#include "store-api.hh"
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
|
@ -185,7 +186,7 @@ static RegisterCommand registerCommand(const std::string & name)
|
||||||
}
|
}
|
||||||
|
|
||||||
Buildables build(ref<Store> store, Realise mode,
|
Buildables build(ref<Store> store, Realise mode,
|
||||||
std::vector<std::shared_ptr<Installable>> installables);
|
std::vector<std::shared_ptr<Installable>> installables, BuildMode bMode = bmNormal);
|
||||||
|
|
||||||
std::set<StorePath> toStorePaths(ref<Store> store,
|
std::set<StorePath> toStorePaths(ref<Store> store,
|
||||||
Realise mode, OperateOn operateOn,
|
Realise mode, OperateOn operateOn,
|
||||||
|
|
|
@ -68,22 +68,22 @@ BuildEnvironment readEnvironment(const Path & path)
|
||||||
|
|
||||||
std::smatch match;
|
std::smatch match;
|
||||||
|
|
||||||
if (std::regex_search(pos, file.cend(), match, declareRegex)) {
|
if (std::regex_search(pos, file.cend(), match, declareRegex, std::regex_constants::match_continuous)) {
|
||||||
pos = match[0].second;
|
pos = match[0].second;
|
||||||
exported.insert(match[1]);
|
exported.insert(match[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (std::regex_search(pos, file.cend(), match, varRegex)) {
|
else if (std::regex_search(pos, file.cend(), match, varRegex, std::regex_constants::match_continuous)) {
|
||||||
pos = match[0].second;
|
pos = match[0].second;
|
||||||
res.env.insert({match[1], Var { .exported = exported.count(match[1]) > 0, .value = match[2] }});
|
res.env.insert({match[1], Var { .exported = exported.count(match[1]) > 0, .value = match[2] }});
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (std::regex_search(pos, file.cend(), match, assocArrayRegex)) {
|
else if (std::regex_search(pos, file.cend(), match, assocArrayRegex, std::regex_constants::match_continuous)) {
|
||||||
pos = match[0].second;
|
pos = match[0].second;
|
||||||
res.env.insert({match[1], Var { .associative = true, .value = match[2] }});
|
res.env.insert({match[1], Var { .associative = true, .value = match[2] }});
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (std::regex_search(pos, file.cend(), match, functionRegex)) {
|
else if (std::regex_search(pos, file.cend(), match, functionRegex, std::regex_constants::match_continuous)) {
|
||||||
res.bashFunctions = std::string(pos, file.cend());
|
res.bashFunctions = std::string(pos, file.cend());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -368,6 +368,21 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto checkBundler = [&](const std::string & attrPath, Value & v, const Pos & pos) {
|
||||||
|
try {
|
||||||
|
state->forceValue(v, pos);
|
||||||
|
if (v.type != tLambda)
|
||||||
|
throw Error("bundler must be a function");
|
||||||
|
if (!v.lambda.fun->formals ||
|
||||||
|
v.lambda.fun->formals->argNames.find(state->symbols.create("program")) == v.lambda.fun->formals->argNames.end() ||
|
||||||
|
v.lambda.fun->formals->argNames.find(state->symbols.create("system")) == v.lambda.fun->formals->argNames.end())
|
||||||
|
throw Error("bundler must take formal arguments 'program' and 'system'");
|
||||||
|
} catch (Error & e) {
|
||||||
|
e.addTrace(pos, hintfmt("while checking the template '%s'", attrPath));
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
Activity act(*logger, lvlInfo, actUnknown, "evaluating flake");
|
Activity act(*logger, lvlInfo, actUnknown, "evaluating flake");
|
||||||
|
|
||||||
|
@ -490,6 +505,16 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
*attr.value, *attr.pos);
|
*attr.value, *attr.pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (name == "defaultBundler")
|
||||||
|
checkBundler(name, vOutput, pos);
|
||||||
|
|
||||||
|
else if (name == "bundlers") {
|
||||||
|
state->forceAttrs(vOutput, pos);
|
||||||
|
for (auto & attr : *vOutput.attrs)
|
||||||
|
checkBundler(fmt("%s.%s", name, attr.name),
|
||||||
|
*attr.value, *attr.pos);
|
||||||
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
warn("unknown flake output '%s'", name);
|
warn("unknown flake output '%s'", name);
|
||||||
|
|
||||||
|
|
|
@ -645,7 +645,7 @@ std::shared_ptr<Installable> SourceExprCommand::parseInstallable(
|
||||||
}
|
}
|
||||||
|
|
||||||
Buildables build(ref<Store> store, Realise mode,
|
Buildables build(ref<Store> store, Realise mode,
|
||||||
std::vector<std::shared_ptr<Installable>> installables)
|
std::vector<std::shared_ptr<Installable>> installables, BuildMode bMode)
|
||||||
{
|
{
|
||||||
if (mode == Realise::Nothing)
|
if (mode == Realise::Nothing)
|
||||||
settings.readOnlyMode = true;
|
settings.readOnlyMode = true;
|
||||||
|
@ -671,7 +671,7 @@ Buildables build(ref<Store> store, Realise mode,
|
||||||
if (mode == Realise::Nothing)
|
if (mode == Realise::Nothing)
|
||||||
printMissing(store, pathsToBuild, lvlError);
|
printMissing(store, pathsToBuild, lvlError);
|
||||||
else if (mode == Realise::Outputs)
|
else if (mode == Realise::Outputs)
|
||||||
store->buildPaths(pathsToBuild);
|
store->buildPaths(pathsToBuild, bMode);
|
||||||
|
|
||||||
return buildables;
|
return buildables;
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ struct CmdPathInfo : StorePathsCommand, MixJSON
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void printSize(unsigned long long value)
|
void printSize(uint64_t value)
|
||||||
{
|
{
|
||||||
if (!humanReadable) {
|
if (!humanReadable) {
|
||||||
std::cout << fmt("\t%11d", value);
|
std::cout << fmt("\t%11d", value);
|
||||||
|
|
|
@ -218,7 +218,9 @@ outPath=$(nix-build --no-out-link -E '
|
||||||
|
|
||||||
nix copy --to file://$cacheDir?write-nar-listing=1 $outPath
|
nix copy --to file://$cacheDir?write-nar-listing=1 $outPath
|
||||||
|
|
||||||
[[ $(cat $cacheDir/$(basename $outPath).ls) = '{"version":1,"root":{"type":"directory","entries":{"bar":{"type":"regular","size":4,"narOffset":232},"link":{"type":"symlink","target":"xyzzy"}}}}' ]]
|
diff -u \
|
||||||
|
<(jq -S < $cacheDir/$(basename $outPath).ls) \
|
||||||
|
<(echo '{"version":1,"root":{"type":"directory","entries":{"bar":{"type":"regular","size":4,"narOffset":232},"link":{"type":"symlink","target":"xyzzy"}}}}' | jq -S)
|
||||||
|
|
||||||
|
|
||||||
# Test debug info index generation.
|
# Test debug info index generation.
|
||||||
|
@ -234,4 +236,6 @@ outPath=$(nix-build --no-out-link -E '
|
||||||
|
|
||||||
nix copy --to "file://$cacheDir?index-debug-info=1&compression=none" $outPath
|
nix copy --to "file://$cacheDir?index-debug-info=1&compression=none" $outPath
|
||||||
|
|
||||||
[[ $(cat $cacheDir/debuginfo/02623eda209c26a59b1a8638ff7752f6b945c26b.debug) = '{"archive":"../nar/100vxs724qr46phz8m24iswmg9p3785hsyagz0kchf6q6gf06sw6.nar","member":"lib/debug/.build-id/02/623eda209c26a59b1a8638ff7752f6b945c26b.debug"}' ]]
|
diff -u \
|
||||||
|
<(cat $cacheDir/debuginfo/02623eda209c26a59b1a8638ff7752f6b945c26b.debug | jq -S) \
|
||||||
|
<(echo '{"archive":"../nar/100vxs724qr46phz8m24iswmg9p3785hsyagz0kchf6q6gf06sw6.nar","member":"lib/debug/.build-id/02/623eda209c26a59b1a8638ff7752f6b945c26b.debug"}' | jq -S)
|
||||||
|
|
|
@ -61,30 +61,30 @@ nix-build check.nix -A nondeterministic --no-out-link --repeat 1 2> $TEST_ROOT/l
|
||||||
[ "$status" = "1" ]
|
[ "$status" = "1" ]
|
||||||
grep 'differs from previous round' $TEST_ROOT/log
|
grep 'differs from previous round' $TEST_ROOT/log
|
||||||
|
|
||||||
path=$(nix-build check.nix -A fetchurl --no-out-link --hashed-mirrors '')
|
path=$(nix-build check.nix -A fetchurl --no-out-link)
|
||||||
|
|
||||||
chmod +w $path
|
chmod +w $path
|
||||||
echo foo > $path
|
echo foo > $path
|
||||||
chmod -w $path
|
chmod -w $path
|
||||||
|
|
||||||
nix-build check.nix -A fetchurl --no-out-link --check --hashed-mirrors ''
|
nix-build check.nix -A fetchurl --no-out-link --check
|
||||||
# Note: "check" doesn't repair anything, it just compares to the hash stored in the database.
|
# Note: "check" doesn't repair anything, it just compares to the hash stored in the database.
|
||||||
[[ $(cat $path) = foo ]]
|
[[ $(cat $path) = foo ]]
|
||||||
|
|
||||||
nix-build check.nix -A fetchurl --no-out-link --repair --hashed-mirrors ''
|
nix-build check.nix -A fetchurl --no-out-link --repair
|
||||||
[[ $(cat $path) != foo ]]
|
[[ $(cat $path) != foo ]]
|
||||||
|
|
||||||
nix-build check.nix -A hashmismatch --no-out-link --hashed-mirrors '' || status=$?
|
nix-build check.nix -A hashmismatch --no-out-link || status=$?
|
||||||
[ "$status" = "102" ]
|
[ "$status" = "102" ]
|
||||||
|
|
||||||
echo -n > ./dummy
|
echo -n > ./dummy
|
||||||
nix-build check.nix -A hashmismatch --no-out-link --hashed-mirrors ''
|
nix-build check.nix -A hashmismatch --no-out-link
|
||||||
echo 'Hello World' > ./dummy
|
echo 'Hello World' > ./dummy
|
||||||
|
|
||||||
nix-build check.nix -A hashmismatch --no-out-link --check --hashed-mirrors '' || status=$?
|
nix-build check.nix -A hashmismatch --no-out-link --check || status=$?
|
||||||
[ "$status" = "102" ]
|
[ "$status" = "102" ]
|
||||||
|
|
||||||
# Multiple failures with --keep-going
|
# Multiple failures with --keep-going
|
||||||
nix-build check.nix -A nondeterministic --no-out-link
|
nix-build check.nix -A nondeterministic --no-out-link
|
||||||
nix-build check.nix -A nondeterministic -A hashmismatch --no-out-link --check --keep-going --hashed-mirrors '' || status=$?
|
nix-build check.nix -A nondeterministic -A hashmismatch --no-out-link --check --keep-going || status=$?
|
||||||
[ "$status" = "110" ]
|
[ "$status" = "110" ]
|
||||||
|
|
|
@ -32,6 +32,8 @@ rev2=$(git -C $repo rev-parse HEAD)
|
||||||
# Fetch a worktree
|
# Fetch a worktree
|
||||||
unset _NIX_FORCE_HTTP
|
unset _NIX_FORCE_HTTP
|
||||||
path0=$(nix eval --impure --raw --expr "(builtins.fetchGit file://$TEST_ROOT/worktree).outPath")
|
path0=$(nix eval --impure --raw --expr "(builtins.fetchGit file://$TEST_ROOT/worktree).outPath")
|
||||||
|
path0_=$(nix eval --impure --raw --expr "(builtins.fetchTree { type = \"git\"; url = file://$TEST_ROOT/worktree; }).outPath")
|
||||||
|
[[ $path0 = $path0_ ]]
|
||||||
export _NIX_FORCE_HTTP=1
|
export _NIX_FORCE_HTTP=1
|
||||||
[[ $(tail -n 1 $path0/hello) = "hello" ]]
|
[[ $(tail -n 1 $path0/hello) = "hello" ]]
|
||||||
|
|
||||||
|
@ -102,6 +104,12 @@ git -C $repo commit -m 'Bla3' -a
|
||||||
path4=$(nix eval --impure --refresh --raw --expr "(builtins.fetchGit file://$repo).outPath")
|
path4=$(nix eval --impure --refresh --raw --expr "(builtins.fetchGit file://$repo).outPath")
|
||||||
[[ $path2 = $path4 ]]
|
[[ $path2 = $path4 ]]
|
||||||
|
|
||||||
|
nix eval --impure --raw --expr "(builtins.fetchGit { url = $repo; rev = \"$rev2\"; narHash = \"sha256-B5yIPHhEm0eysJKEsO7nqxprh9vcblFxpJG11gXJus1=\"; }).outPath" || status=$?
|
||||||
|
[[ "$status" = "102" ]]
|
||||||
|
|
||||||
|
path5=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = $repo; rev = \"$rev2\"; narHash = \"sha256-Hr8g6AqANb3xqX28eu1XnjK/3ab8Gv6TJSnkb1LezG9=\"; }).outPath")
|
||||||
|
[[ $path = $path5 ]]
|
||||||
|
|
||||||
# tarball-ttl should be ignored if we specify a rev
|
# tarball-ttl should be ignored if we specify a rev
|
||||||
echo delft > $repo/hello
|
echo delft > $repo/hello
|
||||||
git -C $repo add hello
|
git -C $repo add hello
|
||||||
|
|
|
@ -5,7 +5,7 @@ clearStore
|
||||||
# Test fetching a flat file.
|
# Test fetching a flat file.
|
||||||
hash=$(nix-hash --flat --type sha256 ./fetchurl.sh)
|
hash=$(nix-hash --flat --type sha256 ./fetchurl.sh)
|
||||||
|
|
||||||
outPath=$(nix-build '<nix/fetchurl.nix>' --argstr url file://$(pwd)/fetchurl.sh --argstr sha256 $hash --no-out-link --hashed-mirrors '')
|
outPath=$(nix-build '<nix/fetchurl.nix>' --argstr url file://$(pwd)/fetchurl.sh --argstr sha256 $hash --no-out-link)
|
||||||
|
|
||||||
cmp $outPath fetchurl.sh
|
cmp $outPath fetchurl.sh
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ clearStore
|
||||||
|
|
||||||
hash=$(nix hash-file --type sha512 --base64 ./fetchurl.sh)
|
hash=$(nix hash-file --type sha512 --base64 ./fetchurl.sh)
|
||||||
|
|
||||||
outPath=$(nix-build '<nix/fetchurl.nix>' --argstr url file://$(pwd)/fetchurl.sh --argstr sha512 $hash --no-out-link --hashed-mirrors '')
|
outPath=$(nix-build '<nix/fetchurl.nix>' --argstr url file://$(pwd)/fetchurl.sh --argstr sha512 $hash --no-out-link)
|
||||||
|
|
||||||
cmp $outPath fetchurl.sh
|
cmp $outPath fetchurl.sh
|
||||||
|
|
||||||
|
@ -25,26 +25,24 @@ hash=$(nix hash-file ./fetchurl.sh)
|
||||||
|
|
||||||
[[ $hash =~ ^sha256- ]]
|
[[ $hash =~ ^sha256- ]]
|
||||||
|
|
||||||
outPath=$(nix-build '<nix/fetchurl.nix>' --argstr url file://$(pwd)/fetchurl.sh --argstr hash $hash --no-out-link --hashed-mirrors '')
|
outPath=$(nix-build '<nix/fetchurl.nix>' --argstr url file://$(pwd)/fetchurl.sh --argstr hash $hash --no-out-link)
|
||||||
|
|
||||||
cmp $outPath fetchurl.sh
|
cmp $outPath fetchurl.sh
|
||||||
|
|
||||||
# Test the hashed mirror feature.
|
# Test that we can substitute from a different store dir.
|
||||||
clearStore
|
clearStore
|
||||||
|
|
||||||
hash=$(nix hash-file --type sha512 --base64 ./fetchurl.sh)
|
other_store=file://$TEST_ROOT/other_store?store=/fnord/store
|
||||||
hash32=$(nix hash-file --type sha512 --base16 ./fetchurl.sh)
|
|
||||||
|
|
||||||
mirror=$TEST_ROOT/hashed-mirror
|
hash=$(nix hash-file --type sha256 --base16 ./fetchurl.sh)
|
||||||
rm -rf $mirror
|
|
||||||
mkdir -p $mirror/sha512
|
|
||||||
ln -s $(pwd)/fetchurl.sh $mirror/sha512/$hash32
|
|
||||||
|
|
||||||
outPath=$(nix-build '<nix/fetchurl.nix>' --argstr url file:///no-such-dir/fetchurl.sh --argstr sha512 $hash --no-out-link --hashed-mirrors "file://$mirror")
|
storePath=$(nix --store $other_store add-to-store --flat ./fetchurl.sh)
|
||||||
|
|
||||||
|
outPath=$(nix-build '<nix/fetchurl.nix>' --argstr url file:///no-such-dir/fetchurl.sh --argstr sha256 $hash --no-out-link --substituters $other_store)
|
||||||
|
|
||||||
# Test hashed mirrors with an SRI hash.
|
# Test hashed mirrors with an SRI hash.
|
||||||
nix-build '<nix/fetchurl.nix>' --argstr url file:///no-such-dir/fetchurl.sh --argstr hash $(nix to-sri --type sha512 $hash) \
|
nix-build '<nix/fetchurl.nix>' --argstr url file:///no-such-dir/fetchurl.sh --argstr hash $(nix to-sri --type sha256 $hash) \
|
||||||
--argstr name bla --no-out-link --hashed-mirrors "file://$mirror"
|
--no-out-link --substituters $other_store
|
||||||
|
|
||||||
# Test unpacking a NAR.
|
# Test unpacking a NAR.
|
||||||
rm -rf $TEST_ROOT/archive
|
rm -rf $TEST_ROOT/archive
|
||||||
|
|
|
@ -10,10 +10,16 @@ touch $TEST_ROOT/filterin/bak
|
||||||
touch $TEST_ROOT/filterin/bla.c.bak
|
touch $TEST_ROOT/filterin/bla.c.bak
|
||||||
ln -s xyzzy $TEST_ROOT/filterin/link
|
ln -s xyzzy $TEST_ROOT/filterin/link
|
||||||
|
|
||||||
nix-build ./filter-source.nix -o $TEST_ROOT/filterout
|
checkFilter() {
|
||||||
|
test ! -e $1/foo/bar
|
||||||
|
test -e $1/xyzzy
|
||||||
|
test -e $1/bak
|
||||||
|
test ! -e $1/bla.c.bak
|
||||||
|
test ! -L $1/link
|
||||||
|
}
|
||||||
|
|
||||||
test ! -e $TEST_ROOT/filterout/foo/bar
|
nix-build ./filter-source.nix -o $TEST_ROOT/filterout1
|
||||||
test -e $TEST_ROOT/filterout/xyzzy
|
checkFilter $TEST_ROOT/filterout1
|
||||||
test -e $TEST_ROOT/filterout/bak
|
|
||||||
test ! -e $TEST_ROOT/filterout/bla.c.bak
|
nix-build ./path.nix -o $TEST_ROOT/filterout2
|
||||||
test ! -L $TEST_ROOT/filterout/link
|
checkFilter $TEST_ROOT/filterout2
|
||||||
|
|
|
@ -26,12 +26,24 @@ nix cat-store $storePath/foo/baz > baz.cat-nar
|
||||||
diff -u baz.cat-nar $storePath/foo/baz
|
diff -u baz.cat-nar $storePath/foo/baz
|
||||||
|
|
||||||
# Test --json.
|
# Test --json.
|
||||||
[[ $(nix ls-nar --json $narFile /) = '{"type":"directory","entries":{"foo":{},"foo-x":{},"qux":{},"zyx":{}}}' ]]
|
diff -u \
|
||||||
[[ $(nix ls-nar --json -R $narFile /foo) = '{"type":"directory","entries":{"bar":{"type":"regular","size":0,"narOffset":368},"baz":{"type":"regular","size":0,"narOffset":552},"data":{"type":"regular","size":58,"narOffset":736}}}' ]]
|
<(nix ls-nar --json $narFile / | jq -S) \
|
||||||
[[ $(nix ls-nar --json -R $narFile /foo/bar) = '{"type":"regular","size":0,"narOffset":368}' ]]
|
<(echo '{"type":"directory","entries":{"foo":{},"foo-x":{},"qux":{},"zyx":{}}}' | jq -S)
|
||||||
[[ $(nix ls-store --json $storePath) = '{"type":"directory","entries":{"foo":{},"foo-x":{},"qux":{},"zyx":{}}}' ]]
|
diff -u \
|
||||||
[[ $(nix ls-store --json -R $storePath/foo) = '{"type":"directory","entries":{"bar":{"type":"regular","size":0},"baz":{"type":"regular","size":0},"data":{"type":"regular","size":58}}}' ]]
|
<(nix ls-nar --json -R $narFile /foo | jq -S) \
|
||||||
[[ $(nix ls-store --json -R $storePath/foo/bar) = '{"type":"regular","size":0}' ]]
|
<(echo '{"type":"directory","entries":{"bar":{"type":"regular","size":0,"narOffset":368},"baz":{"type":"regular","size":0,"narOffset":552},"data":{"type":"regular","size":58,"narOffset":736}}}' | jq -S)
|
||||||
|
diff -u \
|
||||||
|
<(nix ls-nar --json -R $narFile /foo/bar | jq -S) \
|
||||||
|
<(echo '{"type":"regular","size":0,"narOffset":368}' | jq -S)
|
||||||
|
diff -u \
|
||||||
|
<(nix ls-store --json $storePath | jq -S) \
|
||||||
|
<(echo '{"type":"directory","entries":{"foo":{},"foo-x":{},"qux":{},"zyx":{}}}' | jq -S)
|
||||||
|
diff -u \
|
||||||
|
<(nix ls-store --json -R $storePath/foo | jq -S) \
|
||||||
|
<(echo '{"type":"directory","entries":{"bar":{"type":"regular","size":0},"baz":{"type":"regular","size":0},"data":{"type":"regular","size":58}}}' | jq -S)
|
||||||
|
diff -u \
|
||||||
|
<(nix ls-store --json -R $storePath/foo/bar| jq -S) \
|
||||||
|
<(echo '{"type":"regular","size":0}' | jq -S)
|
||||||
|
|
||||||
# Test missing files.
|
# Test missing files.
|
||||||
nix ls-store --json -R $storePath/xyzzy 2>&1 | grep 'does not exist in NAR'
|
nix ls-store --json -R $storePath/xyzzy 2>&1 | grep 'does not exist in NAR'
|
||||||
|
|
14
tests/path.nix
Normal file
14
tests/path.nix
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
with import ./config.nix;
|
||||||
|
|
||||||
|
mkDerivation {
|
||||||
|
name = "filter";
|
||||||
|
builder = builtins.toFile "builder" "ln -s $input $out";
|
||||||
|
input =
|
||||||
|
builtins.path {
|
||||||
|
path = ((builtins.getEnv "TEST_ROOT") + "/filterin");
|
||||||
|
filter = path: type:
|
||||||
|
type != "symlink"
|
||||||
|
&& baseNameOf path != "foo"
|
||||||
|
&& !((import ./lang/lib.nix).hasSuffix ".bak" (baseNameOf path));
|
||||||
|
};
|
||||||
|
}
|
|
@ -27,10 +27,13 @@ test_tarball() {
|
||||||
|
|
||||||
nix-build -o $TEST_ROOT/result -E "import (fetchTarball file://$tarball)"
|
nix-build -o $TEST_ROOT/result -E "import (fetchTarball file://$tarball)"
|
||||||
|
|
||||||
nix-build --experimental-features flakes -o $TEST_ROOT/result -E "import (fetchTree file://$tarball)"
|
nix-build -o $TEST_ROOT/result -E "import (fetchTree file://$tarball)"
|
||||||
nix-build --experimental-features flakes -o $TEST_ROOT/result -E "import (fetchTree { type = \"tarball\"; url = file://$tarball; })"
|
nix-build -o $TEST_ROOT/result -E "import (fetchTree { type = \"tarball\"; url = file://$tarball; })"
|
||||||
nix-build --experimental-features flakes -o $TEST_ROOT/result -E "import (fetchTree { type = \"tarball\"; url = file://$tarball; narHash = \"$hash\"; })"
|
nix-build -o $TEST_ROOT/result -E "import (fetchTree { type = \"tarball\"; url = file://$tarball; narHash = \"$hash\"; })"
|
||||||
nix-build --experimental-features flakes -o $TEST_ROOT/result -E "import (fetchTree { type = \"tarball\"; url = file://$tarball; narHash = \"sha256-xdKv2pq/IiwLSnBBJXW8hNowI4MrdZfW+SYqDQs7Tzc=\"; })" 2>&1 | grep 'NAR hash mismatch in input'
|
nix-build -o $TEST_ROOT/result -E "import (fetchTree { type = \"tarball\"; url = file://$tarball; narHash = \"sha256-xdKv2pq/IiwLSnBBJXW8hNowI4MrdZfW+SYqDQs7Tzc=\"; })" 2>&1 | grep 'NAR hash mismatch in input'
|
||||||
|
|
||||||
|
nix-instantiate --strict --eval -E "!((import (fetchTree { type = \"tarball\"; url = file://$tarball; narHash = \"$hash\"; })) ? submodules)" >&2
|
||||||
|
nix-instantiate --strict --eval -E "!((import (fetchTree { type = \"tarball\"; url = file://$tarball; narHash = \"$hash\"; })) ? submodules)" 2>&1 | grep 'true'
|
||||||
|
|
||||||
nix-instantiate --eval -E '1 + 2' -I fnord=file://no-such-tarball.tar$ext
|
nix-instantiate --eval -E '1 + 2' -I fnord=file://no-such-tarball.tar$ext
|
||||||
nix-instantiate --eval -E 'with <fnord/xyzzy>; 1 + 2' -I fnord=file://no-such-tarball$ext
|
nix-instantiate --eval -E 'with <fnord/xyzzy>; 1 + 2' -I fnord=file://no-such-tarball$ext
|
||||||
|
|
Loading…
Reference in a new issue