aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThéophane Hufschmitt <7226587+thufschmitt@users.noreply.github.com>2023-01-11 07:09:37 +0100
committerGitHub <noreply@github.com>2023-01-11 07:09:37 +0100
commita3ba80357d3a792eb1690011f253c64840c6ae72 (patch)
tree4f151ec354a73e44adf0131808fbab314f7fb3a9
parentf58c30111261a3ad50a6cb462cb2df7c49aa82e4 (diff)
parent5576d5e987e907bf13ae6c7fe79ececce4e86e2d (diff)
Merge pull request #7543 from obsidiansystems/typed-string-context
Parse string context elements properly
-rw-r--r--src/libcmd/installables.hh4
-rw-r--r--src/libexpr/eval-cache.cc15
-rw-r--r--src/libexpr/eval.cc23
-rw-r--r--src/libexpr/eval.hh4
-rw-r--r--src/libexpr/flake/flakeref.hh2
-rw-r--r--src/libexpr/local.mk3
-rw-r--r--src/libexpr/primops.cc90
-rw-r--r--src/libexpr/primops/context.cc51
-rw-r--r--src/libexpr/tests/local.mk4
-rw-r--r--src/libexpr/tests/value/context.cc72
-rw-r--r--src/libexpr/value.hh3
-rw-r--r--src/libexpr/value/context.cc67
-rw-r--r--src/libexpr/value/context.hh90
-rw-r--r--src/libmain/shared.hh1
-rw-r--r--src/libstore/outputs-spec.cc61
-rw-r--r--src/libstore/outputs-spec.hh32
-rw-r--r--src/libstore/path-with-outputs.cc54
-rw-r--r--src/libstore/path-with-outputs.hh30
-rw-r--r--src/libstore/tests/outputs-spec.cc (renamed from src/libstore/tests/path-with-outputs.cc)2
-rw-r--r--src/nix/app.cc33
-rw-r--r--src/nix/develop.cc2
-rw-r--r--src/nix/flake.cc4
-rw-r--r--tests/plugins/local.mk2
23 files changed, 460 insertions, 189 deletions
diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh
index 250cf7e83..9b92cc4be 100644
--- a/src/libcmd/installables.hh
+++ b/src/libcmd/installables.hh
@@ -2,7 +2,7 @@
#include "util.hh"
#include "path.hh"
-#include "path-with-outputs.hh"
+#include "outputs-spec.hh"
#include "derived-path.hh"
#include "eval.hh"
#include "store-api.hh"
@@ -20,7 +20,7 @@ namespace eval_cache { class EvalCache; class AttrCursor; }
struct App
{
- std::vector<StorePathWithOutputs> context;
+ std::vector<DerivedPath> context;
Path program;
// FIXME: add args, sandbox settings, metadata, ...
};
diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc
index afe575fee..1219b2471 100644
--- a/src/libexpr/eval-cache.cc
+++ b/src/libexpr/eval-cache.cc
@@ -300,7 +300,7 @@ struct AttrDb
NixStringContext context;
if (!queryAttribute.isNull(3))
for (auto & s : tokenizeString<std::vector<std::string>>(queryAttribute.getStr(3), ";"))
- context.push_back(decodeContext(cfg, s));
+ context.push_back(NixStringContextElem::parse(cfg, s));
return {{rowId, string_t{queryAttribute.getStr(2), context}}};
}
case AttrType::Bool:
@@ -592,7 +592,18 @@ string_t AttrCursor::getStringWithContext()
if (auto s = std::get_if<string_t>(&cachedValue->second)) {
bool valid = true;
for (auto & c : s->second) {
- if (!root->state.store->isValidPath(c.first)) {
+ const StorePath & path = std::visit(overloaded {
+ [&](const NixStringContextElem::DrvDeep & d) -> const StorePath & {
+ return d.drvPath;
+ },
+ [&](const NixStringContextElem::Built & b) -> const StorePath & {
+ return b.drvPath;
+ },
+ [&](const NixStringContextElem::Opaque & o) -> const StorePath & {
+ return o.path;
+ },
+ }, c.raw());
+ if (!root->state.store->isValidPath(path)) {
valid = false;
break;
}
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 978b0f0e2..277cbb5f9 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -2068,27 +2068,6 @@ std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string
}
-/* Decode a context string ‘!<name>!<path>’ into a pair <path,
- name>. */
-NixStringContextElem decodeContext(const Store & store, std::string_view s)
-{
- if (s.at(0) == '!') {
- size_t index = s.find("!", 1);
- return {
- store.parseStorePath(s.substr(index + 1)),
- std::string(s.substr(1, index - 1)),
- };
- } else
- return {
- store.parseStorePath(
- s.at(0) == '/'
- ? s
- : s.substr(1)),
- "",
- };
-}
-
-
void copyContext(const Value & v, PathSet & context)
{
if (v.string.context)
@@ -2103,7 +2082,7 @@ NixStringContext Value::getContext(const Store & store)
assert(internalType == tString);
if (string.context)
for (const char * * p = string.context; *p; ++p)
- res.push_back(decodeContext(store, *p));
+ res.push_back(NixStringContextElem::parse(store, *p));
return res;
}
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index 4e0c4db95..46b8cbaa5 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -551,10 +551,6 @@ struct DebugTraceStacker {
std::string_view showType(ValueType type);
std::string showType(const Value & v);
-/* Decode a context string ‘!<name>!<path>’ into a pair <path,
- name>. */
-NixStringContextElem decodeContext(const Store & store, std::string_view s);
-
/* If `path' refers to a directory, then append "/default.nix". */
Path resolveExprPath(Path path);
diff --git a/src/libexpr/flake/flakeref.hh b/src/libexpr/flake/flakeref.hh
index a36d852a8..4ec79fb73 100644
--- a/src/libexpr/flake/flakeref.hh
+++ b/src/libexpr/flake/flakeref.hh
@@ -3,7 +3,7 @@
#include "types.hh"
#include "hash.hh"
#include "fetchers.hh"
-#include "path-with-outputs.hh"
+#include "outputs-spec.hh"
#include <variant>
diff --git a/src/libexpr/local.mk b/src/libexpr/local.mk
index 016631647..2171e769b 100644
--- a/src/libexpr/local.mk
+++ b/src/libexpr/local.mk
@@ -6,6 +6,7 @@ libexpr_DIR := $(d)
libexpr_SOURCES := \
$(wildcard $(d)/*.cc) \
+ $(wildcard $(d)/value/*.cc) \
$(wildcard $(d)/primops/*.cc) \
$(wildcard $(d)/flake/*.cc) \
$(d)/lexer-tab.cc \
@@ -37,6 +38,8 @@ clean-files += $(d)/parser-tab.cc $(d)/parser-tab.hh $(d)/lexer-tab.cc $(d)/lexe
$(eval $(call install-file-in, $(d)/nix-expr.pc, $(libdir)/pkgconfig, 0644))
+$(foreach i, $(wildcard src/libexpr/value/*.hh), \
+ $(eval $(call install-file-in, $(i), $(includedir)/nix/value, 0644)))
$(foreach i, $(wildcard src/libexpr/flake/*.hh), \
$(eval $(call install-file-in, $(i), $(includedir)/nix/flake, 0644)))
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index fc1524599..a08fef011 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -43,16 +43,32 @@ StringMap EvalState::realiseContext(const PathSet & context)
std::vector<DerivedPath::Built> drvs;
StringMap res;
- for (auto & i : context) {
- auto [ctx, outputName] = decodeContext(*store, i);
- auto ctxS = store->printStorePath(ctx);
- if (!store->isValidPath(ctx))
- debugThrowLastTrace(InvalidPathError(store->printStorePath(ctx)));
- if (!outputName.empty() && ctx.isDerivation()) {
- drvs.push_back({ctx, {outputName}});
- } else {
- res.insert_or_assign(ctxS, ctxS);
- }
+ for (auto & c_ : context) {
+ auto ensureValid = [&](const StorePath & p) {
+ if (!store->isValidPath(p))
+ debugThrowLastTrace(InvalidPathError(store->printStorePath(p)));
+ };
+ auto c = NixStringContextElem::parse(*store, c_);
+ std::visit(overloaded {
+ [&](const NixStringContextElem::Built & b) {
+ drvs.push_back(DerivedPath::Built {
+ .drvPath = b.drvPath,
+ .outputs = std::set { b.output },
+ });
+ ensureValid(b.drvPath);
+ },
+ [&](const NixStringContextElem::Opaque & o) {
+ auto ctxS = store->printStorePath(o.path);
+ res.insert_or_assign(ctxS, ctxS);
+ ensureValid(o.path);
+ },
+ [&](const NixStringContextElem::DrvDeep & d) {
+ /* Treat same as Opaque */
+ auto ctxS = store->printStorePath(d.drvPath);
+ res.insert_or_assign(ctxS, ctxS);
+ ensureValid(d.drvPath);
+ },
+ }, c.raw());
}
if (drvs.empty()) return {};
@@ -1180,35 +1196,31 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
/* Everything in the context of the strings in the derivation
attributes should be added as dependencies of the resulting
derivation. */
- for (auto & path : context) {
-
- /* Paths marked with `=' denote that the path of a derivation
- is explicitly passed to the builder. Since that allows the
- builder to gain access to every path in the dependency
- graph of the derivation (including all outputs), all paths
- in the graph must be added to this derivation's list of
- inputs to ensure that they are available when the builder
- runs. */
- if (path.at(0) == '=') {
- /* !!! This doesn't work if readOnlyMode is set. */
- StorePathSet refs;
- state.store->computeFSClosure(state.store->parseStorePath(std::string_view(path).substr(1)), refs);
- for (auto & j : refs) {
- drv.inputSrcs.insert(j);
- if (j.isDerivation())
- drv.inputDrvs[j] = state.store->readDerivation(j).outputNames();
- }
- }
-
- /* Handle derivation outputs of the form ‘!<name>!<path>’. */
- else if (path.at(0) == '!') {
- auto ctx = decodeContext(*state.store, path);
- drv.inputDrvs[ctx.first].insert(ctx.second);
- }
-
- /* Otherwise it's a source file. */
- else
- drv.inputSrcs.insert(state.store->parseStorePath(path));
+ for (auto & c_ : context) {
+ auto c = NixStringContextElem::parse(*state.store, c_);
+ std::visit(overloaded {
+ /* Since this allows the builder to gain access to every
+ path in the dependency graph of the derivation (including
+ all outputs), all paths in the graph must be added to
+ this derivation's list of inputs to ensure that they are
+ available when the builder runs. */
+ [&](const NixStringContextElem::DrvDeep & d) {
+ /* !!! This doesn't work if readOnlyMode is set. */
+ StorePathSet refs;
+ state.store->computeFSClosure(d.drvPath, refs);
+ for (auto & j : refs) {
+ drv.inputSrcs.insert(j);
+ if (j.isDerivation())
+ drv.inputDrvs[j] = state.store->readDerivation(j).outputNames();
+ }
+ },
+ [&](const NixStringContextElem::Built & b) {
+ drv.inputDrvs[b.drvPath].insert(b.output);
+ },
+ [&](const NixStringContextElem::Opaque & o) {
+ drv.inputSrcs.insert(o.path);
+ },
+ }, c.raw());
}
/* Do we have all required attributes? */
diff --git a/src/libexpr/primops/context.cc b/src/libexpr/primops/context.cc
index 9fae0b14d..0c65a6b98 100644
--- a/src/libexpr/primops/context.cc
+++ b/src/libexpr/primops/context.cc
@@ -37,8 +37,15 @@ static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx p
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardOutputDependency");
PathSet context2;
- for (auto & p : context)
- context2.insert(p.at(0) == '=' ? std::string(p, 1) : p);
+ for (auto && p : context) {
+ auto c = NixStringContextElem::parse(*state.store, p);
+ if (auto * ptr = std::get_if<NixStringContextElem::DrvDeep>(&c)) {
+ context2.emplace(state.store->printStorePath(ptr->drvPath));
+ } else {
+ /* Can reuse original item */
+ context2.emplace(std::move(p));
+ }
+ }
v.mkString(*s, context2);
}
@@ -74,34 +81,22 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
};
PathSet context;
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getContext");
- auto contextInfos = std::map<Path, ContextInfo>();
+ auto contextInfos = std::map<StorePath, ContextInfo>();
for (const auto & p : context) {
Path drv;
std::string output;
- const Path * path = &p;
- if (p.at(0) == '=') {
- drv = std::string(p, 1);
- path = &drv;
- } else if (p.at(0) == '!') {
- NixStringContextElem ctx = decodeContext(*state.store, p);
- drv = state.store->printStorePath(ctx.first);
- output = ctx.second;
- path = &drv;
- }
- auto isPath = drv.empty();
- auto isAllOutputs = (!drv.empty()) && output.empty();
-
- auto iter = contextInfos.find(*path);
- if (iter == contextInfos.end()) {
- contextInfos.emplace(*path, ContextInfo{isPath, isAllOutputs, output.empty() ? Strings{} : Strings{std::move(output)}});
- } else {
- if (isPath)
- iter->second.path = true;
- else if (isAllOutputs)
- iter->second.allOutputs = true;
- else
- iter->second.outputs.emplace_back(std::move(output));
- }
+ NixStringContextElem ctx = NixStringContextElem::parse(*state.store, p);
+ std::visit(overloaded {
+ [&](NixStringContextElem::DrvDeep & d) {
+ contextInfos[d.drvPath].allOutputs = true;
+ },
+ [&](NixStringContextElem::Built & b) {
+ contextInfos[b.drvPath].outputs.emplace_back(std::move(output));
+ },
+ [&](NixStringContextElem::Opaque & o) {
+ contextInfos[o.path].path = true;
+ },
+ }, ctx.raw());
}
auto attrs = state.buildBindings(contextInfos.size());
@@ -120,7 +115,7 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
for (const auto & [i, output] : enumerate(info.second.outputs))
(outputsVal.listElems()[i] = state.allocValue())->mkString(output);
}
- attrs.alloc(info.first).mkAttrs(infoAttrs);
+ attrs.alloc(state.store->printStorePath(info.first)).mkAttrs(infoAttrs);
}
v.mkAttrs(attrs);
diff --git a/src/libexpr/tests/local.mk b/src/libexpr/tests/local.mk
index b95980cab..e483575a4 100644
--- a/src/libexpr/tests/local.mk
+++ b/src/libexpr/tests/local.mk
@@ -6,7 +6,9 @@ libexpr-tests_DIR := $(d)
libexpr-tests_INSTALL_DIR :=
-libexpr-tests_SOURCES := $(wildcard $(d)/*.cc)
+libexpr-tests_SOURCES := \
+ $(wildcard $(d)/*.cc) \
+ $(wildcard $(d)/value/*.cc)
libexpr-tests_CXXFLAGS += -I src/libexpr -I src/libutil -I src/libstore -I src/libexpr/tests
diff --git a/src/libexpr/tests/value/context.cc b/src/libexpr/tests/value/context.cc
new file mode 100644
index 000000000..d5c9d3bce
--- /dev/null
+++ b/src/libexpr/tests/value/context.cc
@@ -0,0 +1,72 @@
+#include "value/context.hh"
+
+#include "libexprtests.hh"
+
+namespace nix {
+
+// Testing of trivial expressions
+struct NixStringContextElemTest : public LibExprTest {
+ const Store & store() const {
+ return *LibExprTest::store;
+ }
+};
+
+TEST_F(NixStringContextElemTest, empty_invalid) {
+ EXPECT_THROW(
+ NixStringContextElem::parse(store(), ""),
+ BadNixStringContextElem);
+}
+
+TEST_F(NixStringContextElemTest, single_bang_invalid) {
+ EXPECT_THROW(
+ NixStringContextElem::parse(store(), "!"),
+ BadNixStringContextElem);
+}
+
+TEST_F(NixStringContextElemTest, double_bang_invalid) {
+ EXPECT_THROW(
+ NixStringContextElem::parse(store(), "!!/"),
+ BadStorePath);
+}
+
+TEST_F(NixStringContextElemTest, eq_slash_invalid) {
+ EXPECT_THROW(
+ NixStringContextElem::parse(store(), "=/"),
+ BadStorePath);
+}
+
+TEST_F(NixStringContextElemTest, slash_invalid) {
+ EXPECT_THROW(
+ NixStringContextElem::parse(store(), "/"),
+ BadStorePath);
+}
+
+TEST_F(NixStringContextElemTest, opaque) {
+ std::string_view opaque = "/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x";
+ auto elem = NixStringContextElem::parse(store(), opaque);
+ auto * p = std::get_if<NixStringContextElem::Opaque>(&elem);
+ ASSERT_TRUE(p);
+ ASSERT_EQ(p->path, store().parseStorePath(opaque));
+ ASSERT_EQ(elem.to_string(store()), opaque);
+}
+
+TEST_F(NixStringContextElemTest, drvDeep) {
+ std::string_view drvDeep = "=/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv";
+ auto elem = NixStringContextElem::parse(store(), drvDeep);
+ auto * p = std::get_if<NixStringContextElem::DrvDeep>(&elem);
+ ASSERT_TRUE(p);
+ ASSERT_EQ(p->drvPath, store().parseStorePath(drvDeep.substr(1)));
+ ASSERT_EQ(elem.to_string(store()), drvDeep);
+}
+
+TEST_F(NixStringContextElemTest, built) {
+ std::string_view built = "!foo!/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv";
+ auto elem = NixStringContextElem::parse(store(), built);
+ auto * p = std::get_if<NixStringContextElem::Built>(&elem);
+ ASSERT_TRUE(p);
+ ASSERT_EQ(p->output, "foo");
+ ASSERT_EQ(p->drvPath, store().parseStorePath(built.substr(5)));
+ ASSERT_EQ(elem.to_string(store()), built);
+}
+
+}
diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh
index f57597cff..7d3f6d700 100644
--- a/src/libexpr/value.hh
+++ b/src/libexpr/value.hh
@@ -3,6 +3,7 @@
#include <cassert>
#include "symbol-table.hh"
+#include "value/context.hh"
#if HAVE_BOEHMGC
#include <gc/gc_allocator.h>
@@ -67,8 +68,6 @@ class XMLWriter;
typedef int64_t NixInt;
typedef double NixFloat;
-typedef std::pair<StorePath, std::string> NixStringContextElem;
-typedef std::vector<NixStringContextElem> NixStringContext;
/* External values must descend from ExternalValueBase, so that
* type-agnostic nix functions (e.g. showType) can be implemented
diff --git a/src/libexpr/value/context.cc b/src/libexpr/value/context.cc
new file mode 100644
index 000000000..61d9c53df
--- /dev/null
+++ b/src/libexpr/value/context.cc
@@ -0,0 +1,67 @@
+#include "value/context.hh"
+#include "store-api.hh"
+
+#include <optional>
+
+namespace nix {
+
+NixStringContextElem NixStringContextElem::parse(const Store & store, std::string_view s0)
+{
+ std::string_view s = s0;
+
+ if (s.size() == 0) {
+ throw BadNixStringContextElem(s0,
+ "String context element should never be an empty string");
+ }
+ switch (s.at(0)) {
+ case '!': {
+ s = s.substr(1); // advance string to parse after first !
+ size_t index = s.find("!");
+ // This makes index + 1 safe. Index can be the length (one after index
+ // of last character), so given any valid character index --- a
+ // successful find --- we can add one.
+ if (index == std::string_view::npos) {
+ throw BadNixStringContextElem(s0,
+ "String content element beginning with '!' should have a second '!'");
+ }
+ return NixStringContextElem::Built {
+ .drvPath = store.parseStorePath(s.substr(index + 1)),
+ .output = std::string(s.substr(0, index)),
+ };
+ }
+ case '=': {
+ return NixStringContextElem::DrvDeep {
+ .drvPath = store.parseStorePath(s.substr(1)),
+ };
+ }
+ default: {
+ return NixStringContextElem::Opaque {
+ .path = store.parseStorePath(s),
+ };
+ }
+ }
+}
+
+std::string NixStringContextElem::to_string(const Store & store) const {
+ return std::visit(overloaded {
+ [&](const NixStringContextElem::Built & b) {
+ std::string res;
+ res += '!';
+ res += b.output;
+ res += '!';
+ res += store.printStorePath(b.drvPath);
+ return res;
+ },
+ [&](const NixStringContextElem::DrvDeep & d) {
+ std::string res;
+ res += '=';
+ res += store.printStorePath(d.drvPath);
+ return res;
+ },
+ [&](const NixStringContextElem::Opaque & o) {
+ return store.printStorePath(o.path);
+ },
+ }, raw());
+}
+
+}
diff --git a/src/libexpr/value/context.hh b/src/libexpr/value/context.hh
new file mode 100644
index 000000000..d8008c436
--- /dev/null
+++ b/src/libexpr/value/context.hh
@@ -0,0 +1,90 @@
+#pragma once
+
+#include "util.hh"
+#include "path.hh"
+
+#include <optional>
+
+#include <nlohmann/json_fwd.hpp>
+
+namespace nix {
+
+class BadNixStringContextElem : public Error
+{
+public:
+ std::string_view raw;
+
+ template<typename... Args>
+ BadNixStringContextElem(std::string_view raw_, const Args & ... args)
+ : Error("")
+ {
+ raw = raw_;
+ auto hf = hintfmt(args...);
+ err.msg = hintfmt("Bad String Context element: %1%: %2%", normaltxt(hf.str()), raw);
+ }
+};
+
+class Store;
+
+/* Plain opaque path to some store object.
+
+ Encoded as just the path: ‘<path>’.
+*/
+struct NixStringContextElem_Opaque {
+ StorePath path;
+};
+
+/* Path to a derivation and its entire build closure.
+
+ The path doesn't just refer to derivation itself and its closure, but
+ also all outputs of all derivations in that closure (including the
+ root derivation).
+
+ Encoded in the form ‘=<drvPath>’.
+*/
+struct NixStringContextElem_DrvDeep {
+ StorePath drvPath;
+};
+
+/* Derivation output.
+
+ Encoded in the form ‘!<output>!<drvPath>’.
+*/
+struct NixStringContextElem_Built {
+ StorePath drvPath;
+ std::string output;
+};
+
+using _NixStringContextElem_Raw = std::variant<
+ NixStringContextElem_Opaque,
+ NixStringContextElem_DrvDeep,
+ NixStringContextElem_Built
+>;
+
+struct NixStringContextElem : _NixStringContextElem_Raw {
+ using Raw = _NixStringContextElem_Raw;
+ using Raw::Raw;
+
+ using Opaque = NixStringContextElem_Opaque;
+ using DrvDeep = NixStringContextElem_DrvDeep;
+ using Built = NixStringContextElem_Built;
+
+ inline const Raw & raw() const {
+ return static_cast<const Raw &>(*this);
+ }
+ inline Raw & raw() {
+ return static_cast<Raw &>(*this);
+ }
+
+ /* Decode a context string, one of:
+ - ‘<path>’
+ - ‘=<path>’
+ - ‘!<name>!<path>’
+ */
+ static NixStringContextElem parse(const Store & store, std::string_view s);
+ std::string to_string(const Store & store) const;
+};
+
+typedef std::vector<NixStringContextElem> NixStringContext;
+
+}
diff --git a/src/libmain/shared.hh b/src/libmain/shared.hh
index 3c37fd627..1715374a6 100644
--- a/src/libmain/shared.hh
+++ b/src/libmain/shared.hh
@@ -39,7 +39,6 @@ void printVersion(const std::string & programName);
void printGCWarning();
class Store;
-struct StorePathWithOutputs;
void printMissing(
ref<Store> store,
diff --git a/src/libstore/outputs-spec.cc b/src/libstore/outputs-spec.cc
new file mode 100644
index 000000000..76779d193
--- /dev/null
+++ b/src/libstore/outputs-spec.cc
@@ -0,0 +1,61 @@
+#include "outputs-spec.hh"
+#include "nlohmann/json.hpp"
+
+#include <regex>
+
+namespace nix {
+
+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/outputs-spec.hh b/src/libstore/outputs-spec.hh
new file mode 100644
index 000000000..e2cf1d12b
--- /dev/null
+++ b/src/libstore/outputs-spec.hh
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <variant>
+
+#include "util.hh"
+
+#include "nlohmann/json_fwd.hpp"
+
+namespace nix {
+
+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-with-outputs.cc b/src/libstore/path-with-outputs.cc
index d6d67ea05..17230ffc4 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>
@@ -71,57 +70,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..ed55cc333 100644
--- a/src/libstore/path-with-outputs.hh
+++ b/src/libstore/path-with-outputs.hh
@@ -1,13 +1,18 @@
#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 +38,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/tests/path-with-outputs.cc b/src/libstore/tests/outputs-spec.cc
index 350ea7ffd..d781a930e 100644
--- a/src/libstore/tests/path-with-outputs.cc
+++ b/src/libstore/tests/outputs-spec.cc
@@ -1,4 +1,4 @@
-#include "path-with-outputs.hh"
+#include "outputs-spec.hh"
#include <gtest/gtest.h>
diff --git a/src/nix/app.cc b/src/nix/app.cc
index a8d7e115b..c9637dcf5 100644
--- a/src/nix/app.cc
+++ b/src/nix/app.cc
@@ -79,9 +79,29 @@ UnresolvedApp Installable::toApp(EvalState & state)
if (type == "app") {
auto [program, context] = cursor->getAttr("program")->getStringWithContext();
- std::vector<StorePathWithOutputs> context2;
- for (auto & [path, name] : context)
- context2.push_back({path, {name}});
+ std::vector<DerivedPath> context2;
+ for (auto & c : context) {
+ context2.emplace_back(std::visit(overloaded {
+ [&](const NixStringContextElem::DrvDeep & d) -> DerivedPath {
+ /* We want all outputs of the drv */
+ return DerivedPath::Built {
+ .drvPath = d.drvPath,
+ .outputs = {},
+ };
+ },
+ [&](const NixStringContextElem::Built & b) -> DerivedPath {
+ return DerivedPath::Built {
+ .drvPath = b.drvPath,
+ .outputs = { b.output },
+ };
+ },
+ [&](const NixStringContextElem::Opaque & o) -> DerivedPath {
+ return DerivedPath::Opaque {
+ .path = o.path,
+ };
+ },
+ }, c.raw()));
+ }
return UnresolvedApp{App {
.context = std::move(context2),
@@ -105,7 +125,10 @@ UnresolvedApp Installable::toApp(EvalState & state)
: DrvName(name).name;
auto program = outPath + "/bin/" + mainProgram;
return UnresolvedApp { App {
- .context = { { drvPath, {outputName} } },
+ .context = { DerivedPath::Built {
+ .drvPath = drvPath,
+ .outputs = {outputName},
+ } },
.program = program,
}};
}
@@ -123,7 +146,7 @@ App UnresolvedApp::resolve(ref<Store> evalStore, ref<Store> store)
for (auto & ctxElt : unresolved.context)
installableContext.push_back(
- std::make_shared<InstallableDerivedPath>(store, ctxElt.toDerivedPath()));
+ std::make_shared<InstallableDerivedPath>(store, ctxElt));
auto builtContext = Installable::build(evalStore, store, Realise::Outputs, installableContext);
res.program = resolveString(*store, unresolved.program, builtContext);
diff --git a/src/nix/develop.cc b/src/nix/develop.cc
index 1d90d1dac..6aa675386 100644
--- a/src/nix/develop.cc
+++ b/src/nix/develop.cc
@@ -3,7 +3,7 @@
#include "common-args.hh"
#include "shared.hh"
#include "store-api.hh"
-#include "path-with-outputs.hh"
+#include "outputs-spec.hh"
#include "derivations.hh"
#include "progress-bar.hh"
#include "run.hh"
diff --git a/src/nix/flake.cc b/src/nix/flake.cc
index 9b4cdf35a..33ce3f401 100644
--- a/src/nix/flake.cc
+++ b/src/nix/flake.cc
@@ -7,7 +7,7 @@
#include "get-drvs.hh"
#include "store-api.hh"
#include "derivations.hh"
-#include "path-with-outputs.hh"
+#include "outputs-spec.hh"
#include "attr-path.hh"
#include "fetchers.hh"
#include "registry.hh"
@@ -348,7 +348,7 @@ struct CmdFlakeCheck : FlakeCommand
// FIXME
auto app = App(*state, v);
for (auto & i : app.context) {
- auto [drvPathS, outputName] = decodeContext(i);
+ auto [drvPathS, outputName] = NixStringContextElem::parse(i);
store->parseStorePath(drvPathS);
}
#endif
diff --git a/tests/plugins/local.mk b/tests/plugins/local.mk
index 82ad99402..8182a6a83 100644
--- a/tests/plugins/local.mk
+++ b/tests/plugins/local.mk
@@ -8,4 +8,4 @@ libplugintest_ALLOW_UNDEFINED := 1
libplugintest_EXCLUDE_FROM_LIBRARY_LIST := 1
-libplugintest_CXXFLAGS := -I src/libutil -I src/libexpr
+libplugintest_CXXFLAGS := -I src/libutil -I src/libstore -I src/libexpr