forked from lix-project/lix
Merge pull request #7543 from obsidiansystems/typed-string-context
Parse string context elements properly
This commit is contained in:
commit
a3ba80357d
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
#include "util.hh"
|
#include "util.hh"
|
||||||
#include "path.hh"
|
#include "path.hh"
|
||||||
#include "path-with-outputs.hh"
|
#include "outputs-spec.hh"
|
||||||
#include "derived-path.hh"
|
#include "derived-path.hh"
|
||||||
#include "eval.hh"
|
#include "eval.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
@ -20,7 +20,7 @@ namespace eval_cache { class EvalCache; class AttrCursor; }
|
||||||
|
|
||||||
struct App
|
struct App
|
||||||
{
|
{
|
||||||
std::vector<StorePathWithOutputs> context;
|
std::vector<DerivedPath> context;
|
||||||
Path program;
|
Path program;
|
||||||
// FIXME: add args, sandbox settings, metadata, ...
|
// FIXME: add args, sandbox settings, metadata, ...
|
||||||
};
|
};
|
||||||
|
|
|
@ -300,7 +300,7 @@ struct AttrDb
|
||||||
NixStringContext context;
|
NixStringContext context;
|
||||||
if (!queryAttribute.isNull(3))
|
if (!queryAttribute.isNull(3))
|
||||||
for (auto & s : tokenizeString<std::vector<std::string>>(queryAttribute.getStr(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}}};
|
return {{rowId, string_t{queryAttribute.getStr(2), context}}};
|
||||||
}
|
}
|
||||||
case AttrType::Bool:
|
case AttrType::Bool:
|
||||||
|
@ -592,7 +592,18 @@ string_t AttrCursor::getStringWithContext()
|
||||||
if (auto s = std::get_if<string_t>(&cachedValue->second)) {
|
if (auto s = std::get_if<string_t>(&cachedValue->second)) {
|
||||||
bool valid = true;
|
bool valid = true;
|
||||||
for (auto & c : s->second) {
|
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;
|
valid = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
void copyContext(const Value & v, PathSet & context)
|
||||||
{
|
{
|
||||||
if (v.string.context)
|
if (v.string.context)
|
||||||
|
@ -2103,7 +2082,7 @@ NixStringContext Value::getContext(const Store & store)
|
||||||
assert(internalType == tString);
|
assert(internalType == tString);
|
||||||
if (string.context)
|
if (string.context)
|
||||||
for (const char * * p = string.context; *p; ++p)
|
for (const char * * p = string.context; *p; ++p)
|
||||||
res.push_back(decodeContext(store, *p));
|
res.push_back(NixStringContextElem::parse(store, *p));
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -551,10 +551,6 @@ struct DebugTraceStacker {
|
||||||
std::string_view showType(ValueType type);
|
std::string_view showType(ValueType type);
|
||||||
std::string showType(const Value & v);
|
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". */
|
/* If `path' refers to a directory, then append "/default.nix". */
|
||||||
Path resolveExprPath(Path path);
|
Path resolveExprPath(Path path);
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
#include "hash.hh"
|
#include "hash.hh"
|
||||||
#include "fetchers.hh"
|
#include "fetchers.hh"
|
||||||
#include "path-with-outputs.hh"
|
#include "outputs-spec.hh"
|
||||||
|
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ libexpr_DIR := $(d)
|
||||||
|
|
||||||
libexpr_SOURCES := \
|
libexpr_SOURCES := \
|
||||||
$(wildcard $(d)/*.cc) \
|
$(wildcard $(d)/*.cc) \
|
||||||
|
$(wildcard $(d)/value/*.cc) \
|
||||||
$(wildcard $(d)/primops/*.cc) \
|
$(wildcard $(d)/primops/*.cc) \
|
||||||
$(wildcard $(d)/flake/*.cc) \
|
$(wildcard $(d)/flake/*.cc) \
|
||||||
$(d)/lexer-tab.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))
|
$(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), \
|
$(foreach i, $(wildcard src/libexpr/flake/*.hh), \
|
||||||
$(eval $(call install-file-in, $(i), $(includedir)/nix/flake, 0644)))
|
$(eval $(call install-file-in, $(i), $(includedir)/nix/flake, 0644)))
|
||||||
|
|
||||||
|
|
|
@ -43,16 +43,32 @@ StringMap EvalState::realiseContext(const PathSet & context)
|
||||||
std::vector<DerivedPath::Built> drvs;
|
std::vector<DerivedPath::Built> drvs;
|
||||||
StringMap res;
|
StringMap res;
|
||||||
|
|
||||||
for (auto & i : context) {
|
for (auto & c_ : context) {
|
||||||
auto [ctx, outputName] = decodeContext(*store, i);
|
auto ensureValid = [&](const StorePath & p) {
|
||||||
auto ctxS = store->printStorePath(ctx);
|
if (!store->isValidPath(p))
|
||||||
if (!store->isValidPath(ctx))
|
debugThrowLastTrace(InvalidPathError(store->printStorePath(p)));
|
||||||
debugThrowLastTrace(InvalidPathError(store->printStorePath(ctx)));
|
};
|
||||||
if (!outputName.empty() && ctx.isDerivation()) {
|
auto c = NixStringContextElem::parse(*store, c_);
|
||||||
drvs.push_back({ctx, {outputName}});
|
std::visit(overloaded {
|
||||||
} else {
|
[&](const NixStringContextElem::Built & b) {
|
||||||
res.insert_or_assign(ctxS, ctxS);
|
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 {};
|
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
|
/* Everything in the context of the strings in the derivation
|
||||||
attributes should be added as dependencies of the resulting
|
attributes should be added as dependencies of the resulting
|
||||||
derivation. */
|
derivation. */
|
||||||
for (auto & path : context) {
|
for (auto & c_ : context) {
|
||||||
|
auto c = NixStringContextElem::parse(*state.store, c_);
|
||||||
/* Paths marked with `=' denote that the path of a derivation
|
std::visit(overloaded {
|
||||||
is explicitly passed to the builder. Since that allows the
|
/* Since this allows the builder to gain access to every
|
||||||
builder to gain access to every path in the dependency
|
path in the dependency graph of the derivation (including
|
||||||
graph of the derivation (including all outputs), all paths
|
all outputs), all paths in the graph must be added to
|
||||||
in the graph must be added to this derivation's list of
|
this derivation's list of inputs to ensure that they are
|
||||||
inputs to ensure that they are available when the builder
|
available when the builder runs. */
|
||||||
runs. */
|
[&](const NixStringContextElem::DrvDeep & d) {
|
||||||
if (path.at(0) == '=') {
|
/* !!! This doesn't work if readOnlyMode is set. */
|
||||||
/* !!! This doesn't work if readOnlyMode is set. */
|
StorePathSet refs;
|
||||||
StorePathSet refs;
|
state.store->computeFSClosure(d.drvPath, refs);
|
||||||
state.store->computeFSClosure(state.store->parseStorePath(std::string_view(path).substr(1)), refs);
|
for (auto & j : refs) {
|
||||||
for (auto & j : refs) {
|
drv.inputSrcs.insert(j);
|
||||||
drv.inputSrcs.insert(j);
|
if (j.isDerivation())
|
||||||
if (j.isDerivation())
|
drv.inputDrvs[j] = state.store->readDerivation(j).outputNames();
|
||||||
drv.inputDrvs[j] = state.store->readDerivation(j).outputNames();
|
}
|
||||||
}
|
},
|
||||||
}
|
[&](const NixStringContextElem::Built & b) {
|
||||||
|
drv.inputDrvs[b.drvPath].insert(b.output);
|
||||||
/* Handle derivation outputs of the form ‘!<name>!<path>’. */
|
},
|
||||||
else if (path.at(0) == '!') {
|
[&](const NixStringContextElem::Opaque & o) {
|
||||||
auto ctx = decodeContext(*state.store, path);
|
drv.inputSrcs.insert(o.path);
|
||||||
drv.inputDrvs[ctx.first].insert(ctx.second);
|
},
|
||||||
}
|
}, c.raw());
|
||||||
|
|
||||||
/* Otherwise it's a source file. */
|
|
||||||
else
|
|
||||||
drv.inputSrcs.insert(state.store->parseStorePath(path));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Do we have all required attributes? */
|
/* Do we have all required attributes? */
|
||||||
|
|
|
@ -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");
|
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardOutputDependency");
|
||||||
|
|
||||||
PathSet context2;
|
PathSet context2;
|
||||||
for (auto & p : context)
|
for (auto && p : context) {
|
||||||
context2.insert(p.at(0) == '=' ? std::string(p, 1) : p);
|
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);
|
v.mkString(*s, context2);
|
||||||
}
|
}
|
||||||
|
@ -74,34 +81,22 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
|
||||||
};
|
};
|
||||||
PathSet context;
|
PathSet context;
|
||||||
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getContext");
|
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) {
|
for (const auto & p : context) {
|
||||||
Path drv;
|
Path drv;
|
||||||
std::string output;
|
std::string output;
|
||||||
const Path * path = &p;
|
NixStringContextElem ctx = NixStringContextElem::parse(*state.store, p);
|
||||||
if (p.at(0) == '=') {
|
std::visit(overloaded {
|
||||||
drv = std::string(p, 1);
|
[&](NixStringContextElem::DrvDeep & d) {
|
||||||
path = &drv;
|
contextInfos[d.drvPath].allOutputs = true;
|
||||||
} else if (p.at(0) == '!') {
|
},
|
||||||
NixStringContextElem ctx = decodeContext(*state.store, p);
|
[&](NixStringContextElem::Built & b) {
|
||||||
drv = state.store->printStorePath(ctx.first);
|
contextInfos[b.drvPath].outputs.emplace_back(std::move(output));
|
||||||
output = ctx.second;
|
},
|
||||||
path = &drv;
|
[&](NixStringContextElem::Opaque & o) {
|
||||||
}
|
contextInfos[o.path].path = true;
|
||||||
auto isPath = drv.empty();
|
},
|
||||||
auto isAllOutputs = (!drv.empty()) && output.empty();
|
}, ctx.raw());
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto attrs = state.buildBindings(contextInfos.size());
|
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))
|
for (const auto & [i, output] : enumerate(info.second.outputs))
|
||||||
(outputsVal.listElems()[i] = state.allocValue())->mkString(output);
|
(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);
|
v.mkAttrs(attrs);
|
||||||
|
|
|
@ -6,7 +6,9 @@ libexpr-tests_DIR := $(d)
|
||||||
|
|
||||||
libexpr-tests_INSTALL_DIR :=
|
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
|
libexpr-tests_CXXFLAGS += -I src/libexpr -I src/libutil -I src/libstore -I src/libexpr/tests
|
||||||
|
|
||||||
|
|
72
src/libexpr/tests/value/context.cc
Normal file
72
src/libexpr/tests/value/context.cc
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
#include "symbol-table.hh"
|
#include "symbol-table.hh"
|
||||||
|
#include "value/context.hh"
|
||||||
|
|
||||||
#if HAVE_BOEHMGC
|
#if HAVE_BOEHMGC
|
||||||
#include <gc/gc_allocator.h>
|
#include <gc/gc_allocator.h>
|
||||||
|
@ -67,8 +68,6 @@ class XMLWriter;
|
||||||
|
|
||||||
typedef int64_t NixInt;
|
typedef int64_t NixInt;
|
||||||
typedef double NixFloat;
|
typedef double NixFloat;
|
||||||
typedef std::pair<StorePath, std::string> NixStringContextElem;
|
|
||||||
typedef std::vector<NixStringContextElem> NixStringContext;
|
|
||||||
|
|
||||||
/* External values must descend from ExternalValueBase, so that
|
/* External values must descend from ExternalValueBase, so that
|
||||||
* type-agnostic nix functions (e.g. showType) can be implemented
|
* type-agnostic nix functions (e.g. showType) can be implemented
|
||||||
|
|
67
src/libexpr/value/context.cc
Normal file
67
src/libexpr/value/context.cc
Normal file
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
90
src/libexpr/value/context.hh
Normal file
90
src/libexpr/value/context.hh
Normal file
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -39,7 +39,6 @@ void printVersion(const std::string & programName);
|
||||||
void printGCWarning();
|
void printGCWarning();
|
||||||
|
|
||||||
class Store;
|
class Store;
|
||||||
struct StorePathWithOutputs;
|
|
||||||
|
|
||||||
void printMissing(
|
void printMissing(
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
|
|
61
src/libstore/outputs-spec.cc
Normal file
61
src/libstore/outputs-spec.cc
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
32
src/libstore/outputs-spec.hh
Normal file
32
src/libstore/outputs-spec.hh
Normal file
|
@ -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 &);
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
#include "path-with-outputs.hh"
|
#include "path-with-outputs.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "nlohmann/json.hpp"
|
|
||||||
|
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
|
||||||
|
@ -71,57 +70,4 @@ StorePathWithOutputs followLinksToStorePathWithOutputs(const Store & store, std:
|
||||||
return StorePathWithOutputs { store.followLinksToStorePath(path), std::move(outputs) };
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
#include "path.hh"
|
#include "path.hh"
|
||||||
#include "derived-path.hh"
|
#include "derived-path.hh"
|
||||||
#include "nlohmann/json_fwd.hpp"
|
#include "nlohmann/json_fwd.hpp"
|
||||||
|
|
||||||
namespace nix {
|
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
|
struct StorePathWithOutputs
|
||||||
{
|
{
|
||||||
StorePath path;
|
StorePath path;
|
||||||
|
@ -33,25 +38,4 @@ StorePathWithOutputs parsePathWithOutputs(const Store & store, std::string_view
|
||||||
|
|
||||||
StorePathWithOutputs followLinksToStorePathWithOutputs(const Store & store, std::string_view pathWithOutputs);
|
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 &);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#include "path-with-outputs.hh"
|
#include "outputs-spec.hh"
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
|
@ -79,9 +79,29 @@ UnresolvedApp Installable::toApp(EvalState & state)
|
||||||
if (type == "app") {
|
if (type == "app") {
|
||||||
auto [program, context] = cursor->getAttr("program")->getStringWithContext();
|
auto [program, context] = cursor->getAttr("program")->getStringWithContext();
|
||||||
|
|
||||||
std::vector<StorePathWithOutputs> context2;
|
std::vector<DerivedPath> context2;
|
||||||
for (auto & [path, name] : context)
|
for (auto & c : context) {
|
||||||
context2.push_back({path, {name}});
|
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 {
|
return UnresolvedApp{App {
|
||||||
.context = std::move(context2),
|
.context = std::move(context2),
|
||||||
|
@ -105,7 +125,10 @@ UnresolvedApp Installable::toApp(EvalState & state)
|
||||||
: DrvName(name).name;
|
: DrvName(name).name;
|
||||||
auto program = outPath + "/bin/" + mainProgram;
|
auto program = outPath + "/bin/" + mainProgram;
|
||||||
return UnresolvedApp { App {
|
return UnresolvedApp { App {
|
||||||
.context = { { drvPath, {outputName} } },
|
.context = { DerivedPath::Built {
|
||||||
|
.drvPath = drvPath,
|
||||||
|
.outputs = {outputName},
|
||||||
|
} },
|
||||||
.program = program,
|
.program = program,
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
@ -123,7 +146,7 @@ App UnresolvedApp::resolve(ref<Store> evalStore, ref<Store> store)
|
||||||
|
|
||||||
for (auto & ctxElt : unresolved.context)
|
for (auto & ctxElt : unresolved.context)
|
||||||
installableContext.push_back(
|
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);
|
auto builtContext = Installable::build(evalStore, store, Realise::Outputs, installableContext);
|
||||||
res.program = resolveString(*store, unresolved.program, builtContext);
|
res.program = resolveString(*store, unresolved.program, builtContext);
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
#include "common-args.hh"
|
#include "common-args.hh"
|
||||||
#include "shared.hh"
|
#include "shared.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "path-with-outputs.hh"
|
#include "outputs-spec.hh"
|
||||||
#include "derivations.hh"
|
#include "derivations.hh"
|
||||||
#include "progress-bar.hh"
|
#include "progress-bar.hh"
|
||||||
#include "run.hh"
|
#include "run.hh"
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
#include "get-drvs.hh"
|
#include "get-drvs.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "derivations.hh"
|
#include "derivations.hh"
|
||||||
#include "path-with-outputs.hh"
|
#include "outputs-spec.hh"
|
||||||
#include "attr-path.hh"
|
#include "attr-path.hh"
|
||||||
#include "fetchers.hh"
|
#include "fetchers.hh"
|
||||||
#include "registry.hh"
|
#include "registry.hh"
|
||||||
|
@ -348,7 +348,7 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
// FIXME
|
// FIXME
|
||||||
auto app = App(*state, v);
|
auto app = App(*state, v);
|
||||||
for (auto & i : app.context) {
|
for (auto & i : app.context) {
|
||||||
auto [drvPathS, outputName] = decodeContext(i);
|
auto [drvPathS, outputName] = NixStringContextElem::parse(i);
|
||||||
store->parseStorePath(drvPathS);
|
store->parseStorePath(drvPathS);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -8,4 +8,4 @@ libplugintest_ALLOW_UNDEFINED := 1
|
||||||
|
|
||||||
libplugintest_EXCLUDE_FROM_LIBRARY_LIST := 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
|
||||||
|
|
Loading…
Reference in a new issue