aboutsummaryrefslogtreecommitdiff
path: root/src/libstore
diff options
context:
space:
mode:
Diffstat (limited to 'src/libstore')
-rw-r--r--src/libstore/binary-cache-store.cc17
-rw-r--r--src/libstore/binary-cache-store.hh2
-rw-r--r--src/libstore/build/derivation-goal.cc48
-rw-r--r--src/libstore/build/derivation-goal.hh9
-rw-r--r--src/libstore/build/entry-points.cc10
-rw-r--r--src/libstore/build/local-derivation-goal.cc4
-rw-r--r--src/libstore/build/worker.cc6
-rw-r--r--src/libstore/build/worker.hh6
-rw-r--r--src/libstore/derivations.cc6
-rw-r--r--src/libstore/derivations.hh2
-rw-r--r--src/libstore/derived-path.cc25
-rw-r--r--src/libstore/derived-path.hh3
-rw-r--r--src/libstore/globals.hh2
-rw-r--r--src/libstore/legacy-ssh-store.cc7
-rw-r--r--src/libstore/local-fs-store.cc14
-rw-r--r--src/libstore/local-fs-store.hh2
-rw-r--r--src/libstore/log-store.cc12
-rw-r--r--src/libstore/log-store.hh4
-rw-r--r--src/libstore/misc.cc45
-rw-r--r--src/libstore/outputs-spec.cc189
-rw-r--r--src/libstore/outputs-spec.hh95
-rw-r--r--src/libstore/path-info.cc113
-rw-r--r--src/libstore/path-with-outputs.cc81
-rw-r--r--src/libstore/path-with-outputs.hh31
-rw-r--r--src/libstore/path.hh1
-rw-r--r--src/libstore/remote-store.cc18
-rw-r--r--src/libstore/ssh-store.cc4
-rw-r--r--src/libstore/store-api.cc139
-rw-r--r--src/libstore/store-api.hh17
-rw-r--r--src/libstore/tests/outputs-spec.cc187
-rw-r--r--src/libstore/tests/path-with-outputs.cc46
31 files changed, 798 insertions, 347 deletions
diff --git a/src/libstore/binary-cache-store.cc b/src/libstore/binary-cache-store.cc
index aa5aafdbf..087b37655 100644
--- a/src/libstore/binary-cache-store.cc
+++ b/src/libstore/binary-cache-store.cc
@@ -530,22 +530,9 @@ void BinaryCacheStore::addSignatures(const StorePath & storePath, const StringSe
writeNarInfo(narInfo);
}
-std::optional<std::string> BinaryCacheStore::getBuildLog(const StorePath & path)
+std::optional<std::string> BinaryCacheStore::getBuildLogExact(const StorePath & path)
{
- auto drvPath = path;
-
- if (!path.isDerivation()) {
- try {
- auto info = queryPathInfo(path);
- // FIXME: add a "Log" field to .narinfo
- if (!info->deriver) return std::nullopt;
- drvPath = *info->deriver;
- } catch (InvalidPath &) {
- return std::nullopt;
- }
- }
-
- auto logPath = "log/" + std::string(baseNameOf(printStorePath(drvPath)));
+ auto logPath = "log/" + std::string(baseNameOf(printStorePath(path)));
debug("fetching build log from binary cache '%s/%s'", getUri(), logPath);
diff --git a/src/libstore/binary-cache-store.hh b/src/libstore/binary-cache-store.hh
index 8c82e2387..abd92a83c 100644
--- a/src/libstore/binary-cache-store.hh
+++ b/src/libstore/binary-cache-store.hh
@@ -129,7 +129,7 @@ public:
void addSignatures(const StorePath & storePath, const StringSet & sigs) override;
- std::optional<std::string> getBuildLog(const StorePath & path) override;
+ std::optional<std::string> getBuildLogExact(const StorePath & path) override;
void addBuildLog(const StorePath & drvPath, std::string_view log) override;
diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc
index 5e86b5269..2021d0023 100644
--- a/src/libstore/build/derivation-goal.cc
+++ b/src/libstore/build/derivation-goal.cc
@@ -63,7 +63,7 @@
namespace nix {
DerivationGoal::DerivationGoal(const StorePath & drvPath,
- const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode)
+ const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
: Goal(worker, DerivedPath::Built { .drvPath = drvPath, .outputs = wantedOutputs })
, useDerivation(true)
, drvPath(drvPath)
@@ -82,7 +82,7 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath,
DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
- const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode)
+ const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
: Goal(worker, DerivedPath::Built { .drvPath = drvPath, .outputs = wantedOutputs })
, useDerivation(false)
, drvPath(drvPath)
@@ -142,18 +142,12 @@ void DerivationGoal::work()
(this->*state)();
}
-void DerivationGoal::addWantedOutputs(const StringSet & outputs)
+void DerivationGoal::addWantedOutputs(const OutputsSpec & outputs)
{
- /* If we already want all outputs, there is nothing to do. */
- if (wantedOutputs.empty()) return;
-
- if (outputs.empty()) {
- wantedOutputs.clear();
+ auto newWanted = wantedOutputs.union_(outputs);
+ if (!newWanted.isSubsetOf(wantedOutputs))
needRestart = true;
- } else
- for (auto & i : outputs)
- if (wantedOutputs.insert(i).second)
- needRestart = true;
+ wantedOutputs = newWanted;
}
@@ -390,7 +384,7 @@ void DerivationGoal::repairClosure()
auto outputs = queryDerivationOutputMap();
StorePathSet outputClosure;
for (auto & i : outputs) {
- if (!wantOutput(i.first, wantedOutputs)) continue;
+ if (!wantedOutputs.contains(i.first)) continue;
worker.store.computeFSClosure(i.second, outputClosure);
}
@@ -422,7 +416,7 @@ void DerivationGoal::repairClosure()
if (drvPath2 == outputsToDrv.end())
addWaitee(upcast_goal(worker.makePathSubstitutionGoal(i, Repair)));
else
- addWaitee(worker.makeDerivationGoal(drvPath2->second, StringSet(), bmRepair));
+ addWaitee(worker.makeDerivationGoal(drvPath2->second, OutputsSpec::All(), bmRepair));
}
if (waitees.empty()) {
@@ -991,10 +985,15 @@ void DerivationGoal::resolvedFinished()
StorePathSet outputPaths;
- // `wantedOutputs` might be empty, which means “all the outputs”
- auto realWantedOutputs = wantedOutputs;
- if (realWantedOutputs.empty())
- realWantedOutputs = resolvedDrv.outputNames();
+ // `wantedOutputs` might merely indicate “all the outputs”
+ auto realWantedOutputs = std::visit(overloaded {
+ [&](const OutputsSpec::All &) {
+ return resolvedDrv.outputNames();
+ },
+ [&](const OutputsSpec::Names & names) {
+ return static_cast<std::set<std::string>>(names);
+ },
+ }, wantedOutputs.raw());
for (auto & wantedOutput : realWantedOutputs) {
auto initialOutput = get(initialOutputs, wantedOutput);
@@ -1322,7 +1321,14 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
if (!drv->type().isPure()) return { false, {} };
bool checkHash = buildMode == bmRepair;
- auto wantedOutputsLeft = wantedOutputs;
+ auto wantedOutputsLeft = std::visit(overloaded {
+ [&](const OutputsSpec::All &) {
+ return StringSet {};
+ },
+ [&](const OutputsSpec::Names & names) {
+ return static_cast<StringSet>(names);
+ },
+ }, wantedOutputs.raw());
DrvOutputs validOutputs;
for (auto & i : queryPartialDerivationOutputMap()) {
@@ -1331,7 +1337,7 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
// this is an invalid output, gets catched with (!wantedOutputsLeft.empty())
continue;
auto & info = *initialOutput;
- info.wanted = wantOutput(i.first, wantedOutputs);
+ info.wanted = wantedOutputs.contains(i.first);
if (info.wanted)
wantedOutputsLeft.erase(i.first);
if (i.second) {
@@ -1369,7 +1375,7 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
validOutputs.emplace(drvOutput, Realisation { drvOutput, info.known->path });
}
- // If we requested all the outputs via the empty set, we are always fine.
+ // If we requested all the outputs, we are always fine.
// If we requested specific elements, the loop above removes all the valid
// ones, so any that are left must be invalid.
if (!wantedOutputsLeft.empty())
diff --git a/src/libstore/build/derivation-goal.hh b/src/libstore/build/derivation-goal.hh
index d33e04cbc..707e38b4b 100644
--- a/src/libstore/build/derivation-goal.hh
+++ b/src/libstore/build/derivation-goal.hh
@@ -2,6 +2,7 @@
#include "parsed-derivations.hh"
#include "lock.hh"
+#include "outputs-spec.hh"
#include "store-api.hh"
#include "pathlocks.hh"
#include "goal.hh"
@@ -55,7 +56,7 @@ struct DerivationGoal : public Goal
/* The specific outputs that we need to build. Empty means all of
them. */
- StringSet wantedOutputs;
+ OutputsSpec wantedOutputs;
/* Mapping from input derivations + output names to actual store
paths. This is filled in by waiteeDone() as each dependency
@@ -128,10 +129,10 @@ struct DerivationGoal : public Goal
std::string machineName;
DerivationGoal(const StorePath & drvPath,
- const StringSet & wantedOutputs, Worker & worker,
+ const OutputsSpec & wantedOutputs, Worker & worker,
BuildMode buildMode = bmNormal);
DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
- const StringSet & wantedOutputs, Worker & worker,
+ const OutputsSpec & wantedOutputs, Worker & worker,
BuildMode buildMode = bmNormal);
virtual ~DerivationGoal();
@@ -142,7 +143,7 @@ struct DerivationGoal : public Goal
void work() override;
/* Add wanted outputs to an already existing derivation goal. */
- void addWantedOutputs(const StringSet & outputs);
+ void addWantedOutputs(const OutputsSpec & outputs);
/* The states. */
void getDerivation();
diff --git a/src/libstore/build/entry-points.cc b/src/libstore/build/entry-points.cc
index e1b80165e..2925fe3ca 100644
--- a/src/libstore/build/entry-points.cc
+++ b/src/libstore/build/entry-points.cc
@@ -80,7 +80,7 @@ BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivat
BuildMode buildMode)
{
Worker worker(*this, *this);
- auto goal = worker.makeBasicDerivationGoal(drvPath, drv, {}, buildMode);
+ auto goal = worker.makeBasicDerivationGoal(drvPath, drv, OutputsSpec::All {}, buildMode);
try {
worker.run(Goals{goal});
@@ -89,7 +89,10 @@ BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivat
return BuildResult {
.status = BuildResult::MiscFailure,
.errorMsg = e.msg(),
- .path = DerivedPath::Built { .drvPath = drvPath },
+ .path = DerivedPath::Built {
+ .drvPath = drvPath,
+ .outputs = OutputsSpec::All { },
+ },
};
};
}
@@ -130,7 +133,8 @@ void LocalStore::repairPath(const StorePath & path)
auto info = queryPathInfo(path);
if (info->deriver && isValidPath(*info->deriver)) {
goals.clear();
- goals.insert(worker.makeDerivationGoal(*info->deriver, StringSet(), bmRepair));
+ // FIXME: Should just build the specific output we need.
+ goals.insert(worker.makeDerivationGoal(*info->deriver, OutputsSpec::All { }, bmRepair));
worker.run(goals);
} else
throw Error(worker.exitStatus(), "cannot repair path '%s'", printStorePath(path));
diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc
index ff24bd088..d96858fc0 100644
--- a/src/libstore/build/local-derivation-goal.cc
+++ b/src/libstore/build/local-derivation-goal.cc
@@ -1459,7 +1459,7 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
unknown, downloadSize, narSize);
}
- virtual std::optional<std::string> getBuildLog(const StorePath & path) override
+ virtual std::optional<std::string> getBuildLogExact(const StorePath & path) override
{ return std::nullopt; }
virtual void addBuildLog(const StorePath & path, std::string_view log) override
@@ -2726,7 +2726,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
signRealisation(thisRealisation);
worker.store.registerDrvOutput(thisRealisation);
}
- if (wantOutput(outputName, wantedOutputs))
+ if (wantedOutputs.contains(outputName))
builtOutputs.emplace(thisRealisation.id, thisRealisation);
}
diff --git a/src/libstore/build/worker.cc b/src/libstore/build/worker.cc
index b192fbc77..b94fb8416 100644
--- a/src/libstore/build/worker.cc
+++ b/src/libstore/build/worker.cc
@@ -42,7 +42,7 @@ Worker::~Worker()
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon(
const StorePath & drvPath,
- const StringSet & wantedOutputs,
+ const OutputsSpec & wantedOutputs,
std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal)
{
std::weak_ptr<DerivationGoal> & goal_weak = derivationGoals[drvPath];
@@ -59,7 +59,7 @@ std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon(
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(const StorePath & drvPath,
- const StringSet & wantedOutputs, BuildMode buildMode)
+ const OutputsSpec & wantedOutputs, BuildMode buildMode)
{
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
return !dynamic_cast<LocalStore *>(&store)
@@ -70,7 +70,7 @@ std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(const StorePath & drv
std::shared_ptr<DerivationGoal> Worker::makeBasicDerivationGoal(const StorePath & drvPath,
- const BasicDerivation & drv, const StringSet & wantedOutputs, BuildMode buildMode)
+ const BasicDerivation & drv, const OutputsSpec & wantedOutputs, BuildMode buildMode)
{
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
return !dynamic_cast<LocalStore *>(&store)
diff --git a/src/libstore/build/worker.hh b/src/libstore/build/worker.hh
index a1e036a96..6d68d3cf1 100644
--- a/src/libstore/build/worker.hh
+++ b/src/libstore/build/worker.hh
@@ -140,15 +140,15 @@ public:
/* derivation goal */
private:
std::shared_ptr<DerivationGoal> makeDerivationGoalCommon(
- const StorePath & drvPath, const StringSet & wantedOutputs,
+ const StorePath & drvPath, const OutputsSpec & wantedOutputs,
std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal);
public:
std::shared_ptr<DerivationGoal> makeDerivationGoal(
const StorePath & drvPath,
- const StringSet & wantedOutputs, BuildMode buildMode = bmNormal);
+ const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal);
std::shared_ptr<DerivationGoal> makeBasicDerivationGoal(
const StorePath & drvPath, const BasicDerivation & drv,
- const StringSet & wantedOutputs, BuildMode buildMode = bmNormal);
+ const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal);
/* substitution goal */
std::shared_ptr<PathSubstitutionGoal> makePathSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc
index 156a2b425..135f5799b 100644
--- a/src/libstore/derivations.cc
+++ b/src/libstore/derivations.cc
@@ -688,12 +688,6 @@ std::map<std::string, Hash> staticOutputHashes(Store & store, const Derivation &
}
-bool wantOutput(const std::string & output, const std::set<std::string> & wanted)
-{
- return wanted.empty() || wanted.find(output) != wanted.end();
-}
-
-
static DerivationOutput readDerivationOutput(Source & in, const Store & store)
{
const auto pathS = readString(in);
diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh
index f3cd87fb1..7ee3ded6a 100644
--- a/src/libstore/derivations.hh
+++ b/src/libstore/derivations.hh
@@ -294,8 +294,6 @@ typedef std::map<StorePath, DrvHash> DrvHashes;
// FIXME: global, though at least thread-safe.
extern Sync<DrvHashes> drvHashes;
-bool wantOutput(const std::string & output, const std::set<std::string> & wanted);
-
struct Source;
struct Sink;
diff --git a/src/libstore/derived-path.cc b/src/libstore/derived-path.cc
index 3fa5ae4f7..e0d86a42f 100644
--- a/src/libstore/derived-path.cc
+++ b/src/libstore/derived-path.cc
@@ -19,11 +19,11 @@ nlohmann::json DerivedPath::Built::toJSON(ref<Store> store) const {
res["drvPath"] = store->printStorePath(drvPath);
// Fallback for the input-addressed derivation case: We expect to always be
// able to print the output paths, so let’s do it
- const auto knownOutputs = store->queryPartialDerivationOutputMap(drvPath);
- for (const auto & output : outputs) {
- auto knownOutput = get(knownOutputs, output);
- if (knownOutput && *knownOutput)
- res["outputs"][output] = store->printStorePath(**knownOutput);
+ const auto outputMap = store->queryPartialDerivationOutputMap(drvPath);
+ for (const auto & [output, outputPathOpt] : outputMap) {
+ if (!outputs.contains(output)) continue;
+ if (outputPathOpt)
+ res["outputs"][output] = store->printStorePath(*outputPathOpt);
else
res["outputs"][output] = nullptr;
}
@@ -63,7 +63,7 @@ std::string DerivedPath::Built::to_string(const Store & store) const
{
return store.printStorePath(drvPath)
+ "!"
- + (outputs.empty() ? std::string { "*" } : concatStringsSep(",", outputs));
+ + outputs.to_string();
}
std::string DerivedPath::to_string(const Store & store) const
@@ -81,15 +81,10 @@ DerivedPath::Opaque DerivedPath::Opaque::parse(const Store & store, std::string_
DerivedPath::Built DerivedPath::Built::parse(const Store & store, std::string_view drvS, std::string_view outputsS)
{
- auto drvPath = store.parseStorePath(drvS);
- std::set<std::string> outputs;
- if (outputsS != "*") {
- outputs = tokenizeString<std::set<std::string>>(outputsS, ",");
- if (outputs.empty())
- throw Error(
- "Explicit list of wanted outputs '%s' must not be empty. Consider using '*' as a wildcard meaning all outputs if no output in particular is wanted.", outputsS);
- }
- return {drvPath, outputs};
+ return {
+ .drvPath = store.parseStorePath(drvS),
+ .outputs = OutputsSpec::parse(outputsS),
+ };
}
DerivedPath DerivedPath::parse(const Store & store, std::string_view s)
diff --git a/src/libstore/derived-path.hh b/src/libstore/derived-path.hh
index b6063c300..5f2dfa4f1 100644
--- a/src/libstore/derived-path.hh
+++ b/src/libstore/derived-path.hh
@@ -3,6 +3,7 @@
#include "util.hh"
#include "path.hh"
#include "realisation.hh"
+#include "outputs-spec.hh"
#include <optional>
#include <variant>
@@ -45,7 +46,7 @@ struct DerivedPathOpaque {
*/
struct DerivedPathBuilt {
StorePath drvPath;
- std::set<std::string> outputs;
+ OutputsSpec outputs;
std::string to_string(const Store & store) const;
static DerivedPathBuilt parse(const Store & store, std::string_view, std::string_view);
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index f026c8808..7111def92 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -676,7 +676,7 @@ public:
- the store object is signed by one of the [`trusted-public-keys`](#conf-trusted-public-keys)
- the substituter is in the [`trusted-substituters`](#conf-trusted-substituters) list
- the [`require-sigs`](#conf-require-sigs) option has been set to `false`
- - the store object is [output-addressed](glossary.md#gloss-output-addressed-store-object)
+ - the store object is [output-addressed](@docroot@/glossary.md#gloss-output-addressed-store-object)
)",
{"binary-caches"}};
diff --git a/src/libstore/legacy-ssh-store.cc b/src/libstore/legacy-ssh-store.cc
index a3e3d2217..6a694f034 100644
--- a/src/libstore/legacy-ssh-store.cc
+++ b/src/libstore/legacy-ssh-store.cc
@@ -278,7 +278,12 @@ public:
conn->to.flush();
- BuildResult status { .path = DerivedPath::Built { .drvPath = drvPath } };
+ BuildResult status {
+ .path = DerivedPath::Built {
+ .drvPath = drvPath,
+ .outputs = OutputsSpec::All { },
+ },
+ };
status.status = (BuildResult::Status) readInt(conn->from);
conn->from >> status.errorMsg;
diff --git a/src/libstore/local-fs-store.cc b/src/libstore/local-fs-store.cc
index c5ae7536f..b224fc3e9 100644
--- a/src/libstore/local-fs-store.cc
+++ b/src/libstore/local-fs-store.cc
@@ -87,20 +87,8 @@ void LocalFSStore::narFromPath(const StorePath & path, Sink & sink)
const std::string LocalFSStore::drvsLogDir = "drvs";
-std::optional<std::string> LocalFSStore::getBuildLog(const StorePath & path_)
+std::optional<std::string> LocalFSStore::getBuildLogExact(const StorePath & path)
{
- auto path = path_;
-
- if (!path.isDerivation()) {
- try {
- auto info = queryPathInfo(path);
- if (!info->deriver) return std::nullopt;
- path = *info->deriver;
- } catch (InvalidPath &) {
- return std::nullopt;
- }
- }
-
auto baseName = path.to_string();
for (int j = 0; j < 2; j++) {
diff --git a/src/libstore/local-fs-store.hh b/src/libstore/local-fs-store.hh
index e6fb3201a..947707341 100644
--- a/src/libstore/local-fs-store.hh
+++ b/src/libstore/local-fs-store.hh
@@ -50,7 +50,7 @@ public:
return getRealStoreDir() + "/" + std::string(storePath, storeDir.size() + 1);
}
- std::optional<std::string> getBuildLog(const StorePath & path) override;
+ std::optional<std::string> getBuildLogExact(const StorePath & path) override;
};
diff --git a/src/libstore/log-store.cc b/src/libstore/log-store.cc
new file mode 100644
index 000000000..8a26832ab
--- /dev/null
+++ b/src/libstore/log-store.cc
@@ -0,0 +1,12 @@
+#include "log-store.hh"
+
+namespace nix {
+
+std::optional<std::string> LogStore::getBuildLog(const StorePath & path) {
+ auto maybePath = getBuildDerivationPath(path);
+ if (!maybePath)
+ return std::nullopt;
+ return getBuildLogExact(maybePath.value());
+}
+
+}
diff --git a/src/libstore/log-store.hh b/src/libstore/log-store.hh
index ff1b92e17..e4d95bab6 100644
--- a/src/libstore/log-store.hh
+++ b/src/libstore/log-store.hh
@@ -11,7 +11,9 @@ struct LogStore : public virtual Store
/* Return the build log of the specified store path, if available,
or null otherwise. */
- virtual std::optional<std::string> getBuildLog(const StorePath & path) = 0;
+ std::optional<std::string> getBuildLog(const StorePath & path);
+
+ virtual std::optional<std::string> getBuildLogExact(const StorePath & path) = 0;
virtual void addBuildLog(const StorePath & path, std::string_view log) = 0;
diff --git a/src/libstore/misc.cc b/src/libstore/misc.cc
index 87f85c3cc..70e97569a 100644
--- a/src/libstore/misc.cc
+++ b/src/libstore/misc.cc
@@ -184,7 +184,7 @@ void Store::queryMissing(const std::vector<DerivedPath> & targets,
knownOutputPaths = false;
break;
}
- if (wantOutput(outputName, bfd.outputs) && !isValidPath(*pathOpt))
+ if (bfd.outputs.contains(outputName) && !isValidPath(*pathOpt))
invalid.insert(*pathOpt);
}
if (knownOutputPaths && invalid.empty()) return;
@@ -300,4 +300,47 @@ std::map<DrvOutput, StorePath> drvOutputReferences(
return drvOutputReferences(Realisation::closure(store, inputRealisations), info->referencesPossiblyToSelf());
}
+OutputPathMap resolveDerivedPath(Store & store, const DerivedPath::Built & bfd, Store * evalStore_)
+{
+ auto & evalStore = evalStore_ ? *evalStore_ : store;
+
+ OutputPathMap outputs;
+ auto drv = evalStore.readDerivation(bfd.drvPath);
+ auto outputHashes = staticOutputHashes(store, drv);
+ auto drvOutputs = drv.outputsAndOptPaths(store);
+ auto outputNames = std::visit(overloaded {
+ [&](const OutputsSpec::All &) {
+ StringSet names;
+ for (auto & [outputName, _] : drv.outputs)
+ names.insert(outputName);
+ return names;
+ },
+ [&](const OutputsSpec::Names & names) {
+ return static_cast<std::set<std::string>>(names);
+ },
+ }, bfd.outputs);
+ for (auto & output : outputNames) {
+ auto outputHash = get(outputHashes, output);
+ if (!outputHash)
+ throw Error(
+ "the derivation '%s' doesn't have an output named '%s'",
+ store.printStorePath(bfd.drvPath), output);
+ if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
+ DrvOutput outputId { *outputHash, output };
+ auto realisation = store.queryRealisation(outputId);
+ if (!realisation)
+ throw MissingRealisation(outputId);
+ outputs.insert_or_assign(output, realisation->outPath);
+ } else {
+ // If ca-derivations isn't enabled, assume that
+ // the output path is statically known.
+ auto drvOutput = get(drvOutputs, output);
+ assert(drvOutput);
+ assert(drvOutput->second);
+ outputs.insert_or_assign(output, *drvOutput->second);
+ }
+ }
+ return outputs;
+}
+
}
diff --git a/src/libstore/outputs-spec.cc b/src/libstore/outputs-spec.cc
new file mode 100644
index 000000000..d0f39a854
--- /dev/null
+++ b/src/libstore/outputs-spec.cc
@@ -0,0 +1,189 @@
+#include "util.hh"
+#include "outputs-spec.hh"
+#include "nlohmann/json.hpp"
+
+#include <regex>
+
+namespace nix {
+
+bool OutputsSpec::contains(const std::string & outputName) const
+{
+ return std::visit(overloaded {
+ [&](const OutputsSpec::All &) {
+ return true;
+ },
+ [&](const OutputsSpec::Names & outputNames) {
+ return outputNames.count(outputName) > 0;
+ },
+ }, raw());
+}
+
+
+std::optional<OutputsSpec> OutputsSpec::parseOpt(std::string_view s)
+{
+ static std::regex regex(R"((\*)|([a-z]+(,[a-z]+)*))");
+
+ std::smatch match;
+ std::string s2 { s }; // until some improves std::regex
+ if (!std::regex_match(s2, match, regex))
+ return std::nullopt;
+
+ if (match[1].matched)
+ return { OutputsSpec::All {} };
+
+ if (match[2].matched)
+ return OutputsSpec::Names { tokenizeString<StringSet>(match[2].str(), ",") };
+
+ assert(false);
+}
+
+
+OutputsSpec OutputsSpec::parse(std::string_view s)
+{
+ std::optional spec = parseOpt(s);
+ if (!spec)
+ throw Error("Invalid outputs specifier: '%s'", s);
+ return *spec;
+}
+
+
+std::optional<std::pair<std::string_view, ExtendedOutputsSpec>> ExtendedOutputsSpec::parseOpt(std::string_view s)
+{
+ auto found = s.rfind('^');
+
+ if (found == std::string::npos)
+ return std::pair { s, ExtendedOutputsSpec::Default {} };
+
+ auto specOpt = OutputsSpec::parseOpt(s.substr(found + 1));
+ if (!specOpt)
+ return std::nullopt;
+ return std::pair { s.substr(0, found), ExtendedOutputsSpec::Explicit { *std::move(specOpt) } };
+}
+
+
+std::pair<std::string_view, ExtendedOutputsSpec> ExtendedOutputsSpec::parse(std::string_view s)
+{
+ std::optional spec = parseOpt(s);
+ if (!spec)
+ throw Error("Invalid extended outputs specifier: '%s'", s);
+ return *spec;
+}
+
+
+std::string OutputsSpec::to_string() const
+{
+ return std::visit(overloaded {
+ [&](const OutputsSpec::All &) -> std::string {
+ return "*";
+ },
+ [&](const OutputsSpec::Names & outputNames) -> std::string {
+ return concatStringsSep(",", outputNames);
+ },
+ }, raw());
+}
+
+
+std::string ExtendedOutputsSpec::to_string() const
+{
+ return std::visit(overloaded {
+ [&](const ExtendedOutputsSpec::Default &) -> std::string {
+ return "";
+ },
+ [&](const ExtendedOutputsSpec::Explicit & outputSpec) -> std::string {
+ return "^" + outputSpec.to_string();
+ },
+ }, raw());
+}
+
+
+OutputsSpec OutputsSpec::union_(const OutputsSpec & that) const
+{
+ return std::visit(overloaded {
+ [&](const OutputsSpec::All &) -> OutputsSpec {
+ return OutputsSpec::All { };
+ },
+ [&](const OutputsSpec::Names & theseNames) -> OutputsSpec {
+ return std::visit(overloaded {
+ [&](const OutputsSpec::All &) -> OutputsSpec {
+ return OutputsSpec::All {};
+ },
+ [&](const OutputsSpec::Names & thoseNames) -> OutputsSpec {
+ OutputsSpec::Names ret = theseNames;
+ ret.insert(thoseNames.begin(), thoseNames.end());
+ return ret;
+ },
+ }, that.raw());
+ },
+ }, raw());
+}
+
+
+bool OutputsSpec::isSubsetOf(const OutputsSpec & that) const
+{
+ return std::visit(overloaded {
+ [&](const OutputsSpec::All &) {
+ return true;
+ },
+ [&](const OutputsSpec::Names & thoseNames) {
+ return std::visit(overloaded {
+ [&](const OutputsSpec::All &) {
+ return false;
+ },
+ [&](const OutputsSpec::Names & theseNames) {
+ bool ret = true;
+ for (auto & o : theseNames)
+ if (thoseNames.count(o) == 0)
+ ret = false;
+ return ret;
+ },
+ }, raw());
+ },
+ }, that.raw());
+}
+
+}
+
+namespace nlohmann {
+
+using namespace nix;
+
+OutputsSpec adl_serializer<OutputsSpec>::from_json(const json & json) {
+ auto names = json.get<StringSet>();
+ if (names == StringSet({"*"}))
+ return OutputsSpec::All {};
+ else
+ return OutputsSpec::Names { std::move(names) };
+}
+
+void adl_serializer<OutputsSpec>::to_json(json & json, OutputsSpec t) {
+ std::visit(overloaded {
+ [&](const OutputsSpec::All &) {
+ json = std::vector<std::string>({"*"});
+ },
+ [&](const OutputsSpec::Names & names) {
+ json = names;
+ },
+ }, t);
+}
+
+
+ExtendedOutputsSpec adl_serializer<ExtendedOutputsSpec>::from_json(const json & json) {
+ if (json.is_null())
+ return ExtendedOutputsSpec::Default {};
+ else {
+ return ExtendedOutputsSpec::Explicit { json.get<OutputsSpec>() };
+ }
+}
+
+void adl_serializer<ExtendedOutputsSpec>::to_json(json & json, ExtendedOutputsSpec t) {
+ std::visit(overloaded {
+ [&](const ExtendedOutputsSpec::Default &) {
+ json = nullptr;
+ },
+ [&](const ExtendedOutputsSpec::Explicit & e) {
+ adl_serializer<OutputsSpec>::to_json(json, e);
+ },
+ }, t);
+}
+
+}
diff --git a/src/libstore/outputs-spec.hh b/src/libstore/outputs-spec.hh
new file mode 100644
index 000000000..46bc35ebc
--- /dev/null
+++ b/src/libstore/outputs-spec.hh
@@ -0,0 +1,95 @@
+#pragma once
+
+#include <cassert>
+#include <optional>
+#include <set>
+#include <variant>
+
+#include "json-impls.hh"
+
+namespace nix {
+
+struct OutputNames : std::set<std::string> {
+ using std::set<std::string>::set;
+
+ /* These need to be "inherited manually" */
+
+ OutputNames(const std::set<std::string> & s)
+ : std::set<std::string>(s)
+ { assert(!empty()); }
+
+ OutputNames(std::set<std::string> && s)
+ : std::set<std::string>(s)
+ { assert(!empty()); }
+
+ /* This set should always be non-empty, so we delete this
+ constructor in order make creating empty ones by mistake harder.
+ */
+ OutputNames() = delete;
+};
+
+struct AllOutputs : std::monostate { };
+
+typedef std::variant<AllOutputs, OutputNames> _OutputsSpecRaw;
+
+struct OutputsSpec : _OutputsSpecRaw {
+ using Raw = _OutputsSpecRaw;
+ using Raw::Raw;
+
+ /* Force choosing a variant */
+ OutputsSpec() = delete;
+
+ using Names = OutputNames;
+ using All = AllOutputs;
+
+ inline const Raw & raw() const {
+ return static_cast<const Raw &>(*this);
+ }
+
+ inline Raw & raw() {
+ return static_cast<Raw &>(*this);
+ }
+
+ bool contains(const std::string & output) const;
+
+ /* Create a new OutputsSpec which is the union of this and that. */
+ OutputsSpec union_(const OutputsSpec & that) const;
+
+ /* Whether this OutputsSpec is a subset of that. */
+ bool isSubsetOf(const OutputsSpec & outputs) const;
+
+ /* Parse a string of the form 'output1,...outputN' or
+ '*', returning the outputs spec. */
+ static OutputsSpec parse(std::string_view s);
+ static std::optional<OutputsSpec> parseOpt(std::string_view s);
+
+ std::string to_string() const;
+};
+
+struct DefaultOutputs : std::monostate { };
+
+typedef std::variant<DefaultOutputs, OutputsSpec> _ExtendedOutputsSpecRaw;
+
+struct ExtendedOutputsSpec : _ExtendedOutputsSpecRaw {
+ using Raw = _ExtendedOutputsSpecRaw;
+ using Raw::Raw;
+
+ using Default = DefaultOutputs;
+ using Explicit = OutputsSpec;
+
+ inline const Raw & raw() const {
+ return static_cast<const Raw &>(*this);
+ }
+
+ /* Parse a string of the form 'prefix^output1,...outputN' or
+ 'prefix^*', returning the prefix and the extended outputs spec. */
+ static std::pair<std::string_view, ExtendedOutputsSpec> parse(std::string_view s);
+ static std::optional<std::pair<std::string_view, ExtendedOutputsSpec>> parseOpt(std::string_view s);
+
+ std::string to_string() const;
+};
+
+}
+
+JSON_IMPL(OutputsSpec)
+JSON_IMPL(ExtendedOutputsSpec)
diff --git a/src/libstore/path-info.cc b/src/libstore/path-info.cc
index cb3077c61..2972c0bbe 100644
--- a/src/libstore/path-info.cc
+++ b/src/libstore/path-info.cc
@@ -3,6 +3,117 @@
namespace nix {
+std::string ValidPathInfo::fingerprint(const Store & store) const
+{
+ if (narSize == 0)
+ throw Error("cannot calculate fingerprint of path '%s' because its size is not known",
+ store.printStorePath(path));
+ return
+ "1;" + store.printStorePath(path) + ";"
+ + narHash.to_string(Base32, true) + ";"
+ + std::to_string(narSize) + ";"
+ + concatStringsSep(",", store.printStorePathSet(referencesPossiblyToSelf()));
+}
+
+
+void ValidPathInfo::sign(const Store & store, const SecretKey & secretKey)
+{
+ sigs.insert(secretKey.signDetached(fingerprint(store)));
+}
+
+std::optional<StorePathDescriptor> ValidPathInfo::fullStorePathDescriptorOpt() const
+{
+ if (! ca)
+ return std::nullopt;
+
+ return StorePathDescriptor {
+ .name = std::string { path.name() },
+ .info = std::visit(overloaded {
+ [&](const TextHash & th) -> ContentAddressWithReferences {
+ assert(!references.self);
+ return TextInfo {
+ th,
+ .references = references.others,
+ };
+ },
+ [&](const FixedOutputHash & foh) -> ContentAddressWithReferences {
+ return FixedOutputInfo {
+ foh,
+ .references = references,
+ };
+ },
+ }, *ca),
+ };
+}
+
+bool ValidPathInfo::isContentAddressed(const Store & store) const
+{
+ auto fullCaOpt = fullStorePathDescriptorOpt();
+
+ if (! fullCaOpt)
+ return false;
+
+ auto caPath = store.makeFixedOutputPathFromCA(*fullCaOpt);
+
+ bool res = caPath == path;
+
+ if (!res)
+ printError("warning: path '%s' claims to be content-addressed but isn't", store.printStorePath(path));
+
+ return res;
+}
+
+
+size_t ValidPathInfo::checkSignatures(const Store & store, const PublicKeys & publicKeys) const
+{
+ if (isContentAddressed(store)) return maxSigs;
+
+ size_t good = 0;
+ for (auto & sig : sigs)
+ if (checkSignature(store, publicKeys, sig))
+ good++;
+ return good;
+}
+
+
+bool ValidPathInfo::checkSignature(const Store & store, const PublicKeys & publicKeys, const std::string & sig) const
+{
+ return verifyDetached(fingerprint(store), sig, publicKeys);
+}
+
+
+Strings ValidPathInfo::shortRefs() const
+{
+ Strings refs;
+ for (auto & r : referencesPossiblyToSelf())
+ refs.push_back(std::string(r.to_string()));
+ return refs;
+}
+
+
+ValidPathInfo::ValidPathInfo(
+ const Store & store,
+ StorePathDescriptor && info,
+ Hash narHash)
+ : path(store.makeFixedOutputPathFromCA(info))
+ , narHash(narHash)
+{
+ std::visit(overloaded {
+ [this](TextInfo && ti) {
+ this->references = {
+ .others = std::move(ti.references),
+ .self = false,
+ };
+ this->ca = std::move((TextHash &&) ti);
+ },
+ [this](FixedOutputInfo && foi) {
+ this->references = std::move(foi.references);
+ this->ca = std::move((FixedOutputHash &&) foi);
+ },
+ }, std::move(info.info));
+}
+
+
StorePathSet ValidPathInfo::referencesPossiblyToSelf() const
{
return references.possiblyToSelf(path);
@@ -18,6 +129,7 @@ void ValidPathInfo::setReferencesPossiblyToSelf(StorePathSet && refs)
return references.setPossiblyToSelf(path, std::move(refs));
}
+
ValidPathInfo ValidPathInfo::read(Source & source, const Store & store, unsigned int format)
{
return read(source, store, format, store.parseStorePath(readString(source)));
@@ -39,6 +151,7 @@ ValidPathInfo ValidPathInfo::read(Source & source, const Store & store, unsigned
return info;
}
+
void ValidPathInfo::write(
Sink & sink,
const Store & store,
diff --git a/src/libstore/path-with-outputs.cc b/src/libstore/path-with-outputs.cc
index d6d67ea05..869b490ad 100644
--- a/src/libstore/path-with-outputs.cc
+++ b/src/libstore/path-with-outputs.cc
@@ -1,6 +1,5 @@
#include "path-with-outputs.hh"
#include "store-api.hh"
-#include "nlohmann/json.hpp"
#include <regex>
@@ -16,10 +15,14 @@ std::string StorePathWithOutputs::to_string(const Store & store) const
DerivedPath StorePathWithOutputs::toDerivedPath() const
{
- if (!outputs.empty() || path.isDerivation())
- return DerivedPath::Built { path, outputs };
- else
+ if (!outputs.empty()) {
+ return DerivedPath::Built { path, OutputsSpec::Names { outputs } };
+ } else if (path.isDerivation()) {
+ assert(outputs.empty());
+ return DerivedPath::Built { path, OutputsSpec::All { } };
+ } else {
return DerivedPath::Opaque { path };
+ }
}
@@ -42,7 +45,18 @@ std::variant<StorePathWithOutputs, StorePath> StorePathWithOutputs::tryFromDeriv
return StorePathWithOutputs { bo.path };
},
[&](const DerivedPath::Built & bfd) -> std::variant<StorePathWithOutputs, StorePath> {
- return StorePathWithOutputs { bfd.drvPath, bfd.outputs };
+ return StorePathWithOutputs {
+ .path = bfd.drvPath,
+ // Use legacy encoding of wildcard as empty set
+ .outputs = std::visit(overloaded {
+ [&](const OutputsSpec::All &) -> StringSet {
+ return {};
+ },
+ [&](const OutputsSpec::Names & outputs) {
+ return static_cast<StringSet>(outputs);
+ },
+ }, bfd.outputs.raw()),
+ };
},
}, p.raw());
}
@@ -53,8 +67,8 @@ std::pair<std::string_view, StringSet> parsePathWithOutputs(std::string_view s)
size_t n = s.find("!");
return n == s.npos
? std::make_pair(s, std::set<std::string>())
- : std::make_pair(((std::string_view) s).substr(0, n),
- tokenizeString<std::set<std::string>>(((std::string_view) s).substr(n + 1), ","));
+ : std::make_pair(s.substr(0, n),
+ tokenizeString<std::set<std::string>>(s.substr(n + 1), ","));
}
@@ -71,57 +85,4 @@ StorePathWithOutputs followLinksToStorePathWithOutputs(const Store & store, std:
return StorePathWithOutputs { store.followLinksToStorePath(path), std::move(outputs) };
}
-std::pair<std::string, OutputsSpec> parseOutputsSpec(const std::string & s)
-{
- static std::regex regex(R"((.*)\^((\*)|([a-z]+(,[a-z]+)*)))");
-
- std::smatch match;
- if (!std::regex_match(s, match, regex))
- return {s, DefaultOutputs()};
-
- if (match[3].matched)
- return {match[1], AllOutputs()};
-
- return {match[1], tokenizeString<OutputNames>(match[4].str(), ",")};
-}
-
-std::string printOutputsSpec(const OutputsSpec & outputsSpec)
-{
- if (std::get_if<DefaultOutputs>(&outputsSpec))
- return "";
-
- if (std::get_if<AllOutputs>(&outputsSpec))
- return "^*";
-
- if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
- return "^" + concatStringsSep(",", *outputNames);
-
- assert(false);
-}
-
-void to_json(nlohmann::json & json, const OutputsSpec & outputsSpec)
-{
- if (std::get_if<DefaultOutputs>(&outputsSpec))
- json = nullptr;
-
- else if (std::get_if<AllOutputs>(&outputsSpec))
- json = std::vector<std::string>({"*"});
-
- else if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
- json = *outputNames;
-}
-
-void from_json(const nlohmann::json & json, OutputsSpec & outputsSpec)
-{
- if (json.is_null())
- outputsSpec = DefaultOutputs();
- else {
- auto names = json.get<OutputNames>();
- if (names == OutputNames({"*"}))
- outputsSpec = AllOutputs();
- else
- outputsSpec = names;
- }
-}
-
}
diff --git a/src/libstore/path-with-outputs.hh b/src/libstore/path-with-outputs.hh
index 0cb5eb223..5d25656a5 100644
--- a/src/libstore/path-with-outputs.hh
+++ b/src/libstore/path-with-outputs.hh
@@ -1,13 +1,17 @@
#pragma once
-#include <variant>
-
#include "path.hh"
#include "derived-path.hh"
-#include "nlohmann/json_fwd.hpp"
namespace nix {
+/* This is a deprecated old type just for use by the old CLI, and older
+ versions of the RPC protocols. In new code don't use it; you want
+ `DerivedPath` instead.
+
+ `DerivedPath` is better because it handles more cases, and does so more
+ explicitly without devious punning tricks.
+*/
struct StorePathWithOutputs
{
StorePath path;
@@ -33,25 +37,4 @@ StorePathWithOutputs parsePathWithOutputs(const Store & store, std::string_view
StorePathWithOutputs followLinksToStorePathWithOutputs(const Store & store, std::string_view pathWithOutputs);
-typedef std::set<std::string> OutputNames;
-
-struct AllOutputs {
- bool operator < (const AllOutputs & _) const { return false; }
-};
-
-struct DefaultOutputs {
- bool operator < (const DefaultOutputs & _) const { return false; }
-};
-
-typedef std::variant<DefaultOutputs, AllOutputs, OutputNames> OutputsSpec;
-
-/* Parse a string of the form 'prefix^output1,...outputN' or
- 'prefix^*', returning the prefix and the outputs spec. */
-std::pair<std::string, OutputsSpec> parseOutputsSpec(const std::string & s);
-
-std::string printOutputsSpec(const OutputsSpec & outputsSpec);
-
-void to_json(nlohmann::json &, const OutputsSpec &);
-void from_json(const nlohmann::json &, OutputsSpec &);
-
}
diff --git a/src/libstore/path.hh b/src/libstore/path.hh
index 6771f0ba5..7f13c11e9 100644
--- a/src/libstore/path.hh
+++ b/src/libstore/path.hh
@@ -65,7 +65,6 @@ public:
typedef std::set<StorePath> StorePathSet;
typedef std::vector<StorePath> StorePaths;
-typedef std::map<std::string, StorePath> OutputPathMap;
/* Extension of derivations in the Nix store. */
const std::string drvExtension = ".drv";
diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index 1f8098b85..8ea126c65 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -868,8 +868,8 @@ std::vector<BuildResult> RemoteStore::buildPathsWithResults(
OutputPathMap outputs;
auto drv = evalStore->readDerivation(bfd.drvPath);
const auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive
- const auto drvOutputs = drv.outputsAndOptPaths(*this);
- for (auto & output : bfd.outputs) {
+ auto built = resolveDerivedPath(*this, bfd, &*evalStore);
+ for (auto & [output, outputPath] : built) {
auto outputHash = get(outputHashes, output);
if (!outputHash)
throw Error(
@@ -883,16 +883,11 @@ std::vector<BuildResult> RemoteStore::buildPathsWithResults(
throw MissingRealisation(outputId);
res.builtOutputs.emplace(realisation->id, *realisation);
} else {
- // If ca-derivations isn't enabled, assume that
- // the output path is statically known.
- const auto drvOutput = get(drvOutputs, output);
- assert(drvOutput);
- assert(drvOutput->second);
res.builtOutputs.emplace(
outputId,
Realisation {
.id = outputId,
- .outPath = *drvOutput->second,
+ .outPath = outputPath,
});
}
}
@@ -916,7 +911,12 @@ BuildResult RemoteStore::buildDerivation(const StorePath & drvPath, const BasicD
writeDerivation(conn->to, *this, drv);
conn->to << buildMode;
conn.processStderr();
- BuildResult res { .path = DerivedPath::Built { .drvPath = drvPath } };
+ BuildResult res {
+ .path = DerivedPath::Built {
+ .drvPath = drvPath,
+ .outputs = OutputsSpec::All { },
+ },
+ };
res.status = (BuildResult::Status) readInt(conn->from);
conn->from >> res.errorMsg;
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 29) {
diff --git a/src/libstore/ssh-store.cc b/src/libstore/ssh-store.cc
index 62daa838c..a1d4daafd 100644
--- a/src/libstore/ssh-store.cc
+++ b/src/libstore/ssh-store.cc
@@ -53,8 +53,8 @@ public:
{ return false; }
// FIXME extend daemon protocol, move implementation to RemoteStore
- std::optional<std::string> getBuildLog(const StorePath & path) override
- { unsupported("getBuildLog"); }
+ std::optional<std::string> getBuildLogExact(const StorePath & path) override
+ { unsupported("getBuildLogExact"); }
private:
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index a4e98d66b..9446ad132 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -1228,117 +1228,6 @@ std::string showPaths(const PathSet & paths)
return concatStringsSep(", ", quoteStrings(paths));
}
-std::string ValidPathInfo::fingerprint(const Store & store) const
-{
- if (narSize == 0)
- throw Error("cannot calculate fingerprint of path '%s' because its size is not known",
- store.printStorePath(path));
- return
- "1;" + store.printStorePath(path) + ";"
- + narHash.to_string(Base32, true) + ";"
- + std::to_string(narSize) + ";"
- + concatStringsSep(",", store.printStorePathSet(referencesPossiblyToSelf()));
-}
-
-
-void ValidPathInfo::sign(const Store & store, const SecretKey & secretKey)
-{
- sigs.insert(secretKey.signDetached(fingerprint(store)));
-}
-
-std::optional<StorePathDescriptor> ValidPathInfo::fullStorePathDescriptorOpt() const
-{
- if (! ca)
- return std::nullopt;
-
- return StorePathDescriptor {
- .name = std::string { path.name() },
- .info = std::visit(overloaded {
- [&](const TextHash & th) -> ContentAddressWithReferences {
- assert(!references.self);
- return TextInfo {
- th,
- .references = references.others,
- };
- },
- [&](const FixedOutputHash & foh) -> ContentAddressWithReferences {
- return FixedOutputInfo {
- foh,
- .references = references,
- };
- },
- }, *ca),
- };
-}
-
-bool ValidPathInfo::isContentAddressed(const Store & store) const
-{
- auto fullCaOpt = fullStorePathDescriptorOpt();
-
- if (! fullCaOpt)
- return false;
-
- auto caPath = store.makeFixedOutputPathFromCA(*fullCaOpt);
-
- bool res = caPath == path;
-
- if (!res)
- printError("warning: path '%s' claims to be content-addressed but isn't", store.printStorePath(path));
-
- return res;
-}
-
-
-size_t ValidPathInfo::checkSignatures(const Store & store, const PublicKeys & publicKeys) const
-{
- if (isContentAddressed(store)) return maxSigs;
-
- size_t good = 0;
- for (auto & sig : sigs)
- if (checkSignature(store, publicKeys, sig))
- good++;
- return good;
-}
-
-
-bool ValidPathInfo::checkSignature(const Store & store, const PublicKeys & publicKeys, const std::string & sig) const
-{
- return verifyDetached(fingerprint(store), sig, publicKeys);
-}
-
-
-Strings ValidPathInfo::shortRefs() const
-{
- Strings refs;
- for (auto & r : referencesPossiblyToSelf())
- refs.push_back(std::string(r.to_string()));
- return refs;
-}
-
-
-ValidPathInfo::ValidPathInfo(
- const Store & store,
- StorePathDescriptor && info,
- Hash narHash)
- : path(store.makeFixedOutputPathFromCA(info))
- , narHash(narHash)
-{
- std::visit(overloaded {
- [this](TextInfo && ti) {
- this->references = {
- .others = std::move(ti.references),
- .self = false,
- };
- this->ca = std::move((TextHash &&) ti);
- },
- [this](FixedOutputInfo && foi) {
- this->references = std::move(foi.references);
- this->ca = std::move((FixedOutputHash &&) foi);
- },
- }, std::move(info.info));
-}
-
-
Derivation Store::derivationFromPath(const StorePath & drvPath)
{
ensurePath(drvPath);
@@ -1357,6 +1246,34 @@ Derivation readDerivationCommon(Store& store, const StorePath& drvPath, bool req
}
}
+std::optional<StorePath> Store::getBuildDerivationPath(const StorePath & path)
+{
+
+ if (!path.isDerivation()) {
+ try {
+ auto info = queryPathInfo(path);
+ if (!info->deriver) return std::nullopt;
+ return *info->deriver;
+ } catch (InvalidPath &) {
+ return std::nullopt;
+ }
+ }
+
+ if (!settings.isExperimentalFeatureEnabled(Xp::CaDerivations) || !isValidPath(path))
+ return path;
+
+ auto drv = readDerivation(path);
+ if (!drv.type().hasKnownOutputPaths()) {
+ // The build log is actually attached to the corresponding
+ // resolved derivation, so we need to get it first
+ auto resolvedDrv = drv.tryResolve(*this);
+ if (resolvedDrv)
+ return writeDerivation(*this, *resolvedDrv, NoRepair, true);
+ }
+
+ return path;
+}
+
Derivation Store::readDerivation(const StorePath & drvPath)
{ return readDerivationCommon(*this, drvPath, true); }
diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh
index 4579be4c6..d77aea338 100644
--- a/src/libstore/store-api.hh
+++ b/src/libstore/store-api.hh
@@ -71,6 +71,9 @@ class NarInfoDiskCache;
class Store;
+typedef std::map<std::string, StorePath> OutputPathMap;
+
+
enum CheckSigsFlag : bool { NoCheckSigs = false, CheckSigs = true };
enum SubstituteFlag : bool { NoSubstitute = false, Substitute = true };
enum AllowInvalidFlag : bool { DisallowInvalid = false, AllowInvalid = true };
@@ -122,6 +125,8 @@ public:
typedef std::map<std::string, std::string> Params;
+
+
protected:
struct PathInfoCacheValue {
@@ -614,6 +619,13 @@ public:
*/
StorePathSet exportReferences(const StorePathSet & storePaths, const StorePathSet & inputPaths);
+ /**
+ * Given a store path, return the realisation actually used in the realisation of this path:
+ * - If the path is a content-addressed derivation, try to resolve it
+ * - Otherwise, find one of its derivers
+ */
+ std::optional<StorePath> getBuildDerivationPath(const StorePath &);
+
/* Hack to allow long-running processes like hydra-queue-runner to
occasionally flush their path info cache. */
void clearPathInfoCache()
@@ -715,6 +727,11 @@ void copyClosure(
void removeTempRoots();
+/* Resolve the derived path completely, failing if any derivation output
+ is unknown. */
+OutputPathMap resolveDerivedPath(Store &, const DerivedPath::Built &, Store * evalStore = nullptr);
+
+
/* Return a Store object to access the Nix store denoted by
‘uri’ (slight misnomer...). Supported values are:
diff --git a/src/libstore/tests/outputs-spec.cc b/src/libstore/tests/outputs-spec.cc
new file mode 100644
index 000000000..c9c2cafd0
--- /dev/null
+++ b/src/libstore/tests/outputs-spec.cc
@@ -0,0 +1,187 @@
+#include "outputs-spec.hh"
+
+#include <nlohmann/json.hpp>
+#include <gtest/gtest.h>
+
+namespace nix {
+
+#ifndef NDEBUG
+TEST(OutputsSpec, no_empty_names) {
+ ASSERT_DEATH(OutputsSpec::Names { std::set<std::string> { } }, "");
+}
+#endif
+
+#define TEST_DONT_PARSE(NAME, STR) \
+ TEST(OutputsSpec, bad_ ## NAME) { \
+ std::optional OutputsSpecOpt = \
+ OutputsSpec::parseOpt(STR); \
+ ASSERT_FALSE(OutputsSpecOpt); \
+ }
+
+TEST_DONT_PARSE(empty, "")
+TEST_DONT_PARSE(garbage, "&*()")
+TEST_DONT_PARSE(double_star, "**")
+TEST_DONT_PARSE(star_first, "*,foo")
+TEST_DONT_PARSE(star_second, "foo,*")
+
+#undef TEST_DONT_PARSE
+
+TEST(OutputsSpec, all) {
+ std::string_view str = "*";
+ OutputsSpec expected = OutputsSpec::All { };
+ ASSERT_EQ(OutputsSpec::parse(str), expected);
+ ASSERT_EQ(expected.to_string(), str);
+}
+
+TEST(OutputsSpec, names_out) {
+ std::string_view str = "out";
+ OutputsSpec expected = OutputsSpec::Names { "out" };
+ ASSERT_EQ(OutputsSpec::parse(str), expected);
+ ASSERT_EQ(expected.to_string(), str);
+}
+
+TEST(OutputsSpec, names_out_bin) {
+ OutputsSpec expected = OutputsSpec::Names { "out", "bin" };
+ ASSERT_EQ(OutputsSpec::parse("out,bin"), expected);
+ // N.B. This normalization is OK.
+ ASSERT_EQ(expected.to_string(), "bin,out");
+}
+
+#define TEST_SUBSET(X, THIS, THAT) \
+ X((OutputsSpec { THIS }).isSubsetOf(THAT));
+
+TEST(OutputsSpec, subsets_all_all) {
+ TEST_SUBSET(ASSERT_TRUE, OutputsSpec::All { }, OutputsSpec::All { });
+}
+
+TEST(OutputsSpec, subsets_names_all) {
+ TEST_SUBSET(ASSERT_TRUE, OutputsSpec::Names { "a" }, OutputsSpec::All { });
+}
+
+TEST(OutputsSpec, subsets_names_names_eq) {
+ TEST_SUBSET(ASSERT_TRUE, OutputsSpec::Names { "a" }, OutputsSpec::Names { "a" });
+}
+
+TEST(OutputsSpec, subsets_names_names_noneq) {
+ TEST_SUBSET(ASSERT_TRUE, OutputsSpec::Names { "a" }, (OutputsSpec::Names { "a", "b" }));
+}
+
+TEST(OutputsSpec, not_subsets_all_names) {
+ TEST_SUBSET(ASSERT_FALSE, OutputsSpec::All { }, OutputsSpec::Names { "a" });
+}
+
+TEST(OutputsSpec, not_subsets_names_names) {
+ TEST_SUBSET(ASSERT_FALSE, (OutputsSpec::Names { "a", "b" }), (OutputsSpec::Names { "a" }));
+}
+
+#undef TEST_SUBSET
+
+#define TEST_UNION(RES, THIS, THAT) \
+ ASSERT_EQ(OutputsSpec { RES }, (OutputsSpec { THIS }).union_(THAT));
+
+TEST(OutputsSpec, union_all_all) {
+ TEST_UNION(OutputsSpec::All { }, OutputsSpec::All { }, OutputsSpec::All { });
+}
+
+TEST(OutputsSpec, union_all_names) {
+ TEST_UNION(OutputsSpec::All { }, OutputsSpec::All { }, OutputsSpec::Names { "a" });
+}
+
+TEST(OutputsSpec, union_names_all) {
+ TEST_UNION(OutputsSpec::All { }, OutputsSpec::Names { "a" }, OutputsSpec::All { });
+}
+
+TEST(OutputsSpec, union_names_names) {
+ TEST_UNION((OutputsSpec::Names { "a", "b" }), OutputsSpec::Names { "a" }, OutputsSpec::Names { "b" });
+}
+
+#undef TEST_UNION
+
+#define TEST_DONT_PARSE(NAME, STR) \
+ TEST(ExtendedOutputsSpec, bad_ ## NAME) { \
+ std::optional extendedOutputsSpecOpt = \
+ ExtendedOutputsSpec::parseOpt(STR); \
+ ASSERT_FALSE(extendedOutputsSpecOpt); \
+ }
+
+TEST_DONT_PARSE(carot_empty, "^")
+TEST_DONT_PARSE(prefix_carot_empty, "foo^")
+TEST_DONT_PARSE(garbage, "^&*()")
+TEST_DONT_PARSE(double_star, "^**")
+TEST_DONT_PARSE(star_first, "^*,foo")
+TEST_DONT_PARSE(star_second, "^foo,*")
+
+#undef TEST_DONT_PARSE
+
+TEST(ExtendedOutputsSpec, defeault) {
+ std::string_view str = "foo";
+ auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(str);
+ ASSERT_EQ(prefix, "foo");
+ ExtendedOutputsSpec expected = ExtendedOutputsSpec::Default { };
+ ASSERT_EQ(extendedOutputsSpec, expected);
+ ASSERT_EQ(std::string { prefix } + expected.to_string(), str);
+}
+
+TEST(ExtendedOutputsSpec, all) {
+ std::string_view str = "foo^*";
+ auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(str);
+ ASSERT_EQ(prefix, "foo");
+ ExtendedOutputsSpec expected = OutputsSpec::All { };
+ ASSERT_EQ(extendedOutputsSpec, expected);
+ ASSERT_EQ(std::string { prefix } + expected.to_string(), str);
+}
+
+TEST(ExtendedOutputsSpec, out) {
+ std::string_view str = "foo^out";
+ auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(str);
+ ASSERT_EQ(prefix, "foo");
+ ExtendedOutputsSpec expected = OutputsSpec::Names { "out" };
+ ASSERT_EQ(extendedOutputsSpec, expected);
+ ASSERT_EQ(std::string { prefix } + expected.to_string(), str);
+}
+
+TEST(ExtendedOutputsSpec, out_bin) {
+ auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse("foo^out,bin");
+ ASSERT_EQ(prefix, "foo");
+ ExtendedOutputsSpec expected = OutputsSpec::Names { "out", "bin" };
+ ASSERT_EQ(extendedOutputsSpec, expected);
+ ASSERT_EQ(std::string { prefix } + expected.to_string(), "foo^bin,out");
+}
+
+TEST(ExtendedOutputsSpec, many_carrot) {
+ auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse("foo^bar^out,bin");
+ ASSERT_EQ(prefix, "foo^bar");
+ ExtendedOutputsSpec expected = OutputsSpec::Names { "out", "bin" };
+ ASSERT_EQ(extendedOutputsSpec, expected);
+ ASSERT_EQ(std::string { prefix } + expected.to_string(), "foo^bar^bin,out");
+}
+
+
+#define TEST_JSON(TYPE, NAME, STR, VAL) \
+ \
+ TEST(TYPE, NAME ## _to_json) { \
+ using nlohmann::literals::operator "" _json; \
+ ASSERT_EQ( \
+ STR ## _json, \
+ ((nlohmann::json) TYPE { VAL })); \
+ } \
+ \
+ TEST(TYPE, NAME ## _from_json) { \
+ using nlohmann::literals::operator "" _json; \
+ ASSERT_EQ( \
+ TYPE { VAL }, \
+ (STR ## _json).get<TYPE>()); \
+ }
+
+TEST_JSON(OutputsSpec, all, R"(["*"])", OutputsSpec::All { })
+TEST_JSON(OutputsSpec, name, R"(["a"])", OutputsSpec::Names { "a" })
+TEST_JSON(OutputsSpec, names, R"(["a","b"])", (OutputsSpec::Names { "a", "b" }))
+
+TEST_JSON(ExtendedOutputsSpec, def, R"(null)", ExtendedOutputsSpec::Default { })
+TEST_JSON(ExtendedOutputsSpec, all, R"(["*"])", ExtendedOutputsSpec::Explicit { OutputsSpec::All { } })
+TEST_JSON(ExtendedOutputsSpec, name, R"(["a"])", ExtendedOutputsSpec::Explicit { OutputsSpec::Names { "a" } })
+TEST_JSON(ExtendedOutputsSpec, names, R"(["a","b"])", (ExtendedOutputsSpec::Explicit { OutputsSpec::Names { "a", "b" } }))
+
+#undef TEST_JSON
+
+}
diff --git a/src/libstore/tests/path-with-outputs.cc b/src/libstore/tests/path-with-outputs.cc
deleted file mode 100644
index 350ea7ffd..000000000
--- a/src/libstore/tests/path-with-outputs.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-#include "path-with-outputs.hh"
-
-#include <gtest/gtest.h>
-
-namespace nix {
-
-TEST(parseOutputsSpec, basic)
-{
- {
- auto [prefix, outputsSpec] = parseOutputsSpec("foo");
- ASSERT_EQ(prefix, "foo");
- ASSERT_TRUE(std::get_if<DefaultOutputs>(&outputsSpec));
- }
-
- {
- auto [prefix, outputsSpec] = parseOutputsSpec("foo^*");
- ASSERT_EQ(prefix, "foo");
- ASSERT_TRUE(std::get_if<AllOutputs>(&outputsSpec));
- }
-
- {
- auto [prefix, outputsSpec] = parseOutputsSpec("foo^out");
- ASSERT_EQ(prefix, "foo");
- ASSERT_TRUE(std::get<OutputNames>(outputsSpec) == OutputNames({"out"}));
- }
-
- {
- auto [prefix, outputsSpec] = parseOutputsSpec("foo^out,bin");
- ASSERT_EQ(prefix, "foo");
- ASSERT_TRUE(std::get<OutputNames>(outputsSpec) == OutputNames({"out", "bin"}));
- }
-
- {
- auto [prefix, outputsSpec] = parseOutputsSpec("foo^bar^out,bin");
- ASSERT_EQ(prefix, "foo^bar");
- ASSERT_TRUE(std::get<OutputNames>(outputsSpec) == OutputNames({"out", "bin"}));
- }
-
- {
- auto [prefix, outputsSpec] = parseOutputsSpec("foo^&*()");
- ASSERT_EQ(prefix, "foo^&*()");
- ASSERT_TRUE(std::get_if<DefaultOutputs>(&outputsSpec));
- }
-}
-
-}