Prior to this change, we had a bunch of ad-hoc string manipulation code scattered around. This made it hard to figure out what data model for string contexts is. Now, we still store string contexts most of the time as encoded strings --- I was wary of the performance implications of changing that --- but whenever we parse them we do so only through the `NixStringContextElem::parse` method, which handles all cases. This creates a data type that is very similar to `DerivedPath` but: - Represents the funky `=<drvpath>` case as properly distinct from the others. - Only encodes a single output, no wildcards and no set, for the "built" case. (I would like to deprecate `=<path>`, after which we are in spitting distance of `DerivedPath` and could maybe get away with fewer types, but that is another topic for another day.)
159 lines
4.9 KiB
C++
159 lines
4.9 KiB
C++
#include "installables.hh"
|
|
#include "store-api.hh"
|
|
#include "eval-inline.hh"
|
|
#include "eval-cache.hh"
|
|
#include "names.hh"
|
|
#include "command.hh"
|
|
#include "derivations.hh"
|
|
|
|
namespace nix {
|
|
|
|
struct InstallableDerivedPath : Installable
|
|
{
|
|
ref<Store> store;
|
|
const DerivedPath derivedPath;
|
|
|
|
InstallableDerivedPath(ref<Store> store, const DerivedPath & derivedPath)
|
|
: store(store)
|
|
, derivedPath(derivedPath)
|
|
{
|
|
}
|
|
|
|
std::string what() const override { return derivedPath.to_string(*store); }
|
|
|
|
DerivedPathsWithInfo toDerivedPaths() override
|
|
{
|
|
return {{derivedPath}};
|
|
}
|
|
|
|
std::optional<StorePath> getStorePath() override
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Return the rewrites that are needed to resolve a string whose context is
|
|
* included in `dependencies`.
|
|
*/
|
|
StringPairs resolveRewrites(
|
|
Store & store,
|
|
const std::vector<BuiltPathWithResult> & dependencies)
|
|
{
|
|
StringPairs res;
|
|
for (auto & dep : dependencies)
|
|
if (auto drvDep = std::get_if<BuiltPathBuilt>(&dep.path))
|
|
for (auto & [ outputName, outputPath ] : drvDep->outputs)
|
|
res.emplace(
|
|
downstreamPlaceholder(store, drvDep->drvPath, outputName),
|
|
store.printStorePath(outputPath)
|
|
);
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* Resolve the given string assuming the given context.
|
|
*/
|
|
std::string resolveString(
|
|
Store & store,
|
|
const std::string & toResolve,
|
|
const std::vector<BuiltPathWithResult> & dependencies)
|
|
{
|
|
auto rewrites = resolveRewrites(store, dependencies);
|
|
return rewriteStrings(toResolve, rewrites);
|
|
}
|
|
|
|
UnresolvedApp Installable::toApp(EvalState & state)
|
|
{
|
|
auto cursor = getCursor(state);
|
|
auto attrPath = cursor->getAttrPath();
|
|
|
|
auto type = cursor->getAttr("type")->getString();
|
|
|
|
std::string expected = !attrPath.empty() &&
|
|
(state.symbols[attrPath[0]] == "apps" || state.symbols[attrPath[0]] == "defaultApp")
|
|
? "app" : "derivation";
|
|
if (type != expected)
|
|
throw Error("attribute '%s' should have type '%s'", cursor->getAttrPathStr(), expected);
|
|
|
|
if (type == "app") {
|
|
auto [program, context] = cursor->getAttr("program")->getStringWithContext();
|
|
|
|
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),
|
|
.program = program,
|
|
}};
|
|
}
|
|
|
|
else if (type == "derivation") {
|
|
auto drvPath = cursor->forceDerivation();
|
|
auto outPath = cursor->getAttr(state.sOutPath)->getString();
|
|
auto outputName = cursor->getAttr(state.sOutputName)->getString();
|
|
auto name = cursor->getAttr(state.sName)->getString();
|
|
auto aPname = cursor->maybeGetAttr("pname");
|
|
auto aMeta = cursor->maybeGetAttr(state.sMeta);
|
|
auto aMainProgram = aMeta ? aMeta->maybeGetAttr("mainProgram") : nullptr;
|
|
auto mainProgram =
|
|
aMainProgram
|
|
? aMainProgram->getString()
|
|
: aPname
|
|
? aPname->getString()
|
|
: DrvName(name).name;
|
|
auto program = outPath + "/bin/" + mainProgram;
|
|
return UnresolvedApp { App {
|
|
.context = { DerivedPath::Built {
|
|
.drvPath = drvPath,
|
|
.outputs = {outputName},
|
|
} },
|
|
.program = program,
|
|
}};
|
|
}
|
|
|
|
else
|
|
throw Error("attribute '%s' has unsupported type '%s'", cursor->getAttrPathStr(), type);
|
|
}
|
|
|
|
// FIXME: move to libcmd
|
|
App UnresolvedApp::resolve(ref<Store> evalStore, ref<Store> store)
|
|
{
|
|
auto res = unresolved;
|
|
|
|
std::vector<std::shared_ptr<Installable>> installableContext;
|
|
|
|
for (auto & ctxElt : unresolved.context)
|
|
installableContext.push_back(
|
|
std::make_shared<InstallableDerivedPath>(store, ctxElt));
|
|
|
|
auto builtContext = Installable::build(evalStore, store, Realise::Outputs, installableContext);
|
|
res.program = resolveString(*store, unresolved.program, builtContext);
|
|
if (!store->isInStore(res.program))
|
|
throw Error("app program '%s' is not in the Nix store", res.program);
|
|
|
|
return res;
|
|
}
|
|
|
|
}
|