Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions src/libcmd/flake-schemas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
#include "nix/util/memory-source-accessor.hh"
#include "nix/util/mounted-source-accessor.hh"
#include "nix/flake/provenance.hh"
#include "nix/expr/parallel-eval.hh"
#include "nix/store/globals.hh"

#include <nlohmann/json.hpp>

namespace nix::flake_schemas {

Expand Down Expand Up @@ -360,6 +364,113 @@ Schemas getSchemas(ref<AttrCursor> inventory)
return schemas;
}

nlohmann::json getFlakeInventory(
EvalState & state,
Store & evalStore,
LockedFlake & flake,
ref<eval_cache::EvalCache> cache,
const FlakeInventoryOptions & options)
{
auto inventory = cache->getRoot()->getAttr("inventory");
auto outputs = cache->getRoot()->getAttr("outputs");

auto localSystem = std::string(settings.thisSystem.get());

FutureVector futures(*state.executor);

std::function<void(ref<eval_cache::AttrCursor> node, nlohmann::json & obj)> visit;

visit = [&](ref<eval_cache::AttrCursor> node, nlohmann::json & obj) {
flake_schemas::visit(
options.showAllSystems ? std::optional<std::string>() : localSystem,
options.showLegacy,
node,
flake.flake.provenance,

[&](const flake_schemas::Leaf & leaf) {
if (auto what = leaf.what())
obj.emplace("what", *what);

if (auto shortDescription = leaf.shortDescription())
obj.emplace("shortDescription", *shortDescription);

if (auto drv = leaf.derivation(outputs)) {
auto drvObj = nlohmann::json::object();

if (options.showDrvNames)
drvObj.emplace("name", drv->getAttr(state.s.name)->getString());

if (options.showDrvPaths) {
auto drvPath = drv->forceDerivation();
drvObj.emplace("path", state.store->printStorePath(drvPath));
}

if (options.showOutputPaths) {
auto outputs = nlohmann::json::object();
auto drvPath = drv->forceDerivation();
auto drv = evalStore.derivationFromPath(drvPath);
for (auto & i : drv.outputsAndOptPaths(*state.store)) {
if (auto outPath = i.second.second)
outputs.emplace(i.first, state.store->printStorePath(*outPath));
else
outputs.emplace(i.first, nullptr);
}
drvObj.emplace("outputs", std::move(outputs));
}

obj.emplace("derivation", std::move(drvObj));
}

if (auto forSystems = leaf.forSystems())
obj.emplace("forSystems", *forSystems);
},

[&](std::function<void(flake_schemas::ForEachChild)> forEachChild) {
auto children = nlohmann::json::object();
forEachChild([&](Symbol attrName, ref<eval_cache::AttrCursor> node, bool isLast) {
auto & j = children.emplace(state.symbols[attrName], nlohmann::json::object()).first.value();
state.spawn(futures, 1, [&visit, &j, node]() {
try {
visit(node, j);
} catch (EvalError & e) {
// FIXME: make it a flake schema attribute whether to ignore evaluation errors.
if (node->root->state.symbols[node->getAttrPath()[0]] == "legacyPackages")
j.emplace("failed", true);
else
throw;
}
});
});
obj.emplace("children", std::move(children));
},

[&](ref<eval_cache::AttrCursor> node, const std::vector<std::string> & systems) {
obj.emplace("filtered", true);
},

[&](ref<eval_cache::AttrCursor> node) { obj.emplace("isLegacy", true); });
};

auto inv = nlohmann::json::object();

flake_schemas::forEachOutput(
inventory,
[&](Symbol outputName, std::shared_ptr<eval_cache::AttrCursor> output, const std::string & doc, bool isLast) {
auto & j = inv.emplace(state.symbols[outputName], nlohmann::json::object()).first.value();

if (output) {
j.emplace("doc", doc);
auto & j2 = j.emplace("output", nlohmann::json::object()).first.value();
state.spawn(futures, 1, [&visit, output, &j2]() { visit(ref(output), j2); });
} else
j.emplace("unknown", true);
});

futures.finishAll();

return inv;
}

} // namespace nix::flake_schemas

namespace nix {
Expand Down
20 changes: 20 additions & 0 deletions src/libcmd/include/nix/cmd/flake-schemas.hh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
#include "nix/flake/flake.hh"
#include "nix/cmd/command.hh"

namespace nix::flake {
struct LockedFlake;
}

namespace nix::flake_schemas {

using namespace eval_cache;
Expand Down Expand Up @@ -93,4 +97,20 @@ using Schemas = std::map<std::string, SchemaInfo>;

Schemas getSchemas(ref<AttrCursor> root);

struct FlakeInventoryOptions
{
bool showLegacy = false;
bool showAllSystems = false;
bool showOutputPaths = false;
bool showDrvPaths = false;
bool showDrvNames = false;
};

nlohmann::json getFlakeInventory(
EvalState & state,
Store & evalStore,
flake::LockedFlake & flake,
ref<eval_cache::EvalCache> cache,
const FlakeInventoryOptions & options);

} // namespace nix::flake_schemas
8 changes: 7 additions & 1 deletion src/libexpr/attr-path.cc
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@ AttrPath AttrPath::fromStrings(EvalState & state, const std::vector<std::string>

std::string AttrPath::to_string(EvalState & state) const
{
return dropEmptyInitThenConcatStringsSep(".", state.symbols.resolve({*this}));
std::ostringstream ss;
for (const auto & [idx, attr] : enumerate(state.symbols.resolve({*this}))) {
if (idx)
ss << '.';
printAttributeName(ss, attr);
}
return ss.str();
}

std::vector<SymbolStr> AttrPath::resolve(EvalState & state) const
Expand Down
134 changes: 134 additions & 0 deletions src/libexpr/primops/fakeDerivation.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#include "nix/expr/primops.hh"
#include "nix/expr/eval-inline.hh"
#include "nix/store/derivations.hh"
#include "nix/store/store-api.hh"
#include "nix/util/hash.hh"

namespace nix {

static void prim_fakeDerivation(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.fakeDerivation");

std::optional<std::string> name;

struct FakeDerivationOutput
{
StorePath path;
std::optional<Hash> narHash;
};

std::map<std::string, FakeDerivationOutput> outputs;

for (auto & attr : *args[0]->attrs()) {
std::string_view attrName = state.symbols[attr.name];
auto attrHint = fmt("while evaluating the attribute '%s' passed to builtins.fakeDerivation", attrName);

if (attrName == "name") {
name = state.forceStringNoCtx(*attr.value, attr.pos, attrHint);
}

else if (attrName == "outputs") {
state.forceAttrs(*attr.value, attr.pos, attrHint);
for (auto & outAttr : *attr.value->attrs()) {
std::string_view outName = state.symbols[outAttr.name];

state.forceAttrs(
*outAttr.value,
outAttr.pos,
fmt("while evaluating the output '%s' passed to builtins.fakeDerivation", outName));

std::optional<StorePath> path;
std::optional<Hash> narHash;

for (auto & outField : *outAttr.value->attrs()) {
std::string_view fieldName = state.symbols[outField.name];
auto fieldHint =
fmt("while evaluating the attribute '%s' of output '%s' passed to builtins.fakeDerivation",
fieldName,
outName);

if (fieldName == "path") {
auto s = state.forceStringNoCtx(*outField.value, outField.pos, fieldHint);
path = state.store->parseStorePath(s);
}

else if (fieldName == "narHash") {
auto s = state.forceStringNoCtx(*outField.value, outField.pos, fieldHint);
narHash = Hash::parseAny(s, std::nullopt);
}

else
state
.error<EvalError>(
"attribute '%s' isn't supported in an output passed to 'builtins.fakeDerivation'",
fieldName)
.atPos(outField.pos)
.debugThrow();
}

if (!path)
state
.error<EvalError>(
"attribute 'path' is missing in output '%s' passed to 'builtins.fakeDerivation'", outName)
.atPos(outAttr.pos)
.debugThrow();

outputs.emplace(std::string(outName), FakeDerivationOutput{std::move(*path), std::move(narHash)});
}
}

else
state.error<EvalError>("attribute '%s' isn't supported in call to 'builtins.fakeDerivation'", attrName)
.atPos(attr.pos)
.debugThrow();
}

if (!name)
state.error<EvalError>("attribute 'name' is missing in call to 'builtins.fakeDerivation'")
.atPos(pos)
.debugThrow();

if (outputs.empty())
state.error<EvalError>("attribute 'outputs' is missing or empty in call to 'builtins.fakeDerivation'")
.atPos(pos)
.debugThrow();

Derivation drv;
drv.name = *name;
drv.platform = "builtin";
drv.builder = "builtin:substitute";

for (auto & [outName, out] : outputs) {
if (out.narHash)
drv.outputs.insert_or_assign(
outName,
DerivationOutput::CAFixed{
.ca =
ContentAddress{
.method = ContentAddressMethod::Raw::NixArchive,
.hash = *out.narHash,
},
});
else
drv.outputs.insert_or_assign(outName, DerivationOutput::InputAddressed{.path = out.path});
}

auto drvPath = state.store->writeDerivation(*state.asyncPathWriter, drv, state.repair);

// FIXME
state.waitForPath(drvPath);

state.mkStorePathString(drvPath, v);
}

static RegisterPrimOp primop_fakeDerivation({
.name = "__fakeDerivation",
.args = {"attrs"},
.doc = R"(
Placeholder.
)",
.impl = prim_fakeDerivation,
});

} // namespace nix
1 change: 1 addition & 0 deletions src/libexpr/primops/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ generated_headers += gen_header.process(

sources += files(
'context.cc',
'fakeDerivation.cc',
'fetchClosure.cc',
'fetchMercurial.cc',
'fetchTree.cc',
Expand Down
2 changes: 1 addition & 1 deletion src/libexpr/primops/wasm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ struct NixWasmInstance
while (!args.empty()) {
auto arg = &getValue(args[0]);
auto tmp = state.allocValue();
tmp->mkApp(res, {arg});
tmp->mkApp(res, arg);
res = tmp;
args = args.subspan(1);
}
Expand Down
47 changes: 47 additions & 0 deletions src/libstore/build/derivation-trampoline-goal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,53 @@ Goal::Co DerivationTrampolineGoal::haveDerivation(StorePath drvPath, Derivation
},
wantedOutputs.raw);

/* Short-circuit `builtin:substitute`. Since these are never actually built, just create substitution goals for the
* outputs. */
if (drv.type() == DerivationType::Substituted{}) {
std::vector<std::pair<StorePath, GoalPtr>> outputGoals;
Goals waitees;
for (auto & outputName : resolvedWantedOutputs) {
auto i = drv.outputs.find(outputName);
if (i == drv.outputs.end())
throw Error(
"derivation '%s' does not have wanted output '%s'",
worker.store.printStorePath(drvPath),
outputName);
auto outPath = i->second.path(worker.store, drv.name, outputName);
if (!outPath)
throw Error(
"output '%s' of derivation '%s' has no store path",
outputName,
worker.store.printStorePath(drvPath));
auto g = upcast_goal(worker.makePathSubstitutionGoal(*outPath));
outputGoals.emplace_back(*outPath, g);
waitees.insert(g);
}

co_await await(std::move(waitees));

if (nrFailed != 0) {
StringSet failedPaths;
for (auto & [path, g] : outputGoals)
if (g->exitCode != ecSuccess)
failedPaths.insert(worker.store.printStorePath(path));
co_return doneFailure(
ecFailed,
BuildResult::Failure{{
.status = BuildResult::Failure::DependencyFailed,
.msg = HintFmt(
"failed to substitute outputs of '%s': %s",
worker.store.printStorePath(drvPath),
concatStringsSep(", ", quoteStrings(failedPaths))),
}});
}

co_return doneSuccess(
BuildResult::Success{
.status = BuildResult::Success::Substituted,
});
}

Goals concreteDrvGoals;

/* Build this step! */
Expand Down
Loading
Loading